pax_global_header00006660000000000000000000000064127117012120014504gustar00rootroot0000000000000052 comment=3ad43849bd94cd1056303e749524c45e21febeca scantailor-RELEASE_0_9_12_2/000077500000000000000000000000001271170121200154575ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/.gitignore000066400000000000000000000000671271170121200174520ustar00rootroot00000000000000CMakeLists.txt.user* CMakeFiles *.qm *.h.moc build/ *~ scantailor-RELEASE_0_9_12_2/AbstractCommand.h000066400000000000000000000026421271170121200206760ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef ABSTRACTCOMMAND_H_ #define ABSTRACTCOMMAND_H_ #include "RefCountable.h" #include "IntrusivePtr.h" template class AbstractCommand0 : public RefCountable { public: typedef IntrusivePtr Ptr; virtual R operator()() = 0; }; template class AbstractCommand1 : public RefCountable { public: typedef IntrusivePtr Ptr; virtual R operator()(A1 arg1) = 0; }; template class AbstractCommand2 : public RefCountable { public: typedef IntrusivePtr Ptr; virtual R operator()(T1 arg1, T2 arg2) = 0; }; #endif scantailor-RELEASE_0_9_12_2/AbstractFilter.h000066400000000000000000000036431271170121200205470ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef ABSTRACTFILTER_H_ #define ABSTRACTFILTER_H_ #include "RefCountable.h" #include "PageView.h" #include "PageOrderOption.h" #include class FilterUiInterface; class PageId; class ProjectReader; class ProjectWriter; class AbstractRelinker; class QString; class QDomDocument; class QDomElement; /** * Filters represent processing stages, like "Deskew", "Margins" and "Output". */ class AbstractFilter : public RefCountable { public: virtual ~AbstractFilter() {} virtual QString getName() const = 0; virtual PageView getView() const = 0; virtual void selected() {} virtual int selectedPageOrder() const { return -1; } virtual void selectPageOrder(int option) {} virtual std::vector pageOrderOptions() const { return std::vector(); } virtual void performRelinking(AbstractRelinker const& relinker) = 0; virtual void preUpdateUI(FilterUiInterface* ui, PageId const& page_id) = 0; virtual QDomElement saveSettings( ProjectWriter const& writer, QDomDocument& doc) const = 0; virtual void loadSettings( ProjectReader const& reader, QDomElement const& filters_el) = 0; }; #endif scantailor-RELEASE_0_9_12_2/AbstractRelinker.h000066400000000000000000000023311271170121200210660ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef ABSTRACT_RELINKER_H_ #define ABSTRACT_RELINKER_H_ #include "RefCountable.h" class RelinkablePath; class QString; class AbstractRelinker : public RefCountable { public: virtual ~AbstractRelinker() {} /** * 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(RelinkablePath const& orig_path) const = 0; }; #endif scantailor-RELEASE_0_9_12_2/Application.cpp000066400000000000000000000023021271170121200204230ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Application.h" #include "Application.h.moc" #include "OutOfMemoryHandler.h" #include Application::Application(int& argc, char** argv) : QApplication(argc, argv) { } bool Application::notify(QObject* receiver, QEvent* e) { try { return QApplication::notify(receiver, e); } catch (std::bad_alloc const&) { OutOfMemoryHandler::instance().handleOutOfMemorySituation(); return false; } } scantailor-RELEASE_0_9_12_2/Application.h000066400000000000000000000020111271170121200200650ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef APPLICATION_H_ #define APPLICATION_H_ #include class Application : public QApplication { Q_OBJECT public: Application(int& argc, char** argv); virtual bool notify(QObject* receiver, QEvent* e); }; #endif scantailor-RELEASE_0_9_12_2/AtomicFileOverwriter.cpp000066400000000000000000000040131271170121200222660ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "AtomicFileOverwriter.h" #include "Utils.h" #include #include #include AtomicFileOverwriter::AtomicFileOverwriter() { } AtomicFileOverwriter::~AtomicFileOverwriter() { abort(); } QIODevice* AtomicFileOverwriter::startWriting(QString const& file_path) { abort(); m_ptrTempFile.reset(new QTemporaryFile(file_path)); m_ptrTempFile->setAutoRemove(false); if (!m_ptrTempFile->open()) { m_ptrTempFile.reset(); } return m_ptrTempFile.get(); } bool AtomicFileOverwriter::commit() { if (!m_ptrTempFile.get()) { return false; } QString const temp_file_path(m_ptrTempFile->fileName()); QString const target_path(m_ptrTempFile->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(); if (!Utils::overwritingRename(temp_file_path, target_path)) { QFile::remove(temp_file_path); return false; } return true; } void AtomicFileOverwriter::abort() { if (!m_ptrTempFile.get()) { return; } QString const temp_file_path(m_ptrTempFile->fileName()); m_ptrTempFile.reset(); // See comments in commit() QFile::remove(temp_file_path); } scantailor-RELEASE_0_9_12_2/AtomicFileOverwriter.h000066400000000000000000000041161271170121200217370ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef ATOMICFILEOVERWRITER_H_ #define ATOMICFILEOVERWRITER_H_ #include "NonCopyable.h" #include class QString; class QIODevice; class QTemporaryFile; /** * \brief Overwrites files by writing to a temporary file and then replacing * the target file with it. * * Because renaming across volumes doesn't work, we create a temporary file * in the same directory as the target file. */ class AtomicFileOverwriter { DECLARE_NON_COPYABLE(AtomicFileOverwriter) public: 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(QString const& 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 Removes the temporary file without touching the target one. */ void abort(); private: std::auto_ptr m_ptrTempFile; }; #endif scantailor-RELEASE_0_9_12_2/AutoManualMode.h000066400000000000000000000016141271170121200205050ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef AUTOMANUALMODE_H_ #define AUTOMANUALMODE_H_ enum AutoManualMode { MODE_AUTO, MODE_MANUAL }; #endif scantailor-RELEASE_0_9_12_2/BackgroundExecutor.cpp000066400000000000000000000062231271170121200217640ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "BackgroundExecutor.h" #include "OutOfMemoryHandler.h" #include #include #include #include #include #include class BackgroundExecutor::Dispatcher : public QObject { public: Dispatcher(Impl& owner); protected: virtual void customEvent(QEvent* event); private: Impl& m_rOwner; }; class BackgroundExecutor::Impl : public QThread { public: Impl(BackgroundExecutor& owner); ~Impl(); void enqueueTask(TaskPtr const& task); protected: virtual void run(); virtual void customEvent(QEvent* event); private: BackgroundExecutor& m_rOwner; Dispatcher m_dispatcher; bool m_threadStarted; }; /*============================ BackgroundExecutor ==========================*/ BackgroundExecutor::BackgroundExecutor() : m_ptrImpl(new Impl(*this)) { } BackgroundExecutor::~BackgroundExecutor() { } void BackgroundExecutor::shutdown() { m_ptrImpl.reset(); } void BackgroundExecutor::enqueueTask(TaskPtr const& task) { if (m_ptrImpl.get()) { m_ptrImpl->enqueueTask(task); } } /*===================== BackgroundExecutor::Dispatcher =====================*/ BackgroundExecutor::Dispatcher::Dispatcher(Impl& owner) : m_rOwner(owner) { } void BackgroundExecutor::Dispatcher::customEvent(QEvent* event) { try { TaskEvent* evt = dynamic_cast(event); assert(evt); TaskPtr const& task = evt->payload(); assert(task); TaskResultPtr const result((*task)()); if (result) { QCoreApplication::postEvent( &m_rOwner, new ResultEvent(result) ); } } catch (std::bad_alloc const&) { OutOfMemoryHandler::instance().handleOutOfMemorySituation(); } } /*======================= BackgroundExecutor::Impl =========================*/ BackgroundExecutor::Impl::Impl(BackgroundExecutor& owner) : m_rOwner(owner), m_dispatcher(*this), m_threadStarted(false) { m_dispatcher.moveToThread(this); } BackgroundExecutor::Impl::~Impl() { exit(); wait(); } void BackgroundExecutor::Impl::enqueueTask(TaskPtr const& task) { QCoreApplication::postEvent(&m_dispatcher, new TaskEvent(task)); if (!m_threadStarted) { start(); m_threadStarted = true; } } void BackgroundExecutor::Impl::run() { exec(); } void BackgroundExecutor::Impl::customEvent(QEvent* event) { ResultEvent* evt = dynamic_cast(event); assert(evt); TaskResultPtr const& result = evt->payload(); assert(result); (*result)(); } scantailor-RELEASE_0_9_12_2/BackgroundExecutor.h000066400000000000000000000041361271170121200214320ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef BACKGROUNDEXECUTOR_H_ #define BACKGROUNDEXECUTOR_H_ #include "NonCopyable.h" #include "IntrusivePtr.h" #include "AbstractCommand.h" #include "PayloadEvent.h" #include class BackgroundExecutor { DECLARE_NON_COPYABLE(BackgroundExecutor) public: typedef IntrusivePtr > TaskResultPtr; typedef IntrusivePtr > 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(TaskPtr const& task); private: class Impl; class Dispatcher; typedef PayloadEvent TaskEvent; typedef PayloadEvent ResultEvent; std::auto_ptr m_ptrImpl; }; #endif scantailor-RELEASE_0_9_12_2/BackgroundTask.cpp000066400000000000000000000020221271170121200210610ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "BackgroundTask.h" char const* BackgroundTask::CancelledException::what() const throw() { return "BackgroundTask cancelled"; } void BackgroundTask::throwIfCancelled() const { if (isCancelled()) { throw CancelledException(); } } scantailor-RELEASE_0_9_12_2/BackgroundTask.h000066400000000000000000000032661271170121200205410ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef BACKGROUNDTASK_H_ #define BACKGROUNDTASK_H_ #include "AbstractCommand.h" #include "IntrusivePtr.h" #include "FilterResult.h" #include "TaskStatus.h" #include #include class BackgroundTask : public AbstractCommand0, public TaskStatus { public: enum Type { INTERACTIVE, BATCH }; class CancelledException : public std::exception { public: virtual char const* what() const throw(); }; BackgroundTask(Type type) : m_type(type) {} Type type() const { return m_type; } virtual void cancel() { m_cancelFlag.fetchAndStoreRelaxed(1); } virtual bool isCancelled() const { return m_cancelFlag.fetchAndAddRelaxed(0) != 0; } /** * \brief If cancelled, throws CancelledException. */ virtual void throwIfCancelled() const; private: mutable QAtomicInt m_cancelFlag; Type const m_type; }; typedef IntrusivePtr BackgroundTaskPtr; #endif scantailor-RELEASE_0_9_12_2/BasicImageView.cpp000066400000000000000000000025311271170121200210030ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "BasicImageView.h.moc" #include "ImageTransformation.h" #include "ImagePresentation.h" #include "Dpm.h" #include "Dpi.h" BasicImageView::BasicImageView( QImage const& image, ImagePixmapUnion const& downscaled_image, Margins const& 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); } BasicImageView::~BasicImageView() { } scantailor-RELEASE_0_9_12_2/BasicImageView.h000066400000000000000000000024411271170121200204500ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef BASICIMAGEVIEW_H_ #define BASICIMAGEVIEW_H_ #include "ImageViewBase.h" #include "DragHandler.h" #include "ZoomHandler.h" #include "ImagePixmapUnion.h" #include "Margins.h" #include class BasicImageView : public ImageViewBase { Q_OBJECT public: BasicImageView( QImage const& image, ImagePixmapUnion const& downscaled_image = ImagePixmapUnion(), Margins const& margins = Margins()); virtual ~BasicImageView(); private: DragHandler m_dragHandler; ZoomHandler m_zoomHandler; }; #endif scantailor-RELEASE_0_9_12_2/BeforeOrAfter.h000066400000000000000000000016021271170121200203140ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef BEFORE_OR_AFTER_H_ #define BEFORE_OR_AFTER_H_ enum BeforeOrAfter { BEFORE, AFTER }; #endif scantailor-RELEASE_0_9_12_2/BubbleAnimation.cpp000066400000000000000000000057741271170121200212330ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "BubbleAnimation.h" #include "imageproc/Constants.h" #include "imageproc/ColorInterpolation.h" #include #include #include #include #include #include #include #include #include #include using namespace imageproc; BubbleAnimation::BubbleAnimation(int const num_bubbles) : m_numBubbles(num_bubbles), m_curFrame(0) { assert(m_numBubbles > 0); } bool BubbleAnimation::nextFrame( QColor const& head_color, QColor const& tail_color, QPaintDevice* pd, QRectF rect) { if (rect.isNull()) { rect = QRectF(0.0, 0.0, pd->width(), pd->height()); } QPainter painter(pd); return nextFrame(head_color, tail_color, &painter, rect); } bool BubbleAnimation::nextFrame( QColor const& head_color, QColor const& tail_color, QPainter* painter, QRectF const rect) { QPointF const center(rect.center()); double const radius = std::min( center.x() - rect.x(), center.y() - rect.y() ); double const PI = imageproc::constants::PI; double const 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: double const reduced_radius = radius / ( 1.0 + 2.0 * PI * arc_fraction_as_radius / m_numBubbles ); double const bubble_radius = radius - reduced_radius; double const tail_length = 0.5 * m_numBubbles; painter->setRenderHint(QPainter::Antialiasing); painter->setPen(Qt::NoPen); for (int i = 0; i < m_numBubbles; ++i) { double const angle = -0.5 * PI + 2.0 * PI * (m_curFrame - i) / m_numBubbles; double const s = sin(angle); double const c = cos(angle); QPointF const 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); double const 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; } } scantailor-RELEASE_0_9_12_2/BubbleAnimation.h000066400000000000000000000046001271170121200206630ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef BUBBLEANIMATION_H_ #define BUBBLEANIMATION_H_ #include class QColor; class QPaintDevice; class QPainter; /** * \brief Renders a sequence of frames with a circle of bubles of * varying colors. */ class BubbleAnimation { public: 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( QColor const& head_color, QColor const& 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( QColor const& head_color, QColor const& tail_color, QPainter* painter, QRectF rect); private: int m_numBubbles; int m_curFrame; }; #endif scantailor-RELEASE_0_9_12_2/CMakeLists.txt000066400000000000000000000573261271170121200202340ustar00rootroot00000000000000IF(WIN32) CMAKE_MINIMUM_REQUIRED(VERSION 2.8.4) ELSE() CMAKE_MINIMUM_REQUIRED(VERSION 2.6.0) ENDIF() IF(COMMAND cmake_policy) # Suppress warnings. cmake_policy(SET CMP0003 NEW) ENDIF(COMMAND cmake_policy) SET( CMAKE_USER_MAKE_RULES_OVERRIDE "${CMAKE_SOURCE_DIR}/cmake/default_cflags.cmake" ) SET( CMAKE_USER_MAKE_RULES_OVERRIDE_CXX "${CMAKE_SOURCE_DIR}/cmake/default_cxxflags.cmake" ) PROJECT("Scan Tailor") # This forces Visual Studio 2008 SP1 to actually link to the versions of the # libraries it ships with! It's harmless on other platforms. ADD_DEFINITIONS(-D_BIND_TO_CURRENT_VCLIBS_VERSION=1) IF(DEBUG_CLI) ADD_DEFINITIONS(-DDEBUG_CLI) ENDIF(DEBUG_CLI) ENABLE_TESTING() # 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 ) # 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() # 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}" ) 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}" ) 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(CheckIncludeFile) INCLUDE(CheckIncludeFileCXX) INCLUDE(TestCXXAcceptsFlag) INCLUDE(FindBoost) INCLUDE(cmake/FindPthreads.cmake) INCLUDE(cmake/SetDefaultBuildType.cmake) INCLUDE(cmake/SetDefaultGccFlags.cmake) INCLUDE(cmake/ToNativePath.cmake) INCLUDE(cmake/UpdateTranslations.cmake) INCLUDE(cmake/CopyToBuildDir.cmake) INCLUDE(cmake/LibToDLL.cmake) ST_SET_DEFAULT_BUILD_TYPE(Release) IF(CMAKE_COMPILER_IS_GNUCC) ST_SET_DEFAULT_GCC_FLAGS() ENDIF(CMAKE_COMPILER_IS_GNUCC) GET_FILENAME_COMPONENT(source_outer_dir "${PROJECT_SOURCE_DIR}/../../../.." ABSOLUTE) GET_FILENAME_COMPONENT(build_outer_dir "${PROJECT_BINARY_DIR}/.." ABSOLUTE) SET(STAGING_LIBS_DIR "") IF(WIN32) FIND_PATH( DEPS_BUILD_DIR build-qt.bat HINTS "${build_outer_dir}/scantailor-deps-build" DOC "Build directory for Scan Tailor dependencies." ) IF(NOT DEPS_BUILD_DIR) MESSAGE( FATAL_ERROR "The build directory for Scan Tailor dependencies could not be found! " "You can specify it manually in DEPS_BUILD_DIR variable. Make sure you build the dependencies first!" ) ENDIF() SET(STAGING_LIBS_DIR "${DEPS_BUILD_DIR}/staging/libs") IF (NOT EXISTS "${QT_QMAKE_EXECUTABLE}") FILE(GLOB qt_dirs1 "${build_outer_dir}/qt-win-*-4.[0-9]*") FILE(GLOB qt_dirs2 "${build_outer_dir}/qt-everywhere-*-4.[0-9]*") FILE(GLOB qt_dirs3 "${source_outer_dir}/qt-win-*-4.[0-9]*") FILE(GLOB qt_dirs4 "${source_outer_dir}/qt-everywhere-*-4.[0-9]*") FILE(GLOB qt_dirs5 "C:/Qt/4.*.*") FILE(GLOB qt_dirs6 "C:/Qt/20*/qt") FIND_PROGRAM( QT_QMAKE_EXECUTABLE qmake PATHS dummy_dir ${qt_dirs1} ${qt_dirs2} ${qt_dirs3} ${qt_dirs4} ${qt_dirs5} ${qt_dirs6} PATH_SUFFIXES bin ) ENDIF() ENDIF() INCLUDE(FindQt4) IF(NOT QT4_FOUND) MESSAGE( FATAL_ERROR "Qt4 could not be found. " "If it's installed in a non-standard location, specify the path to qmake in QT_QMAKE_EXECUTABLE. " "You can do it in interactive mode (ccmake instead of cmake) or using -DVAR=VAL syntax." ) ENDIF(NOT QT4_FOUND) GET_FILENAME_COMPONENT(QT_BIN_DIR "${QT_QMAKE_EXECUTABLE}" PATH) INCLUDE(FindOpenGL) SET(use_opengl OFF) IF(OPENGL_FOUND AND QT_QTOPENGL_FOUND) # Disabled for now due to various problems associated with it. # SET(use_opengl ON) ENDIF() OPTION(ENABLE_OPENGL "OpenGL may be used for UI acceleration" ${use_opengl}) FILE(GLOB jpeg_dirs1 "${build_outer_dir}/jpeg-[0-9]*") FILE(GLOB jpeg_dirs2 "${source_outer_dir}/jpeg-[0-9]*") FILE(GLOB zlib_dirs1 "${build_outer_dir}/zlib-[0-9]*.[0-9]*.[0-9]*") FILE(GLOB zlib_dirs2 "${source_outer_dir}/zlib-[0-9]*.[0-9]*.[0-9]*") FILE(GLOB png_dirs1 "${build_outer_dir}/libpng-[0-9]*.[0-9]*.[0-9]*") FILE(GLOB png_dirs2 "${source_outer_dir}/libpng-[0-9]*.[0-9]*.[0-9]*") FILE(GLOB png_dirs3 "${build_outer_dir}/lpng[0-9]*") FILE(GLOB png_dirs4 "${source_outer_dir}/lpng[0-9]*") FILE(GLOB tiff_dirs1 "${build_outer_dir}/tiff-[0-9]*.[0-9]*.[0-9]*") FILE(GLOB tiff_dirs2 "${source_outer_dir}/tiff-[0-9]*.[0-9]*.[0-9]*") FILE(GLOB boost_dirs1 "${build_outer_dir}/boost_1_[0-9]*_[0-9]*") FILE(GLOB boost_dirs2 "${source_outer_dir}/boost_1_[0-9]*_[0-9]*") FIND_PATH( JPEG_INCLUDE_DIR jpeglib.h PATHS /usr/local/include /usr/include HINTS ${jpeg_dirs1} ${jpeg_dirs2} DOC "Path to libjpeg headers." ) IF(NOT JPEG_INCLUDE_DIR) MESSAGE( FATAL_ERROR "Could not find jpeg headers.\n" "You may need to install a package named libjpeg62-dev or similarly." ) ENDIF() INCLUDE_DIRECTORIES("${JPEG_INCLUDE_DIR}") FIND_LIBRARY( JPEG_LIBRARY NAMES jpeg libjpeg.lib PATHS /usr/local/lib /usr/lib HINTS ${jpeg_dirs1} ${jpeg_dirs2} ${STAGING_LIBS_DIR} DOC "Path to jpeg library." ) IF(NOT JPEG_LIBRARY) MESSAGE( FATAL_ERROR "Could not find jpeg library.\n" "You may need to install a package named libjpeg62-dev or similarly." ) ENDIF() FIND_PATH( ZLIB_INCLUDE_DIR zlib.h PATHS /usr/local/include /usr/include HINTS ${zlib_dirs1} ${zlib_dirs2} DOC "Path to zlib headers." ) IF(NOT ZLIB_INCLUDE_DIR) MESSAGE( FATAL_ERROR "Could not find zlib headers.\n" "You may need to install a package named zlib1g-dev or similarly." ) ENDIF() INCLUDE_DIRECTORIES("${ZLIB_INCLUDE_DIR}") FIND_LIBRARY( ZLIB_LIBRARY NAMES z zdll.lib PATHS /usr/local/lib /usr/lib HINTS ${zlib_dirs1} ${zlib_dirs2} ${STAGING_LIBS_DIR} DOC "Path to zlib library." ) IF(NOT ZLIB_LIBRARY) MESSAGE( FATAL_ERROR "Could not find zlib library.\n" "You may need to install a package named zlib1g-dev or similarly." ) ENDIF() FIND_PATH( PNG_INCLUDE_DIR png.h PATHS /usr/local/include /usr/include HINTS ${png_dirs1} ${png_dirs2} ${png_dirs3} ${png_dirs4} DOC "Path to libpng headers." ) IF(NOT PNG_INCLUDE_DIR) MESSAGE( FATAL_ERROR "Could not find libpng headers.\n" "You may need to install a package named libpng12-dev or similarly." ) ENDIF() INCLUDE_DIRECTORIES("${PNG_INCLUDE_DIR}") FIND_LIBRARY( PNG_LIBRARY NAMES png libpng.lib PATHS /usr/local/lib /usr/lib HINTS ${png_dirs1} ${png_dirs2} ${png_dirs3} ${png_dirs4} ${STAGING_LIBS_DIR} DOC "Path to png library." ) IF(NOT PNG_LIBRARY) MESSAGE( FATAL_ERROR "Could not find libpng library.\n" "You may need to install a package named libpng12-dev or similarly." ) ENDIF() FIND_PATH( TIFF_INCLUDE_DIR tiff.h PATHS /usr/local/include /usr/include HINTS ${tiff_dirs1} ${tiff_dirs2} PATH_SUFFIXES libtiff DOC "Path to libtiff headers." ) IF(NOT TIFF_INCLUDE_DIR) MESSAGE( FATAL_ERROR "Could not find libtiff headers.\n" "You may need to install a package named libtiff4-dev or similarly." ) ENDIF() INCLUDE_DIRECTORIES("${TIFF_INCLUDE_DIR}") FIND_LIBRARY( TIFF_LIBRARY tiff libtiff.lib PATHS /usr/local/lib /usr/lib HINTS ${tiff_dirs1} ${tiff_dirs2} ${STAGING_LIBS_DIR} PATH_SUFFIXES libtiff DOC "Path to tiff library." ) IF(NOT TIFF_LIBRARY) MESSAGE( FATAL_ERROR "Could not find libtiff library.\n" "You may need to install a package named libtiff4-dev or similarly." ) ENDIF() IF(WIN32) ADD_DEFINITIONS(-DUSE_LIBTIFF_DLL) ENDIF() IF(WIN32) FIND_PATH( BOOST_ROOT boost-build.jam PATHS ${boost_dirs1} ${boost_dirs2} 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.35.0 COMPONENTS unit_test_framework prg_exec_monitor) IF(NOT Boost_FOUND) MESSAGE( FATAL_ERROR "Could not find boost headers or libraries.\n" "You may need to install a package named libboost1.35-dev or similarly." ) ENDIF(NOT Boost_FOUND) INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIRS}) LINK_DIRECTORIES(${Boost_LIBRARY_DIRS}) SET(EXTRA_LIBS "") IF(UNIX) # We need to manually use XRender. INCLUDE(FindX11) IF(NOT X11_Xrender_FOUND) MESSAGE( FATAL_ERROR "Could not find Xrender.h\n" "You may need to install a package called\n" "libxrender-dev or libXrender-dev or similarly." ) ENDIF() INCLUDE_DIRECTORIES("${X11_Xrender_INCLUDE_PATH}") LIST(APPEND EXTRA_LIBS ${X11_Xrender_LIB}) 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(MINGW) ADD_DEFINITIONS(-mthreads) LINK_LIBRARIES(-mthreads) ELSEIF(WIN32) ADD_DEFINITIONS(-DNOMINMAX) ENDIF(UNIX) CHECK_INCLUDE_FILE(stdint.h HAVE_STDINT_H) IF(NOT HAVE_STDINT_H) CONFIGURE_FILE(compat/pstdint.h "${CMAKE_CURRENT_BINARY_DIR}/stdint.h" COPYONLY) INCLUDE_DIRECTORIES("${CMAKE_CURRENT_BINARY_DIR}") ELSE(NOT HAVE_STDINT_H) FILE(REMOVE "${CMAKE_CURRENT_BINARY_DIR}/stdint.h") ENDIF(NOT HAVE_STDINT_H) ADD_DEFINITIONS( ${QT_DEFINITIONS} -DBOOST_MULTI_INDEX_DISABLE_SERIALIZATION ) INCLUDE_DIRECTORIES(${QT_INCLUDES}) LINK_DIRECTORIES(${QT_LIBRARY_DIR}) #SET(QJPEG_LIBRARIES "") IF(WIN32) # FIND_LIBRARY(QJPEG_RELEASE qjpeg PATHS "${QT_PLUGINS_DIR}/imageformats") # FIND_LIBRARY(QJPEG_DEBUG qjpegd PATHS "${QT_PLUGINS_DIR}/imageformats") # MARK_AS_ADVANCED(FORCE QJPEG_RELEASE QJPEG_DEBUG) # IF(QJPEG_RELEASE AND NOT QJPEG_DEBUG) # SET(QJPEG_DEBUG "${QJPEG_RELEASE}") # ELSEIF(QJPEG_DEBUG AND NOT QJPEG_RELEASE) # SET(QJPEG_RELEASE "${QJPEG_DEBUG}") # ELSEIF(NOT QJPEG_DEBUG AND NOT QJPEG_RELEASE) # MESSAGE(FATAL_ERROR "qjpeg plugin wasn't found!") # ENDIF() # SET(QJPEG_LIBRARIES "debug;${QJPEG_DEBUG};optimized;${QJPEG_RELEASE}") # LIST( APPEND EXTRA_LIBS "${QT_QTMAIN_LIBRARY}" winmm imm32 ws2_32 ole32 oleaut32 uuid gdi32 comdlg32 winspool ) ENDIF() # Note that ${JPEG_LIBRARY} must go after qjpeg plugin, because otherwise # the GNU linker won't resolve symbols qjpeg needs from it. LIST(APPEND EXTRA_LIBS ${TIFF_LIBRARY} ${PNG_LIBRARY} ${ZLIB_LIBRARY} ${JPEG_LIBRARY}) IF(ENABLE_OPENGL) LIST(APPEND EXTRA_LIBS ${QT_QTOPENGL_LIBRARY} ${OPENGL_LIBRARIES}) ENDIF() # Crash reporter, only Windows + MSVC supported for now. # Must go before generating config.h, as ENABLE_CRASH_REPORTER # goes in there. IF(MSVC) OPTION(ENABLE_CRASH_REPORTER "Enable crash reporter (only for official builds)" OFF) ENDIF(MSVC) # Prepare config.h IF(WIN32) SET(TRANSLATIONS_DIR_REL "translations") ELSE(WIN32) SET(TRANSLATIONS_DIR_REL "share/scantailor/translations") ENDIF(WIN32) SET(TRANSLATIONS_DIR_ABS "${CMAKE_INSTALL_PREFIX}/${TRANSLATIONS_DIR_REL}") CONFIGURE_FILE(config.h.in ${CMAKE_BINARY_DIR}/config.h @ONLY) # crash_reporter is included unconditionally to collect translation sources from there. ADD_SUBDIRECTORY(crash_reporter) 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}) QT4_WRAP_UI(common_ui_sources ${common_ui_files}) QT4_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_sources) QT4_ADD_RESOURCES(resource_sources resources/resources.qrc) SOURCE_GROUP("Generated" FILES ${common_ui_sources} ${gui_only_ui_sources} ${resource_sources}) SOURCE_GROUP("Resources" FILES resources/resources.qrc resources/icons/COPYING) IF(WIN32) SOURCE_GROUP("Resources" FILES resources/win32/resources.rc) ENDIF() SET( common_sources BackgroundExecutor.cpp BackgroundExecutor.h OpenGLSupport.cpp OpenGLSupport.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 PhysicalTransformation.cpp PhysicalTransformation.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 WorkerThread.cpp WorkerThread.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 OutputFileNameGenerator.cpp OutputFileNameGenerator.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 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 ) SET( cli_only_sources ConsoleBatch.cpp ConsoleBatch.h main-cli.cpp ) SOURCE_GROUP("Sources" FILES ${common_sources} ${gui_only_sources} ${cli_only_sources}) QT4_AUTOMOC(${common_sources} ${gui_only_sources} ${cli_only_sources}) SOURCE_GROUP("Special Headers" FILES version.h config.h.in) IF(CMAKE_COMPILER_IS_GNUCXX) # There seems to be a bug in either gcc or boost that makes page sorting either crash # or go into infinite loop. It's reproducible on a very simple test case on at least # gcc 4.4.3 and gcc 4.4.4. It's not reproducible on gcc 4.5.1. # Adding -fno-strict-aliasing seems to fix it. SET_SOURCE_FILES_PROPERTIES( ThumbnailSequence.cpp PROPERTIES COMPILE_FLAGS "-fno-strict-aliasing" ) ENDIF() 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-cli ${cli_only_sources} ${common_ui_sources}) # Note that order of static libraries matters with gcc. Specifically, # QJPEG_LIBRARIES needs to go before Qt libraries and EXTRA_LIBS needs # to go after both of them. TARGET_LINK_LIBRARIES( scantailor fix_orientation page_split deskew select_content page_layout output stcore dewarping zones interaction imageproc math foundation ${QJPEG_LIBRARIES} ${QT_QTGUI_LIBRARY} ${QT_QTXML_LIBRARY} ${QT_QTCORE_LIBRARY} ${EXTRA_LIBS} ) TARGET_LINK_LIBRARIES( scantailor-cli fix_orientation page_split deskew select_content page_layout output stcore dewarping zones interaction imageproc math foundation ${QJPEG_LIBRARIES} ${QT_QTGUI_LIBRARY} ${QT_QTXML_LIBRARY} ${QT_QTCORE_LIBRARY} ${EXTRA_LIBS} ) INSTALL(TARGETS scantailor scantailor-cli RUNTIME DESTINATION bin) IF(ENABLE_CRASH_REPORTER) FIND_PATH( SYMBOLS_PATH . PATHS "${build_outer_dir}/symbols" "${source_outer_dir}/symbols" NO_DEFAULT_PATH DOC "The directory to write symbol information into." ) IF(NOT SYMBOLS_PATH) MESSAGE(FATAL_ERROR "SYMBOLS_PATH directory is not set.") ENDIF(NOT SYMBOLS_PATH) # We can't build it, because it requires ATL, which is not part # of the Visual Studio Express Edition, so we rely on a pre-built # version which can be found in the Mozilla repository. FIND_PROGRAM( DUMP_SYMS_EXECUTABLE dump_syms PATHS "${build_outer_dir}" "${source_outer_dir}" DOC "Path to dump_syms.exe, which can be found in Mozilla repository." ) IF(NOT DUMP_SYMS_EXECUTABLE) MESSAGE( FATAL_ERROR "dump_syms.exe wasn't found. Specify its location manually by setting the DUMP_SYMS_EXECUTABLE variable. dump_syms.exe may be found in the Mozilla repository under /toolkit/crashreporter/tools/win32" ) ENDIF(NOT DUMP_SYMS_EXECUTABLE) INCLUDE_DIRECTORIES(crash_reporter crash_reporter/google-breakpad) TARGET_LINK_LIBRARIES(scantailor crash_handler) ENDIF(ENABLE_CRASH_REPORTER) # Translations TRANSLATION_SOURCES( scantailor ${common_sources} ${gui_only_sources} ${cli_only_sources} ${common_ui_files} ${gui_only_ui_files} ) FILE(GLOB TRANSLATION_FILES translations/scantailor_*.ts) FILE(GLOB CR_TRANSLATION_FILES translations/crashreporter_*.ts) FINALIZE_TRANSLATION_SET(scantailor ${TRANSLATION_FILES}) FINALIZE_TRANSLATION_SET(crashreporter ${CR_TRANSLATION_FILES}) UPDATE_TRANSLATIONS_TARGET(update_translations scantailor crashreporter) SET(ts_files ${TRANSLATION_FILES}) IF(ENABLE_CRASH_REPORTER) LIST(APPEND ts_files ${CR_TRANSLATION_FILES}) ENDIF(ENABLE_CRASH_REPORTER) # 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) QT4_ADD_TRANSLATION(QM_FILES ${FILTERED_TRANSLATION_FILES}) ADD_CUSTOM_TARGET(compile_translations ALL DEPENDS ${QM_FILES}) INSTALL(FILES ${QM_FILES} DESTINATION "${TRANSLATIONS_DIR_REL}/") # Tests ADD_TEST(ImageProcTests imageproc/tests/imageproc_tests --log_level=message) ADD_TEST(ScanTaylorTests tests/tests --log_level=message) # Source code packaging SET(CPACK_CMAKE_GENERATOR "") SET(CPACK_SOURCE_GENERATOR "TGZ") SET(CPACK_SOURCE_PACKAGE_FILE_NAME "scantailor-${VERSION}") SET( CPACK_SOURCE_IGNORE_FILES "/\\\\.svn/" "/\\\\.git/" "~$" "\\\\.pcs$" "TODO.txt" "CMakeLists.txt.user" "/doxygen/" "${CMAKE_BINARY_DIR}" ) INCLUDE(CPack) IF(WIN32) # Copy some DLLs to the staging dir. IF(MINGW) CHECK_CXX_ACCEPTS_FLAG("-m64" MINGW64) IF(NOT MINGW64) FIND_FILE( MINGWM10_DLL mingwm10.dll PATHS c:/MinGW/bin ENV PATH DOC "Full path to mingwm10.dll" ) IF(NOT MINGWM10_DLL) MESSAGE( FATAL_ERROR "Could not find mingwm10.dll\n" "It shou 'bin' subdirectory of your MinGW installation." ) ENDIF(NOT MINGWM10_DLL) COPY_TO_BUILD_DIR("${MINGWM10_DLL}") FIND_FILE( GCC_DW2_DLL libgcc_s_dw2-1.dll PATHS c:/MinGW/bin ENV PATH DOC "Full path to libgcc_s_dw2-1.dll" ) IF(NOT GCC_DW2_DLL) SET(GCC_DW2_DLL "") # For NSIS MESSAGE( "Could not find libgcc_s_dw2-1.dll\n" "That's fine if your MinGW is old and just doesn't have it." ) ELSE() COPY_TO_BUILD_DIR("${GCC_DW2_DLL}") ENDIF() ENDIF(NOT MINGW64) ENDIF(MINGW) SET( libs_copy_debug "${QT_QTCORE_LIBRARY_DEBUG}" "${QT_QTGUI_LIBRARY_DEBUG}" "${QT_QTXML_LIBRARY_DEBUG}" "${QT_QTNETWORK_LIBRARY_DEBUG}" ) LIB_TO_DLL(libs_copy_debug ${libs_copy_debug}) COPY_TO_BUILD_DIR(${libs_copy_debug} CONFIGURATIONS Debug) SET( libs_copy_nondebug "${QT_QTCORE_LIBRARY_RELEASE}" "${QT_QTGUI_LIBRARY_RELEASE}" "${QT_QTXML_LIBRARY_RELEASE}" "${QT_QTNETWORK_LIBRARY_RELEASE}" ) LIB_TO_DLL(libs_copy_nondebug ${libs_copy_nondebug}) COPY_TO_BUILD_DIR(${libs_copy_nondebug} CONFIGURATIONS Release MinSizeRel RelWithDebInfo) # Qt's imageformat plugins. Note that png is built-in and for tiff we have our own code. COPY_TO_BUILD_DIR("${QT_QJPEG_PLUGIN_RELEASE}" SUBDIR imageformats CONFIGURATIONS Release MinSizeRel RelWithDebInfo) COPY_TO_BUILD_DIR("${QT_QJPEG_PLUGIN_DEBUG}" SUBDIR imageformats CONFIGURATIONS Debug) IF(MINGW) SET(zlib_dll "${QT_BIN_DIR}/libz.dll") ELSE() SET(zlib_dll "${QT_BIN_DIR}/zdll.dll") ENDIF() SET( libs_copy_all_confs "${QT_BIN_DIR}/libpng.dll" "${QT_BIN_DIR}/libjpeg.dll" "${QT_BIN_DIR}/libtiff.dll" "${zlib_dll}" ) COPY_TO_BUILD_DIR(${libs_copy_all_confs}) # Generate the target that will actually do the copying. GENERATE_COPY_TO_BUILD_DIR_TARGET(copy_to_build_dir) # Installer for Windows. Must go the last. ADD_SUBDIRECTORY(packaging/windows) ENDIF(WIN32) scantailor-RELEASE_0_9_12_2/COPYING000066400000000000000000000007201271170121200165110ustar00rootroot00000000000000Scan Tailor 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. The GNU General Public License version 3 can be found in GPL3.txt Some parts of the program are licensed under different, but compatible with GPL3 terms. For more details, see: boost/LICENSE_1_0.txt resources/icons/COPYINGscantailor-RELEASE_0_9_12_2/ChangedStateItemDelegate.h000066400000000000000000000044531271170121200224420ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef CHANGEDSTATEITEMDELEGATE_H_ #define CHANGEDSTATEITEMDELEGATE_H_ #include #include #include /** * \brief A decoration of an existing item delegate * that forces certain item states. */ template class ChangedStateItemDelegate : public T { public: ChangedStateItemDelegate(QObject* parent = 0) : 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, QStyleOptionViewItem const& option, QModelIndex const& index) const { QStyle::State const orig_state = option.state; QStyle::State const 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; }; #endif scantailor-RELEASE_0_9_12_2/CommandLine.cpp000066400000000000000000000355071271170121200203630ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. CommandLine - Interface for ScanTailor's parameters provided on CL. Copyright (C) 2011 Petr Kovar 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include "Dpi.h" #include "ImageId.h" #include "version.h" #include "CommandLine.h" #include "ImageFileInfo.h" #include "ImageMetadata.h" #include "filters/page_split/LayoutType.h" #include "filters/page_layout/Settings.h" #include "Margins.h" #include "Despeckle.h" CommandLine CommandLine::m_globalInstance; void CommandLine::set(CommandLine const& cl) { assert(!m_globalInstance.isGlobal()); m_globalInstance = cl; m_globalInstance.setGlobal(); } void CommandLine::parseCli(QStringList const& argv) { QRegExp rx("^--([^=]+)=(.*)$"); QRegExp rx_switch("^--([^=]+)$"); QRegExp rx_short("^-([^=]+)=(.*)$"); QRegExp rx_short_switch("^-([^=]+)$"); QRegExp rx_project(".*\\.ScanTailor$", Qt::CaseInsensitive); 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> 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 vMetadata; vMetadata.push_back(metadata); ImageFileInfo image_info(file, vMetadata); m_images.push_back(image_info); m_files.push_back(file); } void CommandLine::setup() { m_outputProjectFile = fetchOutputProjectFile(); m_layoutType = fetchLayoutType(); m_layoutDirection = fetchLayoutDirection(); m_colorMode = fetchColorMode(); m_dpi = fetchDpi(); m_outputDpi = fetchDpi("output-dpi"); m_margins = fetchMargins(); m_alignment = fetchAlignment(); m_contentDetection = fetchContentDetection(); m_contentRect = fetchContentRect(); m_orientation = fetchOrientation(); m_threshold = fetchThreshold(); m_deskewAngle = fetchDeskewAngle(); m_startFilterIdx = fetchStartFilterIdx(); m_endFilterIdx = fetchEndFilterIdx(); } void CommandLine::printHelp() { std::cout << "\n"; std::cout << "Scan Tailor is a post-processing tool for scanned pages." << "\n"; std::cout << "Version: " << VERSION << "\n"; std::cout << "\n"; std::cout << "ScanTailor usage: " << "\n"; std::cout << "\t1) scantailor" << "\n"; std::cout << "\t2) scantailor " << "\n"; std::cout << "\t3) scantailor-cli [options] " << "\n"; std::cout << "\t4) scantailor-cli [options] [output_directory]" << "\n"; std::cout << "\n"; std::cout << "1)" << "\n"; std::cout << "\tstart ScanTailor's GUI interface" << "\n"; std::cout << "2)" << "\n"; std::cout << "\tstart ScanTailor's GUI interface and load project file" << "\n"; std::cout << "3)" << "\n"; std::cout << "\tbatch processing images from command line; no GUI" << "\n"; std::cout << "\tfile names are collected from arguments, input directory or stdin (-)" << "\n"; std::cout << "4)" << "\n"; std::cout << "\tbatch processing project from command line; no GUI" << "\n"; std::cout << "\tif output_directory is specified as last argument, it overwrites the one in project file" << "\n"; std::cout << "\n"; std::cout << "Options:" << "\n"; std::cout << "\t--help, -h" << "\n"; std::cout << "\t--verbose, -v" << "\n"; std::cout << "\t--layout=, -l=<0|1|1.5|2>\t\t-- default: 0" << "\n"; std::cout << "\t\t\t 0: auto detect" << "\n"; std::cout << "\t\t\t 1: one page layout" << "\n"; std::cout << "\t\t\t1.5: one page layout but cutting is needed" << "\n"; std::cout << "\t\t\t 2: two page layout" << "\n"; std::cout << "\t--layout-direction=, -ld=\t-- default: lr" << "\n"; std::cout << "\t--orientation=\n\t\t\t\t\t\t-- default: none" << "\n"; std::cout << "\t--rotate=<0.0...360.0>\t\t\t-- it also sets deskew to manual mode" << "\n"; std::cout << "\t--deskew=\t\t\t-- default: auto" << "\n"; std::cout << "\t--content-detection=\n\t\t\t\t\t\t-- default: normal" << "\n"; std::cout << "\t--content-box=<x:x>" << "\n"; std::cout << "\t\t\t\t\t\t-- if set the content detection is se to manual mode" << "\n"; std::cout << "\t\t\t\t\t\t example: --content-box=100x100:1500x2500" << "\n"; std::cout << "\t--margins=\t\t\t-- sets left, top, right and bottom margins to same number." << "\n"; std::cout << "\t\t--margins-left=" << "\n"; std::cout << "\t\t--margins-right=" << "\n"; std::cout << "\t\t--margins-top=" << "\n"; std::cout << "\t\t--margins-bottom=" << "\n"; std::cout << "\t--alignment=center\t\t\t-- sets vertical and horizontal alignment to center" << "\n"; std::cout << "\t\t--alignment-vertical=" << "\n"; std::cout << "\t\t--alignment-horizontal=" << "\n"; std::cout << "\t--dpi=\t\t\t\t-- sets x and y dpi. default: 600" << "\n"; std::cout << "\t\t--dpi-x=" << "\n"; std::cout << "\t\t--dpi-y=" << "\n"; std::cout << "\t--output-dpi=\t\t\t-- sets x and y output dpi. default: 600" << "\n"; std::cout << "\t\t--output-dpi-x=" << "\n"; std::cout << "\t\t--output-dpi-y=" << "\n"; std::cout << "\t--color-mode=\n\t\t\t\t\t\t-- default: black_and_white" << "\n"; std::cout << "\t--white-margins\t\t\t\t-- default: false" << "\n"; std::cout << "\t--normalize-illumination\t\t-- default: false" << "\n"; std::cout << "\t--threshold=\t\t\t\t-- n<0 thinner, n>0 thicker; default: 0" << "\n"; std::cout << "\t--despeckle=\n\t\t\t\t\t\t-- default: normal" << "\n"; std::cout << "\t--dewarping=\t\t\t-- default: off" << "\n"; std::cout << "\t--depth-perception=<1.0...3.0>\t\t-- default: 2.0" << "\n"; std::cout << "\t--start-filter=<1...6>\t\t\t-- default: 4" << "\n"; std::cout << "\t--end-filter=<1...6>\t\t\t-- default: 6" << "\n"; std::cout << "\t--output-project=, -o=" << "\n"; std::cout << "\n"; } page_split::LayoutType CommandLine::fetchLayoutType() { page_split::LayoutType lt = page_split::AUTO_LAYOUT_TYPE; if (!hasLayout()) return lt; if (m_options.value("layout") == "1") lt = page_split::SINGLE_PAGE_UNCUT; else if (m_options.value("layout") == "1.5") lt = page_split::PAGE_PLUS_OFFCUT; else if (m_options.value("layout") == "2") lt = page_split::TWO_PAGES; return lt; } Qt::LayoutDirection CommandLine::fetchLayoutDirection() { Qt::LayoutDirection l = Qt::LeftToRight; if (!hasLayoutDirection()) return l; QString ld = m_options.value("layout-direction").toLower(); if (ld == "rl") l = Qt::RightToLeft; return l; } Dpi CommandLine::fetchDpi(QString oname) { int xdpi=600; int ydpi=600; if (m_options.contains(oname+"-x")) { xdpi = m_options.value(oname+"-x").toInt(); } if (m_options.contains(oname+"-y")) { ydpi = m_options.value(oname+"-y").toInt(); } if (m_options.contains(oname)) { xdpi = m_options.value(oname).toInt(); ydpi = m_options.value(oname).toInt(); } return Dpi(xdpi, ydpi); } output::ColorParams::ColorMode CommandLine::fetchColorMode() { QString cm = m_options.value("color-mode").toLower(); if (cm == "color_grayscale") return output::ColorParams::COLOR_GRAYSCALE; else if (cm == "mixed") return output::ColorParams::MIXED; return output::ColorParams::BLACK_AND_WHITE; } Margins CommandLine::fetchMargins() { Margins margins(page_layout::Settings::defaultHardMarginsMM()); if (m_options.contains("margins")) { double m = m_options.value("margins").toDouble(); margins.setTop(m); margins.setBottom(m); margins.setLeft(m); margins.setRight(m); } if (m_options.contains("margins-left")) margins.setLeft(m_options.value("margins-left").toFloat()); if (m_options.contains("margins-right")) margins.setRight(m_options.value("margins-right").toFloat()); if (m_options.contains("margins-top")) margins.setTop(m_options.value("margins-top").toFloat()); if (m_options.contains("margins-bottom")) margins.setBottom(m_options.value("margins-bottom").toFloat()); return margins; } page_layout::Alignment CommandLine::fetchAlignment() { page_layout::Alignment alignment(page_layout::Alignment::TOP, page_layout::Alignment::HCENTER); if (m_options.contains("alignment")) { alignment.setVertical(page_layout::Alignment::VCENTER); alignment.setHorizontal(page_layout::Alignment::HCENTER); } if (m_options.contains("alignment-vertical")) { QString a = m_options.value("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 (m_options.contains("alignment-horizontal")) { QString a = m_options.value("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); } return alignment; } Despeckle::Level CommandLine::fetchContentDetection() { Despeckle::Level level = Despeckle::NORMAL; if (m_options.value("content-detection") != "") { QString cm = m_options.value("content-detection").toLower(); if (cm == "cautious") level = Despeckle::CAUTIOUS; else if (cm == "aggressive") level = Despeckle::AGGRESSIVE; } return level; } QRectF CommandLine::fetchContentRect() { if (!hasContentRect()) return QRectF(); QRegExp rx("([\\d\\.]+)x([\\d\\.]+):([\\d\\.]+)x([\\d\\.]+)"); if (rx.exactMatch(m_options.value("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.value("content-box").toAscii().constData() << "\n"; exit(1); } CommandLine::Orientation CommandLine::fetchOrientation() { if (!hasOrientation()) return TOP; Orientation orient; QString cli_orient = m_options.value("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.value("orientation").toAscii().constData() << "\n"; exit(1); } return orient; } QString CommandLine::fetchOutputProjectFile() { if (!hasOutputProject()) return QString(); return m_options.value("output-project"); } int CommandLine::fetchThreshold() { if (!hasThreshold()) return 0; return m_options.value("threshold").toInt(); } double CommandLine::fetchDeskewAngle() { if (!hasDeskewAngle()) return 0.0; return m_options.value("rotate").toDouble(); } int CommandLine::fetchStartFilterIdx() { if (!hasStartFilterIdx()) return 0; return m_options.value("start-filter").toInt() - 1; } int CommandLine::fetchEndFilterIdx() { if (!hasEndFilterIdx()) return 5; return m_options.value("end-filter").toInt() - 1; } output::DewarpingMode CommandLine::fetchDewarpingMode() { if (!hasDewarping()) return output::DewarpingMode::OFF; return output::DewarpingMode(m_options.value("dewarping").toLower()); } output::DespeckleLevel CommandLine::fetchDespeckleLevel() { if (!hasDespeckle()) return output::DESPECKLE_NORMAL; return output::despeckleLevelFromString(m_options.value("despeckle")); } output::DepthPerception CommandLine::fetchDepthPerception() { if (!hasDepthPerception()) return output::DepthPerception(); return output::DepthPerception(m_options.value("depth-perception")); } bool CommandLine::hasMargins() const { return( m_options.contains("margins") || m_options.contains("margins-left") || m_options.contains("margins-right") || m_options.contains("margins-top") || m_options.contains("margins-bottom") ); } bool CommandLine::hasAlignment() const { return( m_options.contains("alignment") || m_options.contains("alignment-vertical") || m_options.contains("alignment-horizontal") ); } bool CommandLine::hasOutputDpi() const { return( m_options.contains("output-dpi") || m_options.contains("output-dpi-x") || m_options.contains("output-dpi-y") ); } scantailor-RELEASE_0_9_12_2/CommandLine.h000066400000000000000000000141331271170121200200200ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. CommandLine - Interface for ScanTailor's parameters provided on CL. Copyright (C) 2011 Petr Kovar 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef COMMANDLINE_H_ #define COMMANDLINE_H_ #include #include #include #include "Dpi.h" #include "filters/page_split/LayoutType.h" #include "filters/output/ColorParams.h" #include "filters/output/DespeckleLevel.h" #include "filters/output/DewarpingMode.h" #include "filters/output/DepthPerception.h" #include "filters/page_layout/Alignment.h" #include "ImageFileInfo.h" #include "Margins.h" #include "Despeckle.h" /** * CommandLine is a singleton simulation. * use CommandLine::get() to get access to global class * use CommandLine::set(CommandLine const&) to set the global class */ class CommandLine { // Member-wise copying is OK. public: enum Orientation {TOP, LEFT, RIGHT, UPSIDEDOWN}; static CommandLine const& get() { return m_globalInstance; } static void set(CommandLine const& cl); CommandLine(QStringList const& argv, bool g=true) : m_gui(g), m_global(false) { CommandLine::parseCli(argv); } bool isGui() const { return m_gui; } bool isVerbose() const { return contains("verbose"); } std::vector const& images() const { return m_images; } QString const& outputDirectory() const { return m_outputDirectory; } QString const& projectFile() const { return m_projectFile; } QString const& outputProjectFile() const { return m_outputProjectFile; } bool hasMargins() const; bool hasAlignment() const; bool hasOutputDpi() const; bool hasHelp() const { return contains("help"); } bool hasOutputProject() const { return contains("output-project"); } bool hasLayout() const { return contains("layout"); } bool hasLayoutDirection() const { return contains("layout-direction"); } bool hasStartFilterIdx() const { return contains("start-filter"); } bool hasEndFilterIdx() const { return contains("end-filter"); } bool hasOrientation() const { return contains("orientation"); } bool hasDeskewAngle() const { return contains("rotate"); } bool hasDeskew() const { return contains("deskew"); } bool hasContentRect() const { return contains("content-box"); } bool hasColorMode() const { return contains("color-mode"); } bool hasWhiteMargins() const { return contains("white-margins"); } bool hasNormalizeIllumination() const { return contains("normalize-illumination"); } bool hasThreshold() const { return contains("threshold"); } bool hasDespeckle() const { return contains("despeckle"); } bool hasDewarping() const { return contains("dewarping"); } bool hasDepthPerception() const { return contains("dewarping"); } page_split::LayoutType getLayout() const { return m_layoutType; } Qt::LayoutDirection getLayoutDirection() const { return m_layoutDirection; } output::ColorParams::ColorMode getColorMode() const { return m_colorMode; } Dpi getInputDpi() const { return m_dpi; } Dpi getOutputDpi() const { return m_outputDpi; } Margins getMargins() const { return m_margins; } page_layout::Alignment getAlignment() const { return m_alignment; } Despeckle::Level getContentDetection() const { return m_contentDetection; } QRectF getContentRect() const { return m_contentRect; } Orientation getOrientation() const { return m_orientation; } int getThreshold() const { return m_threshold; } double getDeskewAngle() const { return m_deskewAngle; } int getStartFilterIdx() const { return m_startFilterIdx; } int getEndFilterIdx() const { return m_endFilterIdx; } output::DewarpingMode getDewarpingMode() const { return m_dewarpingMode; } output::DespeckleLevel getDespeckleLevel() const { return m_despeckleLevel; } output::DepthPerception getDepthPerception() const { return m_depthPerception; } bool help() { return m_options.contains("help"); } void printHelp(); private: CommandLine() : m_gui(true), m_global(false) {} static CommandLine m_globalInstance; bool m_gui; bool m_global; bool isGlobal() { return m_global; } void setGlobal() { m_global = true; } bool contains(QString const& 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; page_split::LayoutType m_layoutType; Qt::LayoutDirection m_layoutDirection; output::ColorParams::ColorMode m_colorMode; Dpi m_dpi; Dpi m_outputDpi; Margins m_margins; page_layout::Alignment m_alignment; Despeckle::Level m_contentDetection; QRectF m_contentRect; Orientation m_orientation; int m_threshold; double m_deskewAngle; int m_startFilterIdx; int m_endFilterIdx; output::DewarpingMode m_dewarpingMode; output::DespeckleLevel m_despeckleLevel; output::DepthPerception m_depthPerception; void parseCli(QStringList const& argv); void addImage(QString const& path); void setup(); page_split::LayoutType fetchLayoutType(); output::ColorParams::ColorMode fetchColorMode(); Qt::LayoutDirection fetchLayoutDirection(); Dpi fetchDpi(QString oname="dpi"); Margins fetchMargins(); page_layout::Alignment fetchAlignment(); Despeckle::Level fetchContentDetection(); QRectF fetchContentRect(); Orientation fetchOrientation(); QString fetchOutputProjectFile(); int fetchThreshold(); double fetchDeskewAngle(); int fetchStartFilterIdx(); int fetchEndFilterIdx(); output::DewarpingMode fetchDewarpingMode(); output::DespeckleLevel fetchDespeckleLevel(); output::DepthPerception fetchDepthPerception(); }; #endif scantailor-RELEASE_0_9_12_2/CompositeCacheDrivenTask.h000066400000000000000000000022161271170121200225120ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef COMPOSITECACHEDRIVENTASK_H_ #define COMPOSITECACHEDRIVENTASK_H_ #include "RefCountable.h" class PageInfo; class AbstractFilterDataCollector; class CompositeCacheDrivenTask : public RefCountable { public: virtual ~CompositeCacheDrivenTask() {} virtual void process( PageInfo const& page_info, AbstractFilterDataCollector* collector) = 0; }; #endif scantailor-RELEASE_0_9_12_2/ConsoleBatch.cpp000066400000000000000000000311001271170121200205220ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. ConsoleBatch - Batch processing scanned pages from command line. Copyright (C) 2011 Petr Kovar 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include "Utils.h" #include "ProjectPages.h" #include "PageSelectionAccessor.h" #include "StageSequence.h" #include "ProcessingTaskQueue.h" #include "FileNameDisambiguator.h" #include "OutputFileNameGenerator.h" #include "ImageInfo.h" #include "ImageFileInfo.h" #include "PageInfo.h" #include "PageSequence.h" #include "ImageId.h" #include "ThumbnailPixmapCache.h" #include "LoadFileTask.h" #include "ProjectWriter.h" #include "ProjectReader.h" #include "OrthogonalRotation.h" #include "SelectedPage.h" #include "filters/fix_orientation/Settings.h" #include "filters/fix_orientation/Filter.h" #include "filters/fix_orientation/Task.h" #include "filters/fix_orientation/CacheDrivenTask.h" #include "filters/page_split/Settings.h" #include "filters/page_split/Filter.h" #include "filters/page_split/Task.h" #include "filters/page_split/CacheDrivenTask.h" #include "filters/deskew/Settings.h" #include "filters/deskew/Filter.h" #include "filters/deskew/Task.h" #include "filters/deskew/CacheDrivenTask.h" #include "filters/select_content/Settings.h" #include "filters/select_content/Filter.h" #include "filters/select_content/Task.h" #include "filters/select_content/CacheDrivenTask.h" #include "filters/page_layout/Settings.h" #include "filters/page_layout/Filter.h" #include "filters/page_layout/Task.h" #include "filters/page_layout/CacheDrivenTask.h" #include "filters/output/Settings.h" #include "filters/output/Params.h" #include "filters/output/Filter.h" #include "filters/output/Task.h" #include "filters/output/CacheDrivenTask.h" #include #include #include "ConsoleBatch.h" #include "CommandLine.h" ConsoleBatch::ConsoleBatch(std::vector const& images, QString const& output_directory, Qt::LayoutDirection const layout) : batch(true), debug(true), m_ptrDisambiguator(new FileNameDisambiguator), m_ptrPages(new ProjectPages(images, ProjectPages::AUTO_PAGES, layout)) { PageSelectionAccessor const accessor((IntrusivePtr())); // Won't really be used anyway. m_ptrStages = IntrusivePtr(new StageSequence(m_ptrPages, accessor)); //m_ptrThumbnailCache = IntrusivePtr(new ThumbnailPixmapCache(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()); } ConsoleBatch::ConsoleBatch(QString const project_file) : batch(true), debug(true) { QFile file(project_file); if (!file.open(QIODevice::ReadOnly)) { throw std::runtime_error("Unable to open the project file."); } QDomDocument doc; if (!doc.setContent(&file)) { throw std::runtime_error("The project file is broken."); } file.close(); m_ptrReader.reset(new ProjectReader(doc)); m_ptrPages = m_ptrReader->pages(); PageSelectionAccessor const accessor((IntrusivePtr())); // Won't be used anyway. m_ptrDisambiguator = m_ptrReader->namingDisambiguator(); m_ptrStages = IntrusivePtr(new StageSequence(m_ptrPages, accessor)); m_ptrReader->readFilterSettings(m_ptrStages->filters()); CommandLine const& cli = CommandLine::get(); QString output_directory = m_ptrReader->outputDirectory(); if (!cli.outputDirectory().isEmpty()) { output_directory = cli.outputDirectory(); } //m_ptrThumbnailCache = IntrusivePtr(new 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()); } BackgroundTaskPtr ConsoleBatch::createCompositeTask( PageInfo const& page, int const last_filter_idx) { IntrusivePtr fix_orientation_task; IntrusivePtr page_split_task; IntrusivePtr deskew_task; IntrusivePtr select_content_task; IntrusivePtr page_layout_task; IntrusivePtr 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 BackgroundTaskPtr( new LoadFileTask( BackgroundTask::BATCH, page, m_ptrThumbnailCache, m_ptrPages, fix_orientation_task ) ); } // process the image vector **images** and save output to **output_dir** void ConsoleBatch::process() { CommandLine const& 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("Start filter out of range"); startFilterIdx = sf; } 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("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; itoPageSequence(PAGE_VIEW).pageAt(0); SelectedPage sPage(fpage.id(), IMAGE_VIEW); ProjectWriter writer(m_ptrPages, sPage, m_outFileNameGen); writer.write(project_file, m_ptrStages->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); } void ConsoleBatch::setupFixOrientation(std::set allPages) { IntrusivePtr fix_orientation = m_ptrStages->fixOrientationFilter(); CommandLine const& 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) { IntrusivePtr page_split = m_ptrStages->pageSplitFilter(); CommandLine const& cli = CommandLine::get(); // PAGE SPLIT if (cli.hasLayout()) { page_split->getSettings()->setLayoutTypeForAllPages(cli.getLayout()); } } void ConsoleBatch::setupDeskew(std::set allPages) { IntrusivePtr deskew = m_ptrStages->deskewFilter(); CommandLine const& 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); } } } void ConsoleBatch::setupSelectContent(std::set allPages) { IntrusivePtr select_content = m_ptrStages->selectContentFilter(); CommandLine const& cli = CommandLine::get(); for (std::set::iterator i=allPages.begin(); i!=allPages.end(); i++) { PageId page = *i; // SELECT CONTENT FILTER if (cli.hasContentRect()) { QRectF rect(cli.getContentRect()); QSizeF size_mm(rect.width(), rect.height()); select_content::Dependencies deps; select_content::Params params(rect, size_mm, deps, MODE_MANUAL); select_content->getSettings()->setPageParams(page, params); } } } void ConsoleBatch::setupPageLayout(std::set allPages) { IntrusivePtr page_layout = m_ptrStages->pageLayoutFilter(); CommandLine const& cli = CommandLine::get(); 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.hasMargins()) page_layout->getSettings()->setHardMarginsMM(page, cli.getMargins()); if (cli.hasAlignment()) page_layout->getSettings()->setPageAlignment(page, alignment); } } void ConsoleBatch::setupOutput(std::set allPages) { IntrusivePtr output = m_ptrStages->outputFilter(); CommandLine const& cli = CommandLine::get(); for (std::set::iterator i=allPages.begin(); i!=allPages.end(); i++) { PageId page = *i; // 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.hasWhiteMargins() || cli.hasNormalizeIllumination()) { output::ColorGrayscaleOptions cgo; if (cli.hasWhiteMargins()) cgo.setWhiteMargins(true); if (cli.hasNormalizeIllumination()) cgo.setNormalizeIllumination(true); colorParams.setColorGrayscaleOptions(cgo); } if (cli.hasThreshold()) { output::BlackWhiteOptions bwo; bwo.setThresholdAdjustment(cli.getThreshold()); colorParams.setBlackWhiteOptions(bwo); } params.setColorParams(colorParams); if (cli.hasDespeckle()) params.setDespeckleLevel(cli.getDespeckleLevel()); if (cli.hasDewarping()) params.setDewarpingMode(cli.getDewarpingMode()); if (cli.hasDepthPerception()) params.setDepthPerception(cli.getDepthPerception()); output->getSettings()->setParams(page, params); } } scantailor-RELEASE_0_9_12_2/ConsoleBatch.h000066400000000000000000000046151271170121200202020ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. ConsoleBatch - Batch processing scanned pages from command line. Copyright (C) 2011 Petr Kovar 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef CONSOLEBATCH_H_ #define CONSOLEBATCH_H_ #include #include #include "IntrusivePtr.h" #include "BackgroundTask.h" #include "FilterResult.h" #include "OutputFileNameGenerator.h" #include "PageId.h" #include "PageInfo.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" class ConsoleBatch { // Member-wise copying is OK. public: ConsoleBatch( std::vector const& images, QString const& output_directory, Qt::LayoutDirection const layout); ConsoleBatch(QString const project_file); void process(); void saveProject(QString const project_file); private: bool batch; bool debug; IntrusivePtr m_ptrDisambiguator; IntrusivePtr m_ptrPages; IntrusivePtr m_ptrStages; OutputFileNameGenerator m_outFileNameGen; IntrusivePtr m_ptrThumbnailCache; std::auto_ptr m_ptrReader; void setupFilter(int idx, std::set allPages); void setupFixOrientation(std::set allPages); void setupPageSplit(std::set allPages); void setupDeskew(std::set allPages); void setupSelectContent(std::set allPages); void setupPageLayout(std::set allPages); void setupOutput(std::set allPages); BackgroundTaskPtr createCompositeTask( PageInfo const& page, int const last_filter_idx ); }; #endif scantailor-RELEASE_0_9_12_2/ContentBoxPropagator.cpp000066400000000000000000000052761271170121200223170ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ContentBoxPropagator.h" #include "CompositeCacheDrivenTask.h" #include "ProjectPages.h" #include "PageSequence.h" #include "PageInfo.h" #include "ImageTransformation.h" #include "filters/page_layout/Filter.h" #include "filter_dc/ContentBoxCollector.h" #include class ContentBoxPropagator::Collector : public ContentBoxCollector { public: Collector(); virtual void process( ImageTransformation const& xform, QRectF const& content_rect); bool collected() const { return m_collected; } ImageTransformation const& xform() const { return m_xform; } QRectF const& contentRect() const { return m_contentRect; } private: ImageTransformation m_xform; QRectF m_contentRect; bool m_collected; }; ContentBoxPropagator::ContentBoxPropagator( IntrusivePtr const& page_layout_filter, IntrusivePtr const& task) : m_ptrPageLayoutFilter(page_layout_filter), m_ptrTask(task) { } ContentBoxPropagator::~ContentBoxPropagator() { } void ContentBoxPropagator::propagate(ProjectPages const& pages) { PageSequence const sequence(pages.toPageSequence(PAGE_VIEW)); size_t const num_pages = sequence.numPages(); for (size_t i = 0; i < num_pages; ++i) { PageInfo const& page_info = sequence.pageAt(i); 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()); } } } /*=================== ContentBoxPropagator::Collector ====================*/ ContentBoxPropagator::Collector::Collector() : m_xform(QRectF(0, 0, 1, 1), Dpi(300, 300)), m_collected(false) { } void ContentBoxPropagator::Collector::process( ImageTransformation const& xform, QRectF const& content_rect) { m_xform = xform; m_contentRect = content_rect; m_collected = true; } scantailor-RELEASE_0_9_12_2/ContentBoxPropagator.h000066400000000000000000000033511271170121200217540ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef CONTENTBOXPROPAGATOR_H_ #define CONTENTBOXPROPAGATOR_H_ #include "IntrusivePtr.h" #include class CompositeCacheDrivenTask; class ProjectPages; namespace page_layout { class Filter; } /** * \brief Propagates content boxes from "Select Content" to "Margins" stage. * * This is necessary in the following case:\n * You go back from "Margins" to one of the previous stages and make * adjustments there to several pages. Now you return to "Margins" and * expect to see the results of all your adjustments (not just the current page) * there. */ class ContentBoxPropagator { public: ContentBoxPropagator( IntrusivePtr const& page_layout_filter, IntrusivePtr const& task); ~ContentBoxPropagator(); void propagate(ProjectPages const& pages); private: class Collector; IntrusivePtr m_ptrPageLayoutFilter; IntrusivePtr m_ptrTask; }; #endif scantailor-RELEASE_0_9_12_2/ContentSpanFinder.cpp000066400000000000000000000037461271170121200215610ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ContentSpanFinder.h" #include "imageproc/SlicedHistogram.h" using namespace imageproc; void ContentSpanFinder::findImpl( SlicedHistogram const& histogram, VirtualFunction1& handler) const { int const hist_size = histogram.size(); int i = 0; int content_end = -m_minWhitespaceWidth; int content_begin = content_end; for (;;) { // 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. // 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 (content_end - content_begin >= m_minContentWidth) { handler(Span(content_begin, content_end)); } content_begin = i; } if (i == hist_size) { break; } // 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)); } } scantailor-RELEASE_0_9_12_2/ContentSpanFinder.h000066400000000000000000000036351271170121200212230ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef CONTENTSPANFINDER_H_ #define CONTENTSPANFINDER_H_ #include "Span.h" #include "VirtualFunction.h" namespace imageproc { class SlicedHistogram; } class ContentSpanFinder { // Member-wise copying is OK. public: ContentSpanFinder() : m_minContentWidth(1), m_minWhitespaceWidth(1) {} void setMinContentWidth(int value) { m_minContentWidth = 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(imageproc::SlicedHistogram const& histogram, T handler) const; private: void findImpl( imageproc::SlicedHistogram const& histogram, VirtualFunction1& handler) const; int m_minContentWidth; int m_minWhitespaceWidth; }; template void ContentSpanFinder::find(imageproc::SlicedHistogram const& histogram, T handler) const { ProxyFunction1 proxy(handler); findImpl(histogram, proxy); } #endif scantailor-RELEASE_0_9_12_2/DebugImageView.cpp000066400000000000000000000056501271170121200210150ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "DebugImageView.h" #include "AbstractCommand.h" #include "BackgroundExecutor.h" #include "ImageViewBase.h" #include "BasicImageView.h" #include "ProcessingIndicationWidget.h" #include #include #include class DebugImageView::ImageLoadResult : public AbstractCommand0 { public: ImageLoadResult(QPointer const& owner, QImage const& image) : m_ptrOwner(owner), m_image(image) {} // This method is called from the main thread. virtual void operator()() { if (DebugImageView* owner = m_ptrOwner) { owner->imageLoaded(m_image); } } private: QPointer m_ptrOwner; QImage m_image; }; class DebugImageView::ImageLoader : public AbstractCommand0 { public: ImageLoader(DebugImageView* owner, QString const& file_path) : m_ptrOwner(owner), m_filePath(file_path) {} virtual BackgroundExecutor::TaskResultPtr operator()() { QImage image(m_filePath); return BackgroundExecutor::TaskResultPtr(new ImageLoadResult(m_ptrOwner, image)); } private: QPointer m_ptrOwner; QString m_filePath; }; DebugImageView::DebugImageView(AutoRemovingFile file, boost::function const& 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); } void DebugImageView::setLive(bool const live) { if (live && !m_isLive) { ImageViewBase::backgroundExecutor().enqueueTask( BackgroundExecutor::TaskPtr(new ImageLoader(this, m_file.get())) ); } else if (!live && m_isLive) { if (QWidget* wgt = currentWidget()) { if (wgt != m_pPlaceholderWidget) { removeWidget(wgt); delete wgt; } } } m_isLive = live; } void DebugImageView::imageLoaded(QImage const& image) { if (!m_isLive) { return; } if (currentWidget() == m_pPlaceholderWidget) { std::auto_ptr image_view; if (m_imageViewFactory.empty()) { image_view.reset(new BasicImageView(image)); } else { image_view.reset(m_imageViewFactory(image)); } setCurrentIndex(addWidget(image_view.release())); } } scantailor-RELEASE_0_9_12_2/DebugImageView.h000066400000000000000000000032241271170121200204550ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DEBUG_IMAGE_VIEW_H_ #define DEBUG_IMAGE_VIEW_H_ #include "AutoRemovingFile.h" #include #include #include #include class QImage; class DebugImageView : public QStackedWidget, public boost::intrusive::list_base_hook< boost::intrusive::link_mode > { public: DebugImageView(AutoRemovingFile file, boost::function const& image_view_factory = boost::function(), QWidget* parent = 0); /** * Tells this widget to either display the actual image or just * a placeholder. */ void setLive(bool live); private: class ImageLoadResult; class ImageLoader; void imageLoaded(QImage const& image); AutoRemovingFile m_file; boost::function m_imageViewFactory; QWidget* m_pPlaceholderWidget; bool m_isLive; }; #endif scantailor-RELEASE_0_9_12_2/DebugImages.cpp000066400000000000000000000041111271170121200203340ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "DebugImages.h" #include "imageproc/BinaryImage.h" #include #include #include #include void DebugImages::add( QImage const& image, QString const& label, boost::function const& image_view_factory) { QTemporaryFile file(QDir::tempPath()+"/scantailor-dbg-XXXXXX.png"); if (!file.open()) { return; } AutoRemovingFile arem_file(file.fileName()); file.setAutoRemove(false); QImageWriter writer(&file, "png"); writer.setCompression(2); // Trade space for speed. if (!writer.write(image)) { return; } m_sequence.push_back(IntrusivePtr(new Item(arem_file, label, image_view_factory))); } void DebugImages::add( imageproc::BinaryImage const& image, QString const& label, boost::function const& 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(); } 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(); return file; } scantailor-RELEASE_0_9_12_2/DebugImages.h000066400000000000000000000044131271170121200200060ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DEBUG_IMAGES_H_ #define DEBUG_IMAGES_H_ #include "RefCountable.h" #include "IntrusivePtr.h" #include "AutoRemovingFile.h" #include #include #include class QImage; class QWidget; namespace imageproc { class BinaryImage; } /** * \brief A sequence of image + label pairs. */ class DebugImages { public: void add(QImage const& image, QString const& label, boost::function const& image_view_factory = boost::function()); void add(imageproc::BinaryImage const& image, QString const& label, boost::function const& 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 = 0, boost::function* image_view_factory = 0); private: struct Item : public RefCountable { AutoRemovingFile file; QString label; boost::function imageViewFactory; Item(AutoRemovingFile f, QString const& l, boost::function const& imf) : file(f), label(l), imageViewFactory(imf) {} }; std::deque > m_sequence; }; #endif scantailor-RELEASE_0_9_12_2/Despeckle.cpp000066400000000000000000000656551271170121200201030ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Despeckle.h" #include "TaskStatus.h" #include "DebugImages.h" #include "Dpi.h" #include "FastQueue.h" #include "imageproc/BinaryImage.h" #include "imageproc/ConnectivityMap.h" #include "imageproc/Connectivity.h" #ifndef Q_MOC_RUN #include #endif #include #include #include #include #include #include #include #include #include #include /** * \file * The idea of this despeckling algorithm is as follows: * \li The connected components that are larger than the specified threshold * are marked as non-garbage. * \li If a connected component is close enough to a non-garbage component * and their sizes are comparable or the non-garbage one is larger, then * the other one is also marked as non-garbage. * * The last step may be repeated until no new components are marked. * as non-garbage. */ using namespace imageproc; namespace { /** * We treat vertical distances differently from the horizontal ones. * We want horizontal proximity to have greater weight, so we * multiply the vertical component distances by VERTICAL_SCALE, * so that the distance is not:\n * sqrt(dx^2 + dy^2)\n * but:\n * sqrt(dx^2 + (VERTICAL_SCALE*dy)^2)\n * Keep in mind that we actually operate on squared distances, * so we don't need to take that square root. */ static int const VERTICAL_SCALE = 2; static int const 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, Dpi const& dpi); }; Settings Settings::get(Despeckle::Level const level, Dpi const& dpi) { Settings settings; int const min_dpi = std::min(dpi.horizontal(), dpi.vertical()); double const 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 = 10.0*10.0; settings.bigObjectThreshold = qRound(7 * dpi_factor); break; case Despeckle::NORMAL: settings.minRelativeParentWeight = 0.175 * dpi_factor; settings.pixelsToSqDist = 6.5*6.5; settings.bigObjectThreshold = qRound(12 * dpi_factor); break; case Despeckle::AGGRESSIVE: settings.minRelativeParentWeight = 0.225 * dpi_factor; settings.pixelsToSqDist = 3.5*3.5; settings.bigObjectThreshold = qRound(17 * dpi_factor); break; } return settings; } struct Component { static uint32_t const ANCHORED_TO_BIG = uint32_t(1) << 31; static uint32_t const ANCHORED_TO_SMALL = uint32_t(1) << 30; static uint32_t const 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; Component() : num_pixels(0) {} uint32_t const anchoredToBig() const { return num_pixels & ANCHORED_TO_BIG; } void setAnchoredToBig() { num_pixels |= ANCHORED_TO_BIG; } uint32_t const anchoredToSmall() const { return num_pixels & ANCHORED_TO_SMALL; } void setAnchoredToSmall() { num_pixels |= ANCHORED_TO_SMALL; } bool const anchoredToSmallButNotBig() const { return (num_pixels & TAG_MASK) == ANCHORED_TO_SMALL; } void clearTags() { num_pixels &= ~TAG_MASK; } }; uint32_t const Component::ANCHORED_TO_BIG; uint32_t const Component::ANCHORED_TO_SMALL; uint32_t const 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); } }; struct Vector { int16_t x; int16_t y; }; union Distance { Vector vec; uint32_t raw; static Distance zero() { Distance dist; dist.raw = 0; return dist; } static Distance special() { Distance dist; dist.vec.x = dist.vec.y = std::numeric_limits::max(); return dist; } bool operator==(Distance const& other) const { return raw == other.raw; } bool operator!=(Distance const& other) const { return raw != other.raw; } void reset(int x) { vec.x = std::numeric_limits::max() - x; vec.y = 0; } uint32_t sqdist() const { int const x = vec.x; int const y = vec.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; } } bool operator<(Connection const& 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; } } }; /** * \brief A directional assiciation between two connected components. */ struct TargetSourceConn { uint32_t target; /**< The label of the target connected component. */ uint32_t source; /**< The label of the source connected component. */ 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<(TargetSourceConn const& rhs) const { if (target < rhs.target) { return true; } else if (target > rhs.target) { return false; } else { return source < rhs.source; } } }; /** * \brief If the association didn't exist, create it, * otherwise the minimum distance. */ void updateDistance( std::map& conns, uint32_t label1, uint32_t label2, uint32_t sqdist) { typedef std::map Connections; Connection const conn(label1, label2); Connections::iterator it(conns.lower_bound(conn)); if (it == conns.end() || conn < it->first) { conns.insert(Connections::value_type(conn, sqdist)); } else if (sqdist < it->second) { it->second = sqdist; } } /** * \brief Tag the source component with ANCHORED_TO_SMALL, ANCHORED_TO_BIG * or none of the above. */ void tagSourceComponent( Component& source, Component const& target, uint32_t sqdist, Settings const& 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(); } } /** * Check if the component may be attached to another one. * Attaching a component to another one will preserve the component * being attached, provided that the one it's attached to is also preserved. */ bool canBeAttachedTo( Component const& comp, Component const& target, uint32_t sqdist, Settings const& settings) { if (sqdist <= comp.num_pixels * settings.pixelsToSqDist) { if (target.num_pixels >= comp.num_pixels * settings.minRelativeParentWeight) { return true; } } return false; } void voronoi(ConnectivityMap& cmap, std::vector& dist) { int const width = cmap.size().width() + 2; int const 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 = 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); 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); } } void voronoiSpecial(ConnectivityMap& cmap, std::vector& dist, Distance const special_distance) { int const width = cmap.size().width() + 2; int const 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 = 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]; } } } // 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]; } } } 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]; } } } std::swap(this_sqdist_line, prev_sqdist_line); } } /** * Calculate the minimum distance between components from neighboring * Voronoi segments. */ void voronoiDistances( ConnectivityMap const& cmap, std::vector const& distance_matrix, std::map& conns) { int const width = cmap.size().width(); int const height = cmap.size().height(); int const offsets[] = { -cmap.stride(), -1, 1, cmap.stride() }; uint32_t const* const cmap_data = cmap.data(); Distance const* 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) { uint32_t const label = cmap_data[offset]; assert(label != 0); int const x1 = x + distance_data[offset].vec.x; int const y1 = y + distance_data[offset].vec.y; for (int i = 0; i < 4; ++i) { int const nbh_offset = offset + offsets[i]; uint32_t const nbh_label = cmap_data[nbh_offset]; if (nbh_label == 0 || nbh_label == label) { // label 0 can be encountered in // padding lines. continue; } int const x2 = x + distance_data[nbh_offset].vec.x; int const y2 = y + distance_data[nbh_offset].vec.y; int const dx = x1 - x2; int const dy = y1 - y2; uint32_t const sqdist = dx * dx + dy * dy; updateDistance(conns, label, nbh_label, sqdist); } } } } } // anonymous namespace BinaryImage Despeckle::despeckle( BinaryImage const& src, Dpi const& dpi, Level const level, TaskStatus const& status, DebugImages* const dbg) { BinaryImage dst(src); despeckleInPlace(dst, dpi, level, status, dbg); return dst; } void Despeckle::despeckleInPlace( BinaryImage& image, Dpi const& dpi, Level const level, TaskStatus const& status, DebugImages* const dbg) { Settings const settings(Settings::get(level, dpi)); 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); int const width = image.width(); int const 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; int const cmap_stride = cmap.stride(); for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { uint32_t const 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(); uint32_t const 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::map Connections; // conn -> sqdist Connections conns; voronoiDistances(cmap, distance_matrix, conns); status.throwIfCancelled(); // Tag connected components with ANCHORED_TO_BIG or ANCHORED_TO_SMALL. BOOST_FOREACH(Connections::value_type const& pair, conns) { Connection const conn(pair.first); uint32_t const 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; BOOST_FOREACH(Component const& 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. Distance const zero_distance(Distance::zero()); Distance const special_distance(Distance::special()); for (int y = 0, offset = 0; y < height; ++y, offset += 2) { for (int x = 0; x < width; ++x, ++offset) { uint32_t const label = cmap_data[offset]; assert(label != 0); Component const& 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. BOOST_FOREACH(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()) { Connections::iterator const it(conns.begin()); uint32_t const label1 = it->first.lesser_label; uint32_t const label2 = it->first.greater_label; uint32_t const sqdist = it->second; Component const& comp1 = components[label1]; Component const& comp2 = components[label2]; if (canBeAttachedTo(comp1, comp2, sqdist, settings)) { target_source.push_back(TargetSourceConn(label2, label1)); } if (canBeAttachedTo(comp2, comp1, sqdist, settings)) { target_source.push_back(TargetSourceConn(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; size_t const num_target_sources = target_source.size(); uint32_t prev_label = uint32_t(0) - 1; for (size_t i = 0; i < num_target_sources; ++i) { TargetSourceConn const& 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 (uint32_t label = 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()) { uint32_t const 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. uint32_t const msb = uint32_t(1) << 31; uint32_t* image_line = image.data(); int const 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; } } scantailor-RELEASE_0_9_12_2/Despeckle.h000066400000000000000000000033151271170121200175310ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DESPECKLE_H_ #define DESPECKLE_H_ class Dpi; class TaskStatus; class DebugImages; namespace imageproc { 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( imageproc::BinaryImage const& src, Dpi const& dpi, Level level, TaskStatus const& status, DebugImages* dbg = 0); /** * \brief A slightly faster, in-place version of despeckle(). */ static void despeckleInPlace( imageproc::BinaryImage& image, Dpi const& dpi, Level level, TaskStatus const& status, DebugImages* dbg = 0); }; #endif scantailor-RELEASE_0_9_12_2/Doxyfile000066400000000000000000000234031271170121200171670ustar00rootroot00000000000000# Doxyfile 1.4.6 #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- PROJECT_NAME = Scan Tailor OUTPUT_DIRECTORY = doxygen CREATE_SUBDIRS = YES OUTPUT_LANGUAGE = English BRIEF_MEMBER_DESC = YES REPEAT_BRIEF = YES ABBREVIATE_BRIEF = "The $name class" \ "The $name widget" \ "The $name file" \ is \ provides \ specifies \ contains \ represents \ a \ an \ the ALWAYS_DETAILED_SEC = NO INLINE_INHERITED_MEMB = NO FULL_PATH_NAMES = YES STRIP_FROM_PATH = STRIP_FROM_INC_PATH = SHORT_NAMES = NO JAVADOC_AUTOBRIEF = NO MULTILINE_CPP_IS_BRIEF = NO INHERIT_DOCS = YES SEPARATE_MEMBER_PAGES = NO TAB_SIZE = 8 ALIASES = OPTIMIZE_OUTPUT_FOR_C = NO OPTIMIZE_OUTPUT_JAVA = NO BUILTIN_STL_SUPPORT = YES DISTRIBUTE_GROUP_DOC = NO SUBGROUPING = YES #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- EXTRACT_ALL = YES EXTRACT_PRIVATE = YES EXTRACT_STATIC = NO EXTRACT_LOCAL_CLASSES = YES EXTRACT_LOCAL_METHODS = NO HIDE_UNDOC_MEMBERS = YES HIDE_UNDOC_CLASSES = NO HIDE_FRIEND_COMPOUNDS = NO HIDE_IN_BODY_DOCS = NO INTERNAL_DOCS = NO CASE_SENSE_NAMES = YES HIDE_SCOPE_NAMES = NO SHOW_INCLUDE_FILES = YES INLINE_INFO = YES SORT_MEMBER_DOCS = YES SORT_BRIEF_DOCS = NO SORT_BY_SCOPE_NAME = NO GENERATE_TODOLIST = YES GENERATE_TESTLIST = YES GENERATE_BUGLIST = YES GENERATE_DEPRECATEDLIST= YES ENABLED_SECTIONS = MAX_INITIALIZER_LINES = 30 SHOW_USED_FILES = YES SHOW_DIRECTORIES = NO FILE_VERSION_FILTER = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- QUIET = YES WARNINGS = YES WARN_IF_UNDOCUMENTED = YES WARN_IF_DOC_ERROR = YES WARN_NO_PARAMDOC = NO WARN_FORMAT = "$file:$line: $text" WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- INPUT = FILE_PATTERNS = *.c \ *.cc \ *.cxx \ *.cpp \ *.c++ \ *.d \ *.java \ *.ii \ *.ixx \ *.ipp \ *.i++ \ *.inl \ *.h \ *.hh \ *.hxx \ *.hpp \ *.h++ \ *.idl \ *.odl \ *.cs \ *.php \ *.php3 \ *.inc \ *.m \ *.mm \ *.dox \ *.py \ *.C \ *.CC \ *.C++ \ *.II \ *.I++ \ *.H \ *.HH \ *.H++ \ *.CS \ *.PHP \ *.PHP3 \ *.M \ *.MM \ *.PY RECURSIVE = YES EXCLUDE = build boost EXCLUDE_SYMLINKS = NO EXCLUDE_PATTERNS = EXAMPLE_PATH = EXAMPLE_PATTERNS = * EXAMPLE_RECURSIVE = NO IMAGE_PATH = INPUT_FILTER = FILTER_PATTERNS = FILTER_SOURCE_FILES = NO #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- SOURCE_BROWSER = YES INLINE_SOURCES = NO STRIP_CODE_COMMENTS = YES REFERENCED_BY_RELATION = NO REFERENCES_RELATION = NO USE_HTAGS = NO VERBATIM_HEADERS = NO #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- ALPHABETICAL_INDEX = NO COLS_IN_ALPHA_INDEX = 5 IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- GENERATE_HTML = YES HTML_OUTPUT = html HTML_FILE_EXTENSION = .html HTML_HEADER = HTML_FOOTER = HTML_STYLESHEET = HTML_ALIGN_MEMBERS = YES GENERATE_HTMLHELP = NO CHM_FILE = HHC_LOCATION = GENERATE_CHI = NO BINARY_TOC = NO TOC_EXPAND = NO DISABLE_INDEX = NO ENUM_VALUES_PER_LINE = 4 GENERATE_TREEVIEW = NO TREEVIEW_WIDTH = 250 #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- GENERATE_LATEX = NO LATEX_OUTPUT = latex LATEX_CMD_NAME = latex MAKEINDEX_CMD_NAME = makeindex COMPACT_LATEX = NO PAPER_TYPE = a4wide EXTRA_PACKAGES = LATEX_HEADER = PDF_HYPERLINKS = NO USE_PDFLATEX = NO LATEX_BATCHMODE = NO LATEX_HIDE_INDICES = NO #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- GENERATE_RTF = NO RTF_OUTPUT = rtf COMPACT_RTF = NO RTF_HYPERLINKS = NO RTF_STYLESHEET_FILE = RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- GENERATE_MAN = NO MAN_OUTPUT = man MAN_EXTENSION = .3 MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- GENERATE_XML = NO XML_OUTPUT = xml XML_SCHEMA = XML_DTD = XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- GENERATE_PERLMOD = NO PERLMOD_LATEX = NO PERLMOD_PRETTY = YES PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- ENABLE_PREPROCESSING = YES MACRO_EXPANSION = YES EXPAND_ONLY_PREDEF = NO SEARCH_INCLUDES = YES INCLUDE_PATH = foundation INCLUDE_FILE_PATTERNS = PREDEFINED = DECLARE_NON_COPYABLE(x)= \ Q_DECLARE_TR_FUNCTIONS(x)= EXPAND_AS_DEFINED = SKIP_FUNCTION_MACROS = NO #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- TAGFILES = GENERATE_TAGFILE = ALLEXTERNALS = NO EXTERNAL_GROUPS = YES PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- CLASS_DIAGRAMS = YES HIDE_UNDOC_RELATIONS = YES HAVE_DOT = NO CLASS_GRAPH = YES COLLABORATION_GRAPH = YES GROUP_GRAPHS = YES UML_LOOK = NO TEMPLATE_RELATIONS = NO INCLUDE_GRAPH = YES INCLUDED_BY_GRAPH = YES CALL_GRAPH = NO GRAPHICAL_HIERARCHY = YES DIRECTORY_GRAPH = YES DOT_IMAGE_FORMAT = png DOT_PATH = DOTFILE_DIRS = MAX_DOT_GRAPH_DEPTH = 1000 DOT_TRANSPARENT = NO DOT_MULTI_TARGETS = NO GENERATE_LEGEND = YES DOT_CLEANUP = YES #--------------------------------------------------------------------------- # Configuration::additions related to the search engine #--------------------------------------------------------------------------- SEARCHENGINE = NO scantailor-RELEASE_0_9_12_2/Dpi.cpp000066400000000000000000000025051271170121200167010ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Dpi.h" #include "Dpm.h" #include "imageproc/Constants.h" #include using namespace imageproc; Dpi::Dpi(QSize const size) : m_xDpi(size.width()), m_yDpi(size.height()) { } Dpi::Dpi(Dpm const dpm) : 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); } } bool Dpi::operator==(Dpi const& other) const { return m_xDpi == other.m_xDpi && m_yDpi == other.m_yDpi; } scantailor-RELEASE_0_9_12_2/Dpi.h000066400000000000000000000026311271170121200163460ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DPI_H_ #define DPI_H_ #include class Dpm; /** * \brief Dots per inch (horizontal and vertical). */ class Dpi { public: Dpi() : m_xDpi(0), m_yDpi(0) {} Dpi(int horizontal, int vertical) : m_xDpi(horizontal), m_yDpi(vertical) {} Dpi(Dpm dpm); explicit Dpi(QSize size); int horizontal() const { return m_xDpi; } int vertical() const { return m_yDpi; } QSize toSize() const; bool isNull() const { return m_xDpi <= 1 || m_yDpi <= 1; } bool operator==(Dpi const& other) const; bool operator!=(Dpi const& other) const { return !(*this == other); } private: int m_xDpi; int m_yDpi; }; #endif scantailor-RELEASE_0_9_12_2/Dpm.cpp000066400000000000000000000027651271170121200167150ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Dpm.h" #include "Dpi.h" #include "imageproc/Constants.h" #include #include using namespace imageproc; Dpm::Dpm(QSize const size) : m_xDpm(size.width()), m_yDpm(size.height()) { } Dpm::Dpm(Dpi const dpi) : m_xDpm(qRound(dpi.horizontal() * constants::DPI2DPM)), m_yDpm(qRound(dpi.vertical() * constants::DPI2DPM)) { } Dpm::Dpm(QImage const& image) : m_xDpm(image.dotsPerMeterX()), m_yDpm(image.dotsPerMeterY()) { } bool Dpm::isNull() const { return Dpi(*this).isNull(); } QSize Dpm::toSize() const { if (isNull()) { return QSize(); } else { return QSize(m_xDpm, m_yDpm); } } bool Dpm::operator==(Dpm const& other) const { return m_xDpm == other.m_xDpm && m_yDpm == other.m_yDpm; } scantailor-RELEASE_0_9_12_2/Dpm.h000066400000000000000000000027071271170121200163560ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DPM_H_ #define DPM_H_ #include class Dpi; class QImage; /** * \brief Dots per meter (horizontal and vertical). */ class Dpm { // 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(Dpi dpi); explicit Dpm(QSize size); explicit Dpm(QImage const& image); int horizontal() const { return m_xDpm; } int vertical() const { return m_yDpm; } QSize toSize() const; bool isNull() const; bool operator==(Dpm const& other) const; bool operator!=(Dpm const& other) const { return !(*this == other); } private: int m_xDpm; int m_yDpm; }; #endif scantailor-RELEASE_0_9_12_2/ErrorWidget.cpp000066400000000000000000000023511271170121200204210ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ErrorWidget.h" #include "ErrorWidget.h.moc" #include #include ErrorWidget::ErrorWidget(QString const& 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)); connect(textLabel, SIGNAL(linkActivated(QString const&)), SLOT(linkActivated(QString const&))); } scantailor-RELEASE_0_9_12_2/ErrorWidget.h000066400000000000000000000022531271170121200200670ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef ERRORWIDGET_H_ #define ERRORWIDGET_H_ #include "ui_ErrorWidget.h" #include #include class QString; class ErrorWidget : public QWidget, private Ui::ErrorWidget { Q_OBJECT public: ErrorWidget(QString const& text, Qt::TextFormat fmt = Qt::AutoText); private slots: /** * \see QLabel::linkActivated() */ virtual void linkActivated(QString const& link) {} }; #endif scantailor-RELEASE_0_9_12_2/EstimateBackground.cpp000066400000000000000000000220331271170121200217360ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "EstimateBackground.h" #include "ImageTransformation.h" #include "TaskStatus.h" #include "DebugImages.h" #include "imageproc/GrayImage.h" #include "imageproc/BinaryImage.h" #include "imageproc/BWColor.h" #include "imageproc/BitOps.h" #include "imageproc/Transform.h" #include "imageproc/Scale.h" #include "imageproc/Morphology.h" #include "imageproc/Connectivity.h" #include "imageproc/PolynomialLine.h" #include "imageproc/PolynomialSurface.h" #include "imageproc/PolygonRasterizer.h" #include "imageproc/GrayImage.h" #include "imageproc/GrayRasterOp.h" #include "imageproc/RasterOpGeneric.h" #include "imageproc/SeedFill.h" #include #include #include #include #include #include #include #ifndef Q_MOC_RUN #include #include #include #endif #include #include #include #include #include using namespace imageproc; struct AbsoluteDifference { static uint8_t transform(uint8_t src, uint8_t dst) { return abs(int(src) - int(dst)); } }; /** * The same as seedFillGrayInPlace() with a seed of two black lines * at top and bottom, except here colors may only spread vertically. */ static void seedFillTopBottomInPlace(GrayImage& image) { uint8_t* const data = image.data(); int const stride = image.stride(); int const width = image.width(); int const height = image.height(); std::vector seed_line(height, 0xff); 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. 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(image, "use_method1"); } } else { // image is already set to method2 if (dbg) { dbg->add(image, "use_method2"); } } } imageproc::PolynomialSurface estimateBackground( GrayImage const& input, QPolygonF const& area_to_consider, TaskStatus const& 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"); } status.throwIfCancelled(); morphologicalPreprocessingInPlace(background, dbg); status.throwIfCancelled(); int const width = background.width(); int const height = background.height(); uint8_t const* const bg_data = background.data(); int const bg_stride = background.stride(); 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 (dbg) { dbg->add(mask, "area_to_consider"); } uint32_t* mask_data = mask.data(); int mask_stride = mask.wordsPerLine(); std::vector line(std::max(width, height), 0); uint32_t const msb = uint32_t(1) << 31; status.throwIfCancelled(); // Smooth every horizontal line with a polynomial, // then mask pixels that became significantly lighter. for (int x = 0; x < width; ++x) { uint32_t const mask = ~(msb >> (x & 31)); int const degree = 2; PolynomialLine pl(degree, bg_data + x, height, bg_stride); pl.output(&line[0], height, 1); uint8_t const* 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; } } status.throwIfCancelled(); // Smooth every vertical line with a polynomial, // then mask pixels that became significantly lighter. uint8_t const* bg_line = bg_data; uint32_t* mask_line = mask_data; for (int y = 0; y < height; ++y) { int const 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 (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. int const last_word_idx = (width - 1) >> 5; uint32_t const 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)); } } status.throwIfCancelled(); // Check each vertical line. If it's mostly // white (ignored), then make it completely white. for (int x = 0; x < width; ++x) { uint32_t const 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(); return PolynomialSurface(8, 5, background, mask); } scantailor-RELEASE_0_9_12_2/EstimateBackground.h000066400000000000000000000051531271170121200214070ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef ESTIMATE_BACKGROUND_H_ #define ESTIMATE_BACKGROUND_H_ class ImageTransformation; class TaskStatus; class DebugImages; class QPolygonF; namespace imageproc { class PolynomialSurface; class GrayImage; } /** * \brief Estimates a grayscale background of a scanned page. * * \param input The image of a page. * \param area_to_consider The area in \p input image coordinates to consider. * The resulting surface will only be valid in that area. * This parameter can be an empty polygon, in which case all of the * \p input image area is considered. * \param status The status of a task. If it's cancelled by another thread, * this function may throw an implementation-defined exception. * \param dbg The sink for intermediate images used for debugging purposes. * This argument is optional. * * This implementation can deal with very complex cases, like a page with * a picture covering most of it, but in return, it expects some conditions * to be met: * -# The orientation must be correct. To be precise, it can deal with * a more or less vertical folding line, but not a horizontal one. * -# The page must have some blank margins. The margins must be of * a natural origin, or to be precise, there must not be a noticeable * transition between the page background and the margins. * -# It's better to provide a single page to this function, not a * two page scan. When cutting off one of the pages, feel free * to fill areas near the the edges with black. * -# This implementation can handle dark surroundings around the page, * provided they touch the edges, but it performs better without them. */ imageproc::PolynomialSurface estimateBackground( imageproc::GrayImage const& input, QPolygonF const& area_to_consider, TaskStatus const& status, DebugImages* dbg = 0); #endif scantailor-RELEASE_0_9_12_2/FileNameDisambiguator.cpp000066400000000000000000000174221271170121200223640ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "FileNameDisambiguator.h" #include "RelinkablePath.h" #include "AbstractRelinker.h" #include #include #include #include #include #ifndef Q_MOC_RUN #include #include #include #include #include #include #include #endif using namespace boost::multi_index; class FileNameDisambiguator::Impl { public: Impl(); Impl(QDomElement const& disambiguator_el, boost::function const& file_path_unpacker); QDomElement toXml(QDomDocument& doc, QString const& name, boost::function const& file_path_packer) const; int getLabel(QString const& file_path) const; int registerFile(QString const& file_path); void performRelinking(AbstractRelinker const& relinker); private: class ItemsByFilePathTag; class ItemsByFileNameLabelTag; class UnorderedItemsTag; struct Item { QString filePath; QString fileName; int label; Item(QString const& file_path, int lbl); Item(QString const& file_path, QString const& file_name, int lbl); }; typedef multi_index_container< Item, indexed_by< ordered_unique< tag, member >, ordered_unique< tag, composite_key< Item, member, member > >, sequenced > > > Container; 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; }; /*====================== FileNameDisambiguator =========================*/ FileNameDisambiguator::FileNameDisambiguator() : m_ptrImpl(new Impl) { } FileNameDisambiguator::FileNameDisambiguator( QDomElement const& disambiguator_el) : m_ptrImpl(new Impl(disambiguator_el, boost::lambda::_1)) { } FileNameDisambiguator::FileNameDisambiguator( QDomElement const& disambiguator_el, boost::function const& file_path_unpacker) : m_ptrImpl(new Impl(disambiguator_el, file_path_unpacker)) { } QDomElement FileNameDisambiguator::toXml(QDomDocument& doc, QString const& name) const { return m_ptrImpl->toXml(doc, name, boost::lambda::_1); } QDomElement FileNameDisambiguator::toXml( QDomDocument& doc, QString const& name, boost::function const& file_path_packer) const { return m_ptrImpl->toXml(doc, name, file_path_packer); } int FileNameDisambiguator::getLabel(QString const& file_path) const { return m_ptrImpl->getLabel(file_path); } int FileNameDisambiguator::registerFile(QString const& file_path) { return m_ptrImpl->registerFile(file_path); } void FileNameDisambiguator::performRelinking(AbstractRelinker const& relinker) { m_ptrImpl->performRelinking(relinker); } /*==================== FileNameDisambiguator::Impl ====================*/ FileNameDisambiguator::Impl::Impl() : m_items(), m_itemsByFilePath(m_items.get()), m_itemsByFileNameLabel(m_items.get()), m_unorderedItems(m_items.get()) { } FileNameDisambiguator::Impl::Impl( QDomElement const& disambiguator_el, boost::function const& 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; } QDomElement const file_el(node.toElement()); QString const file_path_shorthand(file_el.attribute("file")); QString const file_path = file_path_unpacker(file_path_shorthand); if (file_path.isEmpty()) { // Unresolved shorthand - skipping this record. continue; } int const label = file_el.attribute("label").toInt(); m_items.insert(Item(file_path, label)); } } QDomElement FileNameDisambiguator::Impl::toXml( QDomDocument& doc, QString const& name, boost::function const& file_path_packer) const { QMutexLocker const locker(&m_mutex); QDomElement el(doc.createElement(name)); BOOST_FOREACH(Item const& item, m_unorderedItems) { QString const 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); } return el; } int FileNameDisambiguator::Impl::getLabel(QString const& file_path) const { QMutexLocker const locker(&m_mutex); ItemsByFilePath::iterator const fp_it(m_itemsByFilePath.find(file_path)); if (fp_it != m_itemsByFilePath.end()) { return fp_it->label; } return 0; } int FileNameDisambiguator::Impl::registerFile(QString const& file_path) { QMutexLocker const locker(&m_mutex); ItemsByFilePath::iterator const fp_it(m_itemsByFilePath.find(file_path)); if (fp_it != m_itemsByFilePath.end()) { return fp_it->label; } int label = 0; QString const file_name(QFileInfo(file_path).fileName()); ItemsByFileNameLabel::iterator const 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. Item const new_item(file_path, file_name, label); m_itemsByFileNameLabel.insert(fn_it, new_item); return label; } void FileNameDisambiguator::Impl::performRelinking(AbstractRelinker const& relinker) { QMutexLocker const locker(&m_mutex); Container new_items; BOOST_FOREACH(Item const& item, m_unorderedItems) { RelinkablePath const 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); } /*============================ Impl::Item =============================*/ FileNameDisambiguator::Impl::Item::Item(QString const& file_path, int lbl) : filePath(file_path), fileName(QFileInfo(file_path).fileName()), label(lbl) { } FileNameDisambiguator::Impl::Item::Item( QString const& file_path, QString const& file_name, int lbl) : filePath(file_path), fileName(file_name), label(lbl) { } scantailor-RELEASE_0_9_12_2/FileNameDisambiguator.h000066400000000000000000000054251271170121200220310ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef FILENAME_DISAMBIGUATOR_H_ #define FILENAME_DISAMBIGUATOR_H_ #include "NonCopyable.h" #include "RefCountable.h" #include #include #include class AbstractRelinker; class QString; class QDomElement; class QDomDocument; /** * \brief Associates an integer label with each file path so that * files with the same name but from different directories * have distinctive labels. * * \note This class is thread-safe. */ class FileNameDisambiguator : public RefCountable { DECLARE_NON_COPYABLE(FileNameDisambiguator) public: FileNameDisambiguator(); /** * \brief Load disambiguation information from XML. */ FileNameDisambiguator(QDomElement const& 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(QDomElement const& disambiguator_el, boost::function const& file_path_unpacker); /** * \brief Serialize disambiguation information to XML. */ QDomElement toXml( QDomDocument& doc, QString const& 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, QString const& name, boost::function const& file_path_packer) const; int getLabel(QString const& file_path) const; int registerFile(QString const& file_path); void performRelinking(AbstractRelinker const& relinker); private: class Impl; std::auto_ptr m_ptrImpl; }; #endif scantailor-RELEASE_0_9_12_2/FilterData.cpp000066400000000000000000000024621271170121200202060ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "FilterData.h" #include "Dpm.h" #include "Dpi.h" #include "imageproc/Grayscale.h" using namespace imageproc; FilterData::FilterData(QImage const& image) : m_origImage(image), m_grayImage(toGrayscale(m_origImage)), m_xform(image.rect(), Dpm(image)), m_bwThreshold(BinaryThreshold::otsuThreshold(m_grayImage)) { } FilterData::FilterData(FilterData const& other, ImageTransformation const& xform) : m_origImage(other.m_origImage), m_grayImage(other.m_grayImage), m_xform(xform), m_bwThreshold(other.m_bwThreshold) { } scantailor-RELEASE_0_9_12_2/FilterData.h000066400000000000000000000030161271170121200176470ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef FILTERDATA_H_ #define FILTERDATA_H_ #include "imageproc/BinaryThreshold.h" #include "imageproc/GrayImage.h" #include "ImageTransformation.h" #include class FilterData { // Member-wise copying is OK. public: FilterData(QImage const& image); FilterData(FilterData const& other, ImageTransformation const& xform); imageproc::BinaryThreshold bwThreshold() const { return m_bwThreshold; } ImageTransformation const& xform() const { return m_xform; } QImage const& origImage() const {return m_origImage;} imageproc::GrayImage const& grayImage() const {return m_grayImage;} private: QImage m_origImage; imageproc::GrayImage m_grayImage; ImageTransformation m_xform; imageproc::BinaryThreshold m_bwThreshold; }; #endif scantailor-RELEASE_0_9_12_2/FilterOptionsWidget.cpp000066400000000000000000000015021271170121200221260ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "FilterOptionsWidget.h.moc" scantailor-RELEASE_0_9_12_2/FilterOptionsWidget.h000066400000000000000000000030601271170121200215740ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef FILTEROPTIONSWIDGET_H_ #define FILTEROPTIONSWIDGET_H_ #include "PageId.h" #include "PageInfo.h" #include class FilterOptionsWidget : public QWidget { Q_OBJECT signals: /** * \brief To be emitted by subclasses when they want to reload the page. */ void reloadRequested(); void invalidateThumbnail(PageId const& page_id); /** * This signature differs from invalidateThumbnail(PageId) in that * it will cause PageInfo stored by ThumbnailSequence to be updated. */ void invalidateThumbnail(PageInfo const& page_info); 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(PageId const& page_id); }; #endif scantailor-RELEASE_0_9_12_2/FilterResult.h000066400000000000000000000026001271170121200202520ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef FILTERRESULT_H_ #define FILTERRESULT_H_ #include "IntrusivePtr.h" #include "RefCountable.h" class AbstractFilter; class FilterUiInterface; class FilterResult : public RefCountable { 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. */ virtual IntrusivePtr filter() = 0; }; typedef IntrusivePtr FilterResultPtr; #endif scantailor-RELEASE_0_9_12_2/FilterUiInterface.h000066400000000000000000000032461271170121200212010ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef FILTERUIINTERFACE_H_ #define FILTERUIINTERFACE_H_ #include "PageId.h" #include "AbstractCommand.h" #include "IntrusivePtr.h" class DebugImages; class FilterOptionsWidget; class QWidget; /** * \brief A reduced interface to MainWindow to allow filters to manupulate the UI. */ class FilterUiInterface { public: enum Ownership { KEEP_OWNERSHIP, TRANSFER_OWNERSHIP }; virtual ~FilterUiInterface() {} virtual void setOptionsWidget( FilterOptionsWidget* widget, Ownership ownership) = 0; virtual void setImageWidget( QWidget* widget, Ownership ownership, DebugImages* debug_images = 0) = 0; virtual void invalidateThumbnail(PageId const& page_id) = 0; virtual void invalidateAllThumbnails() = 0; /** * Returns a callable object that when called will open a relinking dialog. */ virtual IntrusivePtr > relinkingDialogRequester() = 0; }; #endif scantailor-RELEASE_0_9_12_2/FixDpiDialog.cpp000066400000000000000000000560461271170121200205010ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "FixDpiDialog.h" #include "FixDpiDialog.h.moc" #include #include #include #include #include #include #include #include #include #include #include #include #ifndef Q_MOC_RUN #include #include #include #endif #include #include #include // To be able to use it in QVariant Q_DECLARE_METATYPE(ImageMetadata) static int const NEED_FIXING_TAB = 0; static int const ALL_PAGES_TAB = 1; // Requests a group of ImageMetadata objects folded into one. static int const AGGREGATE_METADATA_ROLE = Qt::UserRole; // Same as the one above, but only objects with .isDpiOK() == false // will be considered. static int const AGGREGATE_NOT_OK_METADATA_ROLE = Qt::UserRole + 1; /** * This class computes an aggregate ImageMetadata object from a group of other * ImageMetadata objects. If all ImageMetadata objects in a group are equal, * that will make it an aggregate metadata. Otherwise, a null (default * constructed) ImageMetadata() object will be considered * the DPIs within the group are not consistent, * the aggregate Image metadata object will have zeros both for size and for * DPI values. If the DPIs are consistent but sizes are not, the aggregate * ImageMetadata will have the consistent DPI and zero size. */ class FixDpiDialog::DpiCounts { public: void add(ImageMetadata const& metadata); void remove(ImageMetadata const& metadata); /** * 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; private: struct MetadataComparator { bool operator()(ImageMetadata const& lhs, ImageMetadata const& rhs) const; }; typedef std::map Map; Map m_counts; }; /** * This comparator puts objects that are not OK to the front. */ bool FixDpiDialog::DpiCounts::MetadataComparator::operator()( ImageMetadata const& lhs, ImageMetadata const& rhs) const { bool const lhs_ok = lhs.isDpiOK(); bool const 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; Item(int file_idx, int image_idx) : fileIdx(file_idx), imageIdx(image_idx) {} }; SizeGroup(QSize const& size) : m_size(size) {} void append(Item const& item, ImageMetadata const& metadata); QSize const& size() const { return m_size; } std::vector const& items() const { return m_items; } DpiCounts& dpiCounts() { return m_dpiCounts; } DpiCounts const& dpiCounts() const { return m_dpiCounts; } private: QSize m_size; std::vector m_items; DpiCounts m_dpiCounts; }; class FixDpiDialog::TreeModel : private QAbstractItemModel { public: TreeModel(std::vector const& files); std::vector const& files() const { return m_files; } QAbstractItemModel* model() { return this; } bool allDpisOK() const { return m_dpiCounts.allDpisOK(); } bool isVisibleForFilter(QModelIndex const& parent, int row) const; void applyDpiToSelection( Scope scope, Dpi const& dpi, QItemSelection const& selection); private: struct Tag {}; virtual int columnCount(QModelIndex const& parent) const; virtual int rowCount(QModelIndex const& parent) const; virtual QModelIndex index(int row, int column, QModelIndex const& parent) const; virtual QModelIndex parent(QModelIndex const& index) const; virtual QVariant data(QModelIndex const& index, int role) const; void applyDpiToAllGroups(Scope scope, Dpi const& dpi); void applyDpiToGroup(Scope scope, Dpi const& dpi, SizeGroup& group, DpiCounts& total_dpi_counts); void applyDpiToItem( Scope scope, ImageMetadata const& new_metadata, SizeGroup::Item item, DpiCounts& total_dpi_counts, DpiCounts& group_dpi_counts); void emitAllPagesChanged(QModelIndex const& idx); void emitSizeGroupChanged(QModelIndex const& idx); void emitItemChanged(QModelIndex const& idx); SizeGroup& sizeGroupFor(QSize size); static QString sizeToString(QSize size); static Tag m_allPagesNodeId; static Tag m_sizeGroupNodeId; std::vector m_files; std::vector m_sizes; DpiCounts m_dpiCounts; }; class FixDpiDialog::FilterModel : private QSortFilterProxyModel { public: FilterModel(TreeModel& delegate); QAbstractProxyModel* model() { return this; } private: virtual bool filterAcceptsRow( int source_row, QModelIndex const& source_parent) const; virtual QVariant data(QModelIndex const& index, int role) const; TreeModel& m_rDelegate; }; FixDpiDialog::FixDpiDialog(std::vector const& files, QWidget* parent) : QDialog(parent), m_ptrPages(new TreeModel(files)), m_ptrUndefinedDpiPages(new FilterModel(*m_ptrPages)) { setupUi(this); m_normalPalette = xDpi->palette(); m_errorPalette = m_normalPalette; m_errorPalette.setColor(QPalette::Text, Qt::red); 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(); 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( undefinedDpiView->selectionModel(), SIGNAL(selectionChanged(QItemSelection const&, QItemSelection const&)), this, SLOT(selectionChanged(QItemSelection const&)) ); connect( allPagesView->selectionModel(), SIGNAL(selectionChanged(QItemSelection const&, QItemSelection const&)), this, SLOT(selectionChanged(QItemSelection const&)) ); connect( dpiCombo, SIGNAL(activated(int)), this, SLOT(dpiComboChangedByUser(int)) ); connect( xDpi, SIGNAL(textEdited(QString const&)), this, SLOT(dpiValueChanged()) ); connect( yDpi, SIGNAL(textEdited(QString const&)), this, SLOT(dpiValueChanged()) ); connect(applyBtn, SIGNAL(clicked()), this, SLOT(applyClicked())); enableDisableOkButton(); } FixDpiDialog::~FixDpiDialog() { } std::vector const& FixDpiDialog::files() const { return m_ptrPages->files(); } void FixDpiDialog::tabChanged(int const tab) { QTreeView* views[2]; views[NEED_FIXING_TAB] = undefinedDpiView; views[ALL_PAGES_TAB] = allPagesView; updateDpiFromSelection(views[tab]->selectionModel()->selection()); } void FixDpiDialog::selectionChanged(QItemSelection const& selection) { updateDpiFromSelection(selection); } void FixDpiDialog::dpiComboChangedByUser(int const index) { QVariant const data(dpiCombo->itemData(index)); if (data.isValid()) { QSize const dpi(data.toSize()); xDpi->setText(QString::number(dpi.width())); yDpi->setText(QString::number(dpi.height())); dpiValueChanged(); } } void FixDpiDialog::dpiValueChanged() { updateDpiCombo(); Dpi const dpi(xDpi->text().toInt(), yDpi->text().toInt()); ImageMetadata const metadata(m_selectedItemPixelSize, dpi); decorateDpiInputField(xDpi, metadata.horizontalDpiStatus()); decorateDpiInputField(yDpi, metadata.verticalDpiStatus()); if (m_xDpiInitialValue == xDpi->text() && m_yDpiInitialValue == yDpi->text()) { applyBtn->setEnabled(false); return; } if (metadata.isDpiOK()) { applyBtn->setEnabled(true); return; } applyBtn->setEnabled(false); } void FixDpiDialog::applyClicked() { Dpi const dpi(xDpi->text().toInt(), yDpi->text().toInt()); QItemSelectionModel* selection_model = 0; if (tabWidget->currentIndex() == ALL_PAGES_TAB) { selection_model = allPagesView->selectionModel(); QItemSelection const selection(selection_model->selection()); m_ptrPages->applyDpiToSelection(ALL, dpi, selection); } else { selection_model = undefinedDpiView->selectionModel(); QItemSelection const selection( m_ptrUndefinedDpiPages->model()->mapSelectionToSource( selection_model->selection() ) ); m_ptrPages->applyDpiToSelection(NOT_OK, dpi, selection); } updateDpiFromSelection(selection_model->selection()); enableDisableOkButton(); } void FixDpiDialog::enableDisableOkButton() { bool const enable = m_ptrPages->allDpisOK(); buttonBox->button(QDialogButtonBox::Ok)->setEnabled(enable); } /** * This function work with both TreeModel and FilterModel selections. * It is assumed that only a single item is selected. */ void FixDpiDialog::updateDpiFromSelection(QItemSelection const& 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. QVariant const 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(); } void FixDpiDialog::setDpiForm(ImageMetadata const& metadata) { Dpi const dpi(metadata.dpi()); if (dpi.isNull()) { resetDpiForm(); 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(); } void FixDpiDialog::updateDpiCombo() { bool x_ok = true, y_ok = true; QSize const dpi(xDpi->text().toInt(&x_ok), yDpi->text().toInt(&y_ok)); if (x_ok && y_ok) { int const count = dpiCombo->count(); for (int i = 0; i < count; ++i) { QVariant const data(dpiCombo->itemData(i)); if (data.isValid()) { if (dpi == data.toSize()) { dpiCombo->setCurrentIndex(i); return; } } } } 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; } } /*====================== FixDpiDialog::DpiCounts ======================*/ void FixDpiDialog::DpiCounts::add(ImageMetadata const& metadata) { ++m_counts[metadata]; } void FixDpiDialog::DpiCounts::remove(ImageMetadata const& 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. Map::const_iterator const it(m_counts.begin()); return (it == m_counts.end() || it->first.isDpiOK()); } ImageMetadata FixDpiDialog::DpiCounts::aggregate(Scope const scope) const { Map::const_iterator const it(m_counts.begin()); 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(); } Map::const_iterator next(it); ++next; 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; } return ImageMetadata(); } /*====================== FixDpiDialog::SizeGroup ======================*/ void FixDpiDialog::SizeGroup::append(Item const& item, ImageMetadata const& metadata) { m_items.push_back(item); m_dpiCounts.add(metadata); } /*====================== FixDpiDialog::TreeModel ======================*/ FixDpiDialog::TreeModel::Tag FixDpiDialog::TreeModel::m_allPagesNodeId; FixDpiDialog::TreeModel::Tag FixDpiDialog::TreeModel::m_sizeGroupNodeId; FixDpiDialog::TreeModel::TreeModel(std::vector const& files) : m_files(files) { int const num_files = m_files.size(); for (int i = 0; i < num_files; ++i) { ImageFileInfo const& file = m_files[i]; int const num_images = file.imageInfo().size(); for (int j = 0; j < num_images; ++j) { ImageMetadata const& metadata = file.imageInfo()[j]; SizeGroup& group = sizeGroupFor(metadata.size()); group.append(SizeGroup::Item(i, j), metadata); m_dpiCounts.add(metadata); } } } bool FixDpiDialog::TreeModel::isVisibleForFilter(QModelIndex const& parent, int row) const { void const* 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. SizeGroup const& group = m_sizes[parent.row()]; SizeGroup::Item const& item = group.items()[row]; ImageFileInfo const& file = m_files[item.fileIdx]; return !file.imageInfo()[item.imageIdx].isDpiOK(); } else { // Should not happen. return false; } } void FixDpiDialog::TreeModel::applyDpiToSelection( Scope const scope, Dpi const& dpi, QItemSelection const& selection) { if (selection.isEmpty()) { return; } QModelIndex const parent(selection.front().parent()); int const row = selection.front().top(); void const* const ptr = parent.internalPointer(); QModelIndex const 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()]; SizeGroup::Item const& item = group.items()[row]; ImageMetadata const metadata(group.size(), dpi); applyDpiToItem(scope, metadata, item, m_dpiCounts, group.dpiCounts()); emitItemChanged(idx); } } int FixDpiDialog::TreeModel::columnCount(QModelIndex const& parent) const { return 1; } int FixDpiDialog::TreeModel::rowCount(QModelIndex const& parent) const { void const* const ptr = parent.internalPointer(); if (!parent.isValid()) { // The single 'All Pages' item. return 1; } else if (ptr == &m_allPagesNodeId) { // Size groups. return m_sizes.size(); } else if (ptr == &m_sizeGroupNodeId) { // Images within a size group. return m_sizes[parent.row()].items().size(); } else { // Children of an image. return 0; } } QModelIndex FixDpiDialog::TreeModel::index(int const row, int const column, QModelIndex const& parent) const { void const* 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()]); } return QModelIndex(); } QModelIndex FixDpiDialog::TreeModel::parent(QModelIndex const& index) const { void const* 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. SizeGroup const* group = static_cast(ptr); return createIndex(group - &m_sizes[0], index.column(), &m_sizeGroupNodeId); } } QVariant FixDpiDialog::TreeModel::data(QModelIndex const& index, int const role) const { void const* 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. SizeGroup const& 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. SizeGroup const* group = static_cast(ptr); SizeGroup::Item const& item = group->items()[index.row()]; ImageFileInfo const& file = m_files[item.fileIdx]; if (role == Qt::DisplayRole) { QString const& 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(); } void FixDpiDialog::TreeModel::applyDpiToAllGroups(Scope const scope, Dpi const& dpi) { int const num_groups = m_sizes.size(); for (int i = 0; i < num_groups; ++i) { applyDpiToGroup(scope, dpi, m_sizes[i], m_dpiCounts); } } void FixDpiDialog::TreeModel::applyDpiToGroup( Scope const scope, Dpi const& dpi, SizeGroup& group, DpiCounts& total_dpi_counts) { DpiCounts& group_dpi_counts = group.dpiCounts(); ImageMetadata const metadata(group.size(), dpi); std::vector const& items = group.items(); int const num_items = 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( Scope const scope, ImageMetadata const& new_metadata, SizeGroup::Item const item, DpiCounts& total_dpi_counts, DpiCounts& group_dpi_counts) { ImageFileInfo& file = m_files[item.fileIdx]; ImageMetadata& old_metadata = file.imageInfo()[item.imageIdx]; 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); old_metadata = new_metadata; } void FixDpiDialog::TreeModel::emitAllPagesChanged(QModelIndex const& idx) { int const num_groups = m_sizes.size(); for (int i = 0; i < num_groups; ++i) { QModelIndex const group_node(index(i, 0, idx)); int const num_items = rowCount(group_node); for (int j = 0; j < num_items; ++j) { QModelIndex const 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); } void FixDpiDialog::TreeModel::emitSizeGroupChanged(QModelIndex const& 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 'All Pages' node. QModelIndex const all_pages_node(idx.parent()); emit dataChanged(all_pages_node, all_pages_node); } void FixDpiDialog::TreeModel::emitItemChanged(QModelIndex const& idx) { // The item itself. emit dataChanged(idx, idx); // The size group node. QModelIndex const group_node(idx.parent()); emit dataChanged(group_node, group_node); // The 'All Pages' node. QModelIndex const all_pages_node(group_node.parent()); emit dataChanged(all_pages_node, all_pages_node); } FixDpiDialog::SizeGroup& FixDpiDialog::TreeModel::sizeGroupFor(QSize const size) { using namespace boost::lambda; std::vector::iterator const 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.push_back(SizeGroup(size)); return m_sizes.back(); } } QString FixDpiDialog::TreeModel::sizeToString(QSize const size) { 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()); } bool FixDpiDialog::FilterModel::filterAcceptsRow( int const source_row, QModelIndex const& source_parent) const { return m_rDelegate.isVisibleForFilter(source_parent, source_row); } QVariant FixDpiDialog::FilterModel::data(QModelIndex const& index, int role) const { if (role == AGGREGATE_METADATA_ROLE) { role = AGGREGATE_NOT_OK_METADATA_ROLE; } return QSortFilterProxyModel::data(index, role); } scantailor-RELEASE_0_9_12_2/FixDpiDialog.h000066400000000000000000000042171271170121200201370ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #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 class QItemSelection; class FixDpiDialog : public QDialog, private Ui::FixDpiDialog { Q_OBJECT public: FixDpiDialog(std::vector const& files, QWidget* parent = 0); virtual ~FixDpiDialog(); std::vector const& files() const; private slots: void tabChanged(int tab); void selectionChanged(QItemSelection const& selection); void dpiComboChangedByUser(int index); void dpiValueChanged(); void applyClicked(); private: class DpiCounts; class SizeGroup; class TreeModel; class FilterModel; enum Scope { ALL, NOT_OK }; void enableDisableOkButton(); void updateDpiFromSelection(QItemSelection const& selection); void resetDpiForm(); void setDpiForm(ImageMetadata const& metadata); void updateDpiCombo(); void decorateDpiInputField(QLineEdit* field, ImageMetadata::DpiStatus dpi_status) const; std::auto_ptr m_ptrPages; std::auto_ptr m_ptrUndefinedDpiPages; QString m_xDpiInitialValue; QString m_yDpiInitialValue; QSize m_selectedItemPixelSize; QPalette m_normalPalette; QPalette m_errorPalette; }; #endif scantailor-RELEASE_0_9_12_2/GPL3.txt000066400000000000000000001045131271170121200167310ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . scantailor-RELEASE_0_9_12_2/ImageFileInfo.cpp000066400000000000000000000021331271170121200206200ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ImageFileInfo.h" #include #include #include bool ImageFileInfo::isDpiOK() const { using namespace boost::lambda; return std::find_if( m_imageInfo.begin(), m_imageInfo.end(), !bind(&ImageMetadata::isDpiOK, _1) ) == m_imageInfo.end(); } scantailor-RELEASE_0_9_12_2/ImageFileInfo.h000066400000000000000000000026311271170121200202700ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEFILEINFO_H_ #define IMAGEFILEINFO_H_ #include "ImageMetadata.h" #include #include class ImageFileInfo { // Member-wise copying is OK. public: ImageFileInfo(QFileInfo const& file_info, std::vector const& image_info) : m_fileInfo(file_info), m_imageInfo(image_info) {} QFileInfo const& fileInfo() const { return m_fileInfo; } std::vector& imageInfo() { return m_imageInfo; } std::vector const& imageInfo() const { return m_imageInfo; } bool isDpiOK() const; private: QFileInfo m_fileInfo; std::vector m_imageInfo; }; #endif scantailor-RELEASE_0_9_12_2/ImageId.cpp000066400000000000000000000027431271170121200174700ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ImageId.h" #include ImageId::ImageId(QString const& file_path, int const page) : m_filePath(file_path), m_page(page) { } ImageId::ImageId(QFileInfo const& file_info, int const page) : m_filePath(file_info.absoluteFilePath()), m_page(page) { } bool operator==(ImageId const& lhs, ImageId const& rhs) { return lhs.page() == rhs.page() && lhs.filePath() == rhs.filePath(); } bool operator!=(ImageId const& lhs, ImageId const& rhs) { return !(lhs == rhs); } bool operator<(ImageId const& lhs, ImageId const& rhs) { int const comp = lhs.filePath().compare(rhs.filePath()); if (comp < 0) { return true; } else if (comp > 0) { return false; } return lhs.page() < rhs.page(); } scantailor-RELEASE_0_9_12_2/ImageId.h000066400000000000000000000034371271170121200171360ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEID_H_ #define IMAGEID_H_ #include class QFileInfo; class ImageId { // Member-wise copying is OK. public: ImageId() : m_filePath(), m_page(0) {} explicit ImageId(QString const& file_path, int page = 0); explicit ImageId(QFileInfo const& file_info, int page = 0); bool isNull() const { return m_filePath.isNull(); } QString const& filePath() const { return m_filePath; } void setFilePath(QString const& path) { m_filePath = path; } int page() const { return m_page; } void setPage(int page) { m_page = page; } int zeroBasedPage() const { return m_page > 0 ? m_page - 1 : 0; } bool isMultiPageFile() const { return m_page > 0; } 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; }; bool operator==(ImageId const& lhs, ImageId const& rhs); bool operator!=(ImageId const& lhs, ImageId const& rhs); bool operator<(ImageId const& lhs, ImageId const& rhs); #endif scantailor-RELEASE_0_9_12_2/ImageInfo.cpp000066400000000000000000000023001271170121200200140ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ImageInfo.h" ImageInfo::ImageInfo() : m_numSubPages(0), m_leftHalfRemoved(false), m_rightHalfRemoved(false) { } ImageInfo::ImageInfo( ImageId const& id, ImageMetadata const& 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) { } scantailor-RELEASE_0_9_12_2/ImageInfo.h000066400000000000000000000034551271170121200174750ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEINFO_H_ #define IMAGEINFO_H_ #include "ImageId.h" #include "ImageMetadata.h" /** * This class stores the same information about an image as ProjectPages does, * and is used for adding images to ProjectPages objects. Beyond that, * ProjectPages doesn't operate with ImageInfo objects, but with PageInfo ones. */ class ImageInfo { // Member-wise copying is OK. public: ImageInfo(); ImageInfo(ImageId const& id, ImageMetadata const& metadata, int num_sub_pages, bool left_page_removed, bool right_page_removed); ImageId const& id() const { return m_id; } ImageMetadata const& metadata() const { return m_metadata; } int numSubPages() const { return m_numSubPages; } bool leftHalfRemoved() const { return m_leftHalfRemoved; } 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. }; #endif scantailor-RELEASE_0_9_12_2/ImageLoader.cpp000066400000000000000000000030441271170121200203350ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ImageLoader.h" #include "TiffReader.h" #include "ImageId.h" #include #include #include #include QImage ImageLoader::load(ImageId const& image_id) { return load(image_id.filePath(), image_id.zeroBasedPage()); } QImage ImageLoader::load(QString const& file_path, int const page_num) { QFile file(file_path); if (!file.open(QIODevice::ReadOnly)) { return QImage(); } return load(file, page_num); } QImage ImageLoader::load(QIODevice& io_dev, int const 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(); } QImage image; image.load(&io_dev, 0); return image; } scantailor-RELEASE_0_9_12_2/ImageLoader.h000066400000000000000000000021331271170121200200000ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGELOADER_H_ #define IMAGELOADER_H_ class ImageId; class QImage; class QString; class QIODevice; class ImageLoader { public: static QImage load(QString const& file_path, int page_num = 0); static QImage load(ImageId const& image_id); static QImage load(QIODevice& io_dev, int page_num); }; #endif scantailor-RELEASE_0_9_12_2/ImageMetadata.cpp000066400000000000000000000044031271170121200206470ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ImageMetadata.h" #include "imageproc/Constants.h" using namespace imageproc::constants; bool ImageMetadata::operator==(ImageMetadata const& 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; } } bool ImageMetadata::isDpiOK() const { return horizontalDpiStatus() == DPI_OK && verticalDpiStatus() == DPI_OK; } ImageMetadata::DpiStatus ImageMetadata::horizontalDpiStatus() const { return dpiStatus(m_size.width(), m_dpi.horizontal()); } ImageMetadata::DpiStatus ImageMetadata::verticalDpiStatus() const { 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 < 150) { return DPI_TOO_SMALL; } if (dpi > 9999) { return DPI_TOO_LARGE; } double const 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; } scantailor-RELEASE_0_9_12_2/ImageMetadata.h000066400000000000000000000032411271170121200203130ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEMETADATA_H_ #define IMAGEMETADATA_H_ #include #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 }; ImageMetadata() {} ImageMetadata(QSize size, Dpi dpi) : m_size(size), m_dpi(dpi) {} QSize const& size() const { return m_size; } void setSize(QSize const& size) { m_size = size; } Dpi const& dpi() const { return m_dpi; } void setDpi(Dpi const& dpi) { m_dpi = dpi; } bool isDpiOK() const; DpiStatus horizontalDpiStatus() const; DpiStatus verticalDpiStatus() const; bool operator==(ImageMetadata const& other) const; bool operator!=(ImageMetadata const& other) const { return !(*this == other); } private: static DpiStatus dpiStatus(int pixel_size, int dpi); QSize m_size; Dpi m_dpi; }; #endif scantailor-RELEASE_0_9_12_2/ImageMetadataLoader.cpp000066400000000000000000000034011271170121200217730ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ImageMetadataLoader.h" #include "ImageMetadata.h" #include #include #include ImageMetadataLoader::LoaderList ImageMetadataLoader::m_sLoaders; void ImageMetadataLoader::registerLoader( IntrusivePtr const& loader) { m_sLoaders.push_back(loader); } ImageMetadataLoader::Status ImageMetadataLoader::loadImpl( QIODevice& io_device, VirtualFunction1& out) { LoaderList::iterator it(m_sLoaders.begin()); LoaderList::iterator const end(m_sLoaders.end()); for (; it != end; ++it) { Status const status = (*it)->loadMetadata(io_device, out); if (status != FORMAT_NOT_RECOGNIZED) { return status; } } return FORMAT_NOT_RECOGNIZED; } ImageMetadataLoader::Status ImageMetadataLoader::loadImpl( QString const& file_path, VirtualFunction1& out) { QFile file(file_path); if (!file.open(QIODevice::ReadOnly)) { return GENERIC_ERROR; } return loadImpl(file, out); } scantailor-RELEASE_0_9_12_2/ImageMetadataLoader.h000066400000000000000000000062631271170121200214510ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEMETADATALOADER_H_ #define IMAGEMETADATALOADER_H_ #include "VirtualFunction.h" #include "RefCountable.h" #include "IntrusivePtr.h" #include class QString; class QIODevice; class ImageMetadata; class ImageMetadataLoader : public RefCountable { 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(IntrusivePtr const& loader); template static Status load(QIODevice& io_device, OutFunc out); template static Status load(QString const& file_path, OutFunc out); protected: virtual ~ImageMetadataLoader() {} /** * \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, VirtualFunction1& out) = 0; private: static Status loadImpl( QIODevice& io_device, VirtualFunction1& out); static Status loadImpl( QString const& file_path, VirtualFunction1& out); typedef std::vector > LoaderList; static LoaderList m_sLoaders; }; template ImageMetadataLoader::Status ImageMetadataLoader::load(QIODevice& io_device, OutFunc out) { ProxyFunction1 proxy(out); return loadImpl(io_device, proxy); } template ImageMetadataLoader::Status ImageMetadataLoader::load(QString const& file_path, OutFunc out) { ProxyFunction1 proxy(out); return loadImpl(file_path, proxy); } #endif scantailor-RELEASE_0_9_12_2/ImagePixmapUnion.h000066400000000000000000000024741271170121200210510ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGE_PIXMAP_UNION_H_ #define IMAGE_PIXMAP_UNION_H_ #include #include class ImagePixmapUnion { // Member-wise copying is OK. public: ImagePixmapUnion() {} ImagePixmapUnion(QImage const& image) : m_image(image) {} ImagePixmapUnion(QPixmap const& pixmap) : m_pixmap(pixmap) {} QImage const& image() const { return m_image; } QPixmap const& pixmap() const { return m_pixmap; } bool isNull() const { return m_image.isNull() && m_pixmap.isNull(); } private: QImage m_image; QPixmap m_pixmap; }; #endif scantailor-RELEASE_0_9_12_2/ImagePresentation.h000066400000000000000000000044001271170121200212440ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGE_PRESENTATION_H_ #define IMAGE_PRESENTATION_H_ #include #include #include /** * Image presentation consists of 3 components: * \li A transformation from pixel coordinates to some arbitrary virtual coordinates. * Such transformation usually comes from an instance of ImageTransformation. * \li Image crop area, in virtual coordinates, that specifies which parts of the * image should be visible. * \li Display area, in virtual coordinates, which is usually the bounding box * of the image crop area, but can be larger, to reserve space for custom drawing * or extra controls. */ class ImagePresentation { // Member-wise copying is OK. public: ImagePresentation(QTransform const& xform, QPolygonF const& crop_area) : m_xform(xform), m_cropArea(crop_area), m_displayArea(crop_area.boundingRect()) {} ImagePresentation(QTransform const& xform, QPolygonF const& crop_area, QRectF const& display_area) : m_xform(xform), m_cropArea(crop_area), m_displayArea(display_area) {} QTransform const& transform() const { return m_xform; } void setTransform(QTransform const& xform) { m_xform = xform; } QPolygonF const& cropArea() const { return m_cropArea; } void setCropArea(QPolygonF const& crop_area) { m_cropArea = crop_area; } QRectF const& displayArea() const { return m_displayArea; } void setDisplayArea(QRectF const& display_area) { m_displayArea = display_area; } private: QTransform m_xform; QPolygonF m_cropArea; QRectF m_displayArea; }; #endif scantailor-RELEASE_0_9_12_2/ImageTransformation.cpp000066400000000000000000000155111271170121200221370ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ImageTransformation.h" #include #include #include ImageTransformation::ImageTransformation( QRectF const& orig_image_rect, Dpi const& orig_dpi) : m_postRotation(0.0), m_origRect(orig_image_rect), m_resultingRect(orig_image_rect), m_origDpi(orig_dpi) { preScaleToEqualizeDpi(); } ImageTransformation::~ImageTransformation() { } void ImageTransformation::preScaleToDpi(Dpi const& dpi) { if (m_origDpi.isNull() || dpi.isNull()) { return; } m_preScaledDpi = dpi; double const xscale = (double)dpi.horizontal() / m_origDpi.horizontal(); double const yscale = (double)dpi.vertical() / m_origDpi.vertical(); QSizeF const new_pre_scaled_image_size( m_origRect.width() * xscale, m_origRect.height() * yscale ); // Undo's for the specified steps. QTransform const undo21(m_preRotateXform.inverted() * m_preScaleXform.inverted()); QTransform const undo4321(m_postRotateXform.inverted() * m_preCropXform.inverted() * undo21); // 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 #3: pre-crop. QTransform const 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 #5: post-crop. QTransform const 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(); } void ImageTransformation::preScaleToEqualizeDpi() { int const min_dpi = std::min(m_origDpi.horizontal(), m_origDpi.vertical()); preScaleToDpi(Dpi(min_dpi, min_dpi)); } void ImageTransformation::setPreRotation(OrthogonalRotation const rotation) { m_preRotation = rotation; m_preRotateXform = m_preRotation.transform(m_origRect.size()); resetPreCropArea(); resetPostRotation(); resetPostCrop(); resetPostScale(); update(); } void ImageTransformation::setPreCropArea(QPolygonF const& area) { m_preCropArea = area; m_preCropXform = calcCropXform(area); resetPostRotation(); resetPostCrop(); resetPostScale(); update(); } void ImageTransformation::setPostRotation(double const degrees) { m_postRotateXform = calcPostRotateXform(degrees); m_postRotation = degrees; resetPostCrop(); resetPostScale(); update(); } void ImageTransformation::setPostCropArea(QPolygonF const& area) { m_postCropArea = area; m_postCropXform = calcCropXform(area); resetPostScale(); update(); } void ImageTransformation::postScaleToDpi(Dpi const& dpi) { m_postScaledDpi = dpi; m_postScaleXform = calcPostScaleXform(dpi); update(); } QTransform ImageTransformation::calcCropXform(QPolygonF const& area) { QRectF const bounds(area.boundingRect()); QTransform xform; xform.translate(-bounds.x(), -bounds.y()); return xform; } QTransform ImageTransformation::calcPostRotateXform(double const degrees) { QTransform xform; if (degrees != 0.0) { QPointF const 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. QPolygonF const pre_rotate_poly(m_preCropXform.map(m_preCropArea)); QRectF const pre_rotate_rect(pre_rotate_poly.boundingRect()); QPolygonF const post_rotate_poly(xform.map(pre_rotate_poly)); QRectF const 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(Dpi const& target_dpi) { 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 ... QTransform const to_orig(m_postScaleXform * m_transform.inverted()); // IMPORTANT: in the above line we assume post-scale is the last transform. QLineF const hor_unit(QPointF(0, 0), QPointF(1, 0)); QLineF const vert_unit(QPointF(0, 0), QPointF(0, 1)); QLineF const orig_hor_unit(to_orig.map(hor_unit)); QLineF const orig_vert_unit(to_orig.map(vert_unit)); double const xscale = target_dpi.horizontal() * orig_hor_unit.length() / m_origDpi.horizontal(); double const yscale = target_dpi.vertical() * orig_vert_unit.length() / m_origDpi.vertical(); QTransform xform; xform.scale(xscale, yscale); return xform; } void ImageTransformation::resetPreCropArea() { m_preCropArea.clear(); m_preCropXform.reset(); } void ImageTransformation::resetPostRotation() { m_postRotation = 0.0; m_postRotateXform.reset(); } void ImageTransformation::resetPostCrop() { m_postCropArea.clear(); m_postCropXform.reset(); } void ImageTransformation::resetPostScale() { m_postScaledDpi = Dpi(); m_postScaleXform.reset(); } void ImageTransformation::update() { QTransform const pre_scale_then_pre_rotate(m_preScaleXform * m_preRotateXform); // 12 QTransform const pre_crop_then_post_rotate(m_preCropXform * m_postRotateXform); // 34 QTransform const post_crop_then_post_scale(m_postCropXform * m_postScaleXform); // 56 QTransform const 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(); } scantailor-RELEASE_0_9_12_2/ImageTransformation.h000066400000000000000000000171101271170121200216010ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGETRANSFORMATION_H_ #define IMAGETRANSFORMATION_H_ #include "OrthogonalRotation.h" #include "Dpi.h" #include #include #include /** * \brief Provides a transformed view of an image. * * \anchor transformations * Suppose we need to make the following transformations * to a particular image (in this order): * -# Scale it, to achieve the desired optical resolution. * It's generally done only in case input had different * horizontal and vertical DPI. We refer to this operation * as pre-scale. * -# Rotate it by a multiple of 90 degrees, * swapping its width and height if necessary. * That's done on the "Fix Orientation" stage. We refer to * this operation as pre-rotate. * -# Crop it, reducing its width and height. * That's done on the "Split Pages" stage, where we crop * to the bounding box of a polygon formed by cutters and * remaining image edges. We refer to this operation as * pre-crop. * -# Apply a rotation around the image center, * increasing its width and height as necessary. * That's done on the "Deskew" stage. We refer to this * operation as post-rotate. * -# Apply another crop, or maybe extend the image boundaries, * in order to place the required margins around content. * That's done on the "Margins" stage. We refer to this operation * as post-crop. * -# Scale the image to desired DPI. That's done on the "Output" stage. * We refer to this operation as post-scale. * * Instead of actually modifying the image, we provide * a mapping from the original image coordinates to the new ones. * Note that all transformation steps are optional. */ class ImageTransformation { public: // Member-wise copying is OK. ImageTransformation(QRectF const& orig_image_rect, Dpi const& orig_dpi); ~ImageTransformation(); /** * \brief Set the 1st step transformation, recalculating the following ones. * * \see \ref transformations Transformations. */ void preScaleToDpi(Dpi const& 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. */ Dpi const& 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. */ Dpi const& 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(QPolygonF const& 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. */ QPolygonF const& 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. */ QPolygonF const& 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(QPolygonF const& 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. */ QPolygonF const& 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(Dpi const& dpi); /** * \brief Returns the transformation matrix from the original * to resulting image coordinates. */ QTransform const& transform() const { return m_transform; } /** * \brief Returns the transformation matrix from the resulting * to original image coordinates. */ QTransform const& transformBack() const { return m_invTransform; } /** * \brief Returns the original image rectangle, as specified. */ QRectF const& 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. */ QRectF const& resultingRect() const { return m_resultingRect; } private: QTransform calcCropXform(QPolygonF const& crop_area); QTransform calcPostRotateXform(double degrees); QTransform calcPostScaleXform(Dpi const& 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; }; #endif scantailor-RELEASE_0_9_12_2/ImageViewBase.cpp000066400000000000000000001025711271170121200206410ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ImageViewBase.h" #include "ImageViewBase.h.moc" #include "NonCopyable.h" #include "ImagePresentation.h" #include "OpenGLSupport.h" #include "PixmapRenderer.h" #include "BackgroundExecutor.h" #include "Dpm.h" #include "Dpi.h" #include "ScopedIncDec.h" #include "imageproc/PolygonUtils.h" #include "imageproc/Transform.h" #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef ENABLE_OPENGL #include #include #endif using namespace imageproc; class ImageViewBase::HqTransformTask : public AbstractCommand0 > >, public QObject { DECLARE_NON_COPYABLE(HqTransformTask) public: HqTransformTask( ImageViewBase* image_view, QImage const& image, QTransform const& xform, QSize const& target_size); void cancel() { m_ptrResult->cancel(); } bool const isCancelled() const { return m_ptrResult->isCancelled(); } virtual IntrusivePtr > operator()(); private: class Result : public AbstractCommand0 { public: Result(ImageViewBase* image_view); void setData(QPoint const& origin, QImage const& hq_image); void cancel() { m_cancelFlag.fetchAndStoreRelaxed(1); } bool isCancelled() const { return m_cancelFlag.fetchAndAddRelaxed(0) != 0; } virtual void operator()(); private: QPointer m_ptrImageView; QPoint m_origin; QImage m_hqImage; mutable QAtomicInt m_cancelFlag; }; IntrusivePtr m_ptrResult; QImage m_image; QTransform m_xform; QSize m_targetSize; }; /** * \brief Temporarily adjust the widget focal point, then change it back. * * When adjusting and restoring the widget focal point, the pixmap * focal point is recalculated accordingly. */ class ImageViewBase::TempFocalPointAdjuster { public: /** * Change the widget focal point to obj.centeredWidgetFocalPoint(). */ TempFocalPointAdjuster(ImageViewBase& obj); /** * Change the widget focal point to \p temp_widget_fp */ TempFocalPointAdjuster(ImageViewBase& obj, QPointF temp_widget_fp); /** * Restore the widget focal point. */ ~TempFocalPointAdjuster(); private: ImageViewBase& m_rObj; QPointF m_origWidgetFP; }; class ImageViewBase::TransformChangeWatcher { public: TransformChangeWatcher(ImageViewBase& owner); ~TransformChangeWatcher(); private: ImageViewBase& m_rOwner; QTransform m_imageToVirtual; QTransform m_virtualToWidget; QRectF m_virtualDisplayArea; }; ImageViewBase::ImageViewBase( QImage const& image, ImagePixmapUnion const& downscaled_version, ImagePresentation const& presentation, Margins const& margins) : 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) { #ifdef ENABLE_OPENGL 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)); } } #endif 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()) ); 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())); } ImageViewBase::~ImageViewBase() { } void ImageViewBase::hqTransformSetEnabled(bool const enabled) { if (!enabled && m_hqTransformEnabled) { // Turning off. m_hqTransformEnabled = false; if (m_ptrHqTransformTask.get()) { 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(); } } QImage ImageViewBase::createDownscaledImage(QImage const& image) { assert(!image.isNull()); // Original and downscaled DPM. Dpm const o_dpm(image); Dpm const d_dpm(Dpi(300, 300)); int const o_w = image.width(); int const 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); 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); return transform( image, xform, QRect(0, 0, d_w, d_h), OutsidePixels::assumeColor(Qt::white) ); } QRectF ImageViewBase::maxViewportRect() const { QRectF const 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 { QRectF const 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 { QRectF const widget_rect(m_virtualToWidget.mapRect(virtualDisplayRect())); return widget_rect.intersected(dynamicViewportRect()); } void ImageViewBase::setWidgetFocalPoint(QPointF const& widget_fp) { setNewWidgetFP(widget_fp, /*update =*/true); } void ImageViewBase::adjustAndSetWidgetFocalPoint(QPointF const& widget_fp) { adjustAndSetNewWidgetFP(widget_fp, /*update=*/true); } void ImageViewBase::setZoomLevel(double zoom) { if (m_zoom != zoom) { m_zoom = zoom; updateWidgetTransform(); update(); } } void ImageViewBase::moveTowardsIdealPosition(double const pixel_length) { if (pixel_length <= 0) { // The name implies we are moving *towards* the ideal position. return; } QPointF const ideal_widget_fp(getIdealWidgetFocalPoint(CENTER_IF_FITS)); if (ideal_widget_fp == m_widgetFocalPoint) { return; } QPointF vec(ideal_widget_fp - m_widgetFocalPoint); double const max_length = 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(ImagePresentation const& presentation) { TransformChangeWatcher const watcher(*this); TempFocalPointAdjuster const temp_fp(*this); m_imageToVirtual = presentation.transform(); m_virtualToImage = m_imageToVirtual.inverted(); m_virtualImageCropArea = presentation.cropArea(); m_virtualDisplayArea = presentation.displayArea(); updateWidgetTransform(); update(); } void ImageViewBase::updateTransformAndFixFocalPoint( ImagePresentation const& presentation, FocalPointMode const mode) { TransformChangeWatcher const watcher(*this); TempFocalPointAdjuster const temp_fp(*this); m_imageToVirtual = presentation.transform(); m_virtualToImage = m_imageToVirtual.inverted(); m_virtualImageCropArea = presentation.cropArea(); m_virtualDisplayArea = presentation.displayArea(); updateWidgetTransformAndFixFocalPoint(mode); update(); } void ImageViewBase::updateTransformPreservingScale(ImagePresentation const& presentation) { TransformChangeWatcher const watcher(*this); TempFocalPointAdjuster const temp_fp(*this); // An arbitrary line in image coordinates. QLineF const image_line(0.0, 0.0, 1.0, 1.0); QLineF const 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(); updateWidgetTransform(); QLineF const widget_line_after( (m_imageToVirtual * m_virtualToWidget).map(image_line) ); m_zoom *= widget_line_before.length() / widget_line_after.length(); updateWidgetTransform(); update(); } void ImageViewBase::ensureStatusTip(QString const& status_tip) { QString const 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(); // On X11 (except with OpenGL), SmoothPixmapTransform is too slow, so don't enable it. bool smooth_pixmap_ok = true; #if defined(Q_WS_X11) smooth_pixmap_ok = viewport()->inherits("QGLWidget"); #endif if (smooth_pixmap_ok) { double const xscale = m_virtualToWidget.m11(); // Width of a source pixel in mm, as it's displayed on screen. double const pixel_width = widthMM() * xscale / width(); // Disable antialiasing for large zoom levels. painter.setRenderHint(QPainter::SmoothPixmapTransform, pixel_width < 0.5); } if (validateHqPixmap()) { // HQ pixmap maps one to one to screen pixels, so antialiasing is not necessary. painter.setRenderHint(QPainter::SmoothPixmapTransform, false); painter.drawPixmap(m_hqPixmapPos, m_hqPixmap); } else { scheduleHqVersionRebuild(); painter.setWorldTransform( m_pixmapToImage * m_imageToVirtual * m_virtualToWidget ); PixmapRenderer::drawPixmap(painter, m_pixmap); } painter.setRenderHints(QPainter::Antialiasing, true); painter.setWorldMatrixEnabled(false); // Cover parts of the image that should not be visible with background. // Note that because of Qt::WA_OpaquePaintEvent attribute, we need // to paint the whole widget, which we do here. QPolygonF const image_area( PolygonUtils::round( m_virtualToWidget.map( m_imageToVirtual.map(QRectF(m_image.rect())) ) ) ); QPolygonF const crop_area( PolygonUtils::round(m_virtualToWidget.map(m_virtualImageCropArea)) ); QPolygonF const intersected_area( PolygonUtils::round(image_area.intersected(crop_area)) ); QPainterPath intersected_path; intersected_path.addPolygon(intersected_area); QPainterPath containing_path; containing_path.addRect(viewport()->rect()); QBrush const brush(palette().color(QPalette::Window)); QPen pen(brush, 1.0); pen.setCosmetic(true); // By using a pen with the same color as the brush, we essentially // expanding the area we are going to draw. It's necessary because // XRender doesn't provide subpixel accuracy. painter.setPen(pen); painter.setBrush(brush); painter.drawPath(containing_path.subtracted(intersected_path)); painter.restore(); 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(); } void ImageViewBase::keyPressEvent(QKeyEvent* event) { event->setAccepted(false); m_rootInteractionHandler.keyPressEvent(event, m_interactionState); event->setAccepted(true); updateStatusTipAndCursor(); maybeQueueRedraw(); } void ImageViewBase::keyReleaseEvent(QKeyEvent* event) { event->setAccepted(false); m_rootInteractionHandler.keyReleaseEvent(event, m_interactionState); event->setAccepted(true); 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(); void 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(); } 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(); } void ImageViewBase::wheelEvent(QWheelEvent* event) { 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(); } void ImageViewBase::resizeEvent(QResizeEvent* event) { QAbstractScrollArea::resizeEvent(event); if (m_ignoreResizeEvents) { return; } ScopedIncDec const guard(m_ignoreScrollEvents); if (maximumViewportSize() != m_lastMaximumViewportSize) { m_lastMaximumViewportSize = maximumViewportSize(); m_widgetFocalPoint = centeredWidgetFocalPoint(); updateWidgetTransform(); } else { TransformChangeWatcher const watcher(*this); TempFocalPointAdjuster const temp_fp(*this, QPointF(0, 0)); updateTransformPreservingScale( ImagePresentation(m_imageToVirtual, m_virtualImageCropArea, m_virtualDisplayArea) ); } } void ImageViewBase::enterEvent(QEvent* event) { viewport()->setFocus(); QAbstractScrollArea::enterEvent(event); } /** * Called when any of the transformations change. */ void ImageViewBase::transformChanged() { updateScrollBars(); } void ImageViewBase::updateScrollBars() { if (verticalScrollBar()->isSliderDown() || horizontalScrollBar()->isSliderDown()) { return; } ScopedIncDec const guard1(m_ignoreScrollEvents); ScopedIncDec const guard2(m_ignoreResizeEvents); QRectF const picture(m_virtualToWidget.mapRect(virtualDisplayRect())); QPointF const viewport_center(maxViewportRect().center()); QPointF const 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) { double const 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()); } double const 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()); } int const xrange = (int)ceil(xmax - xmin); int const yrange = (int)ceil(ymax - ymin); int const xfirst = 0; int const xlast = xrange - 1; int const yfirst = 0; int const 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 double const xa = (xfirst == xlast) ? 1 : (xmax - xmin) / (xfirst - xlast); double const xb = xmax - xa * xfirst; double const ya = (yfirst == ylast) ? 1 : (ymax - ymin) / (yfirst - ylast); double const yb = ymax - ya * yfirst; // Inverse transformation. // xlast = ixa * xmin + ixb // xfirst = ixa * xmax + ixb double const ixa = (xmax == xmin) ? 1 : (xfirst - xlast) / (xmax - xmin); double const ixb = xfirst - ixa * xmax; double const iya = (ymax == ymin) ? 1 : (yfirst - ylast) / (ymax - ymin); double const iyb = yfirst - iya * ymax; m_scrollTransform.setMatrix(xa, 0, 0, 0, ya, 0, xb, yb, 1); int const xcur = qRound(ixa * xval + ixb); int const 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()); QRectF const old_viewport(viewport); viewport = dynamicViewportRect(); if (viewport == old_viewport) { break; } } } void ImageViewBase::reactToScrollBars() { if (m_ignoreScrollEvents) { return; } TransformChangeWatcher const watcher(*this); QPointF const raw_position( horizontalScrollBar()->value(), verticalScrollBar()->value() ); QPointF const new_fp(m_scrollTransform.map(raw_position)); QPointF const old_fp(getWidgetFocalPoint()); m_pixmapFocalPoint = m_virtualToImage.map(m_virtualDisplayArea.center()); m_widgetFocalPoint = new_fp; updateWidgetTransform(); setWidgetFocalPointWithoutMoving(old_fp); } /** * Updates m_virtualToWidget and m_widgetToVirtual.\n * To be called whenever any of the following is modified: * m_imageToVirt, m_widgetFocalPoint, m_pixmapFocalPoint, m_zoom. * Modifying both m_widgetFocalPoint and m_pixmapFocalPoint in a way * that doesn't cause image movement doesn't require calling this method. */ void ImageViewBase::updateWidgetTransform() { TransformChangeWatcher const watcher(*this); QRectF const virt_rect(virtualDisplayRect()); QPointF const virt_origin(m_imageToVirtual.map(m_pixmapFocalPoint)); QPointF const widget_origin(m_widgetFocalPoint); QSizeF zoom1_widget_size(virt_rect.size()); zoom1_widget_size.scale(maxViewportRect().size(), Qt::KeepAspectRatio); double const zoom1_x = zoom1_widget_size.width() / virt_rect.width(); double const 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()); m_virtualToWidget = xform; m_widgetToVirtual = m_virtualToWidget.inverted(); } /** * Updates m_virtualToWidget and m_widgetToVirtual and adjusts * the focal point if necessary.\n * To be called whenever m_imageToVirt is modified in such a way that * may invalidate the focal point. */ void ImageViewBase::updateWidgetTransformAndFixFocalPoint(FocalPointMode const mode) { TransformChangeWatcher const watcher(*this); // This must go before getIdealWidgetFocalPoint(), as it // recalculates m_virtualToWidget, that is used by // getIdealWidgetFocalPoint(). updateWidgetTransform(); QPointF const ideal_widget_fp(getIdealWidgetFocalPoint(mode)); if (ideal_widget_fp != m_widgetFocalPoint) { m_widgetFocalPoint = ideal_widget_fp; updateWidgetTransform(); } } /** * Returns a proposed value for m_widgetFocalPoint to minimize the * unused widget space. Unused widget space indicates one or both * of the following: * \li The image is smaller than the display area. * \li Parts of the image are outside of the display area. * * \param mode If set to CENTER_IF_FITS, then the returned focal point * will center the image if it completely fits into the widget. * This works in horizontal and vertical directions independently.\n * If \p mode is set to DONT_CENTER and the image completely fits * the widget, then the returned focal point will cause a minimal * move to force the whole image to be visible. * * In case there is no unused widget space, the returned focal point * is equal to the current focal point (m_widgetFocalPoint). This works * in horizontal and vertical dimensions independently. */ QPointF ImageViewBase::getIdealWidgetFocalPoint(FocalPointMode const mode) const { // Widget rect reduced by margins. QRectF const display_area(maxViewportRect()); // The virtual image rectangle in widget coordinates. QRectF const image_area(m_virtualToWidget.mapRect(virtualDisplayRect())); // Unused display space from each side. double const left_margin = image_area.left() - display_area.left(); double const right_margin = display_area.right() - image_area.right(); double const top_margin = image_area.top() - display_area.top(); double const 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. double const 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. double const movement = std::min(fabs(left_margin), 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. double const movement = std::min(fabs(left_margin), 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. double const 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. double const movement = std::min(fabs(top_margin), 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. double const movement = std::min(fabs(top_margin), fabs(bottom_margin)); widget_focal_point.ry() -= movement; } return widget_focal_point; } void ImageViewBase::setNewWidgetFP(QPointF const widget_fp, bool const update) { if (widget_fp != m_widgetFocalPoint) { m_widgetFocalPoint = widget_fp; updateWidgetTransform(); if (update) { this->update(); } } } /** * Used when dragging the image. It adjusts the movement to disallow * dragging it away from the ideal position (determined by * getIdealWidgetFocalPoint()). Movement towards the ideal position * is permitted. This works independently in horizontal and vertical * direction. * * \param proposed_widget_fp The proposed value for m_widgetFocalPoint. * \param update Whether to call this->update() in case the focal point * has changed. */ void ImageViewBase::adjustAndSetNewWidgetFP( QPointF const proposed_widget_fp, bool const 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. QPointF const old_widget_fp(m_widgetFocalPoint); setNewWidgetFP(proposed_widget_fp, update); QPointF const ideal_widget_fp(getIdealWidgetFocalPoint(CENTER_IF_FITS)); QPointF const towards_ideal(ideal_widget_fp - old_widget_fp); QPointF const 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 (fabs(towards_proposed.x()) > 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 (fabs(towards_proposed.y()) > fabs(towards_ideal.y())) { // Too much movement - limit it. movement.setY(towards_ideal.y()); } QPointF const adjusted_widget_fp(old_widget_fp + movement); if (adjusted_widget_fp != m_widgetFocalPoint) { m_widgetFocalPoint = adjusted_widget_fp; updateWidgetTransform(); if (update) { this->update(); } } } /** * Returns the center point of the available display area. */ QPointF ImageViewBase::centeredWidgetFocalPoint() const { return maxViewportRect().center(); } void ImageViewBase::setWidgetFocalPointWithoutMoving(QPointF const new_widget_fp) { 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_hqPixmap.isNull()) { return false; } if (m_hqSourceId != m_image.cacheKey()) { return false; } if (m_hqXform != m_imageToVirtual * m_virtualToWidget) { return false; } return true; } void ImageViewBase::scheduleHqVersionRebuild() { QTransform const xform(m_imageToVirtual * m_virtualToWidget); if (!m_timer.isActive() || m_potentialHqXform != xform) { if (m_ptrHqTransformTask.get()) { m_ptrHqTransformTask->cancel(); m_ptrHqTransformTask.reset(); } m_potentialHqXform = xform; } m_timer.start(); } void ImageViewBase::initiateBuildingHqVersion() { if (validateHqPixmap()) { return; } m_hqPixmap = QPixmap(); if (m_ptrHqTransformTask.get()) { m_ptrHqTransformTask->cancel(); m_ptrHqTransformTask.reset(); } QTransform const xform(m_imageToVirtual * m_virtualToWidget); IntrusivePtr const task( new HqTransformTask(this, m_image, xform, viewport()->size()) ); backgroundExecutor().enqueueTask(task); m_ptrHqTransformTask = task; m_hqXform = xform; m_hqSourceId = m_image.cacheKey(); } /** * Gets called from HqTransformationTask::Result. */ void ImageViewBase::hqVersionBuilt( QPoint const& origin, QImage const& image) { if (!m_hqTransformEnabled) { return; } m_hqPixmap = QPixmap::fromImage(image); m_hqPixmapPos = origin; m_ptrHqTransformTask.reset(); update(); } void ImageViewBase::updateStatusTipAndCursor() { updateStatusTip(); updateCursor(); } void ImageViewBase::updateStatusTip() { ensureStatusTip(m_interactionState.statusTip()); } void ImageViewBase::updateCursor() { viewport()->setCursor(m_interactionState.cursor()); } void ImageViewBase::maybeQueueRedraw() { if (m_interactionState.redrawRequested()) { m_interactionState.setRedrawRequested(false); update(); } } BackgroundExecutor& ImageViewBase::backgroundExecutor() { static BackgroundExecutor executor; return executor; } /*==================== ImageViewBase::HqTransformTask ======================*/ ImageViewBase::HqTransformTask::HqTransformTask( ImageViewBase* image_view, QImage const& image, QTransform const& xform, QSize const& target_size) : m_ptrResult(new Result(image_view)), m_image(image), m_xform(xform), m_targetSize(target_size) { } IntrusivePtr > ImageViewBase::HqTransformTask::operator()() { if (isCancelled()) { return IntrusivePtr >(); } QRect const 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) ) ); #if defined(Q_WS_X11) // ARGB32_Premultiplied is an optimal format for X11 + XRender. hq_image = hq_image.convertToFormat(QImage::Format_ARGB32_Premultiplied); #endif m_ptrResult->setData(target_rect.topLeft(), hq_image); return m_ptrResult; } /*================ ImageViewBase::HqTransformTask::Result ================*/ ImageViewBase::HqTransformTask::Result::Result( ImageViewBase* image_view) : m_ptrImageView(image_view) { } void ImageViewBase::HqTransformTask::Result::setData( QPoint const& origin, QImage const& hq_image) { m_hqImage = hq_image; m_origin = origin; } void ImageViewBase::HqTransformTask::Result::operator()() { if (m_ptrImageView && !isCancelled()) { m_ptrImageView->hqVersionBuilt(m_origin, m_hqImage); } } /*================= ImageViewBase::TempFocalPointAdjuster =================*/ ImageViewBase::TempFocalPointAdjuster::TempFocalPointAdjuster(ImageViewBase& obj) : m_rObj(obj), m_origWidgetFP(obj.getWidgetFocalPoint()) { obj.setWidgetFocalPointWithoutMoving(obj.centeredWidgetFocalPoint()); } ImageViewBase::TempFocalPointAdjuster::TempFocalPointAdjuster( ImageViewBase& obj, QPointF const temp_widget_fp) : m_rObj(obj), m_origWidgetFP(obj.getWidgetFocalPoint()) { obj.setWidgetFocalPointWithoutMoving(temp_widget_fp); } ImageViewBase::TempFocalPointAdjuster::~TempFocalPointAdjuster() { m_rObj.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; } 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(); } } } scantailor-RELEASE_0_9_12_2/ImageViewBase.h000066400000000000000000000306731271170121200203110ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEVIEWBASE_H_ #define IMAGEVIEWBASE_H_ #include "Margins.h" #include "IntrusivePtr.h" #include "InteractionHandler.h" #include "InteractionState.h" #include "ImagePixmapUnion.h" #include #include #include #include #include #include #include #include #include #include #include #include class QPainter; class BackgroundExecutor; class ImagePresentation; /** * \brief The base class for widgets that display and manipulate images. * * This class operates with 3 coordinate systems: * \li Image coordinates, where m_image.rect() is defined. * \li Pixmap coordinates, where m_pixmap.rect() is defined. * m_pixmap is constructed from a downscaled version of m_image. * \li Virtual image coordinates. We need them because we are not * displaying m_image as is. Instead, we display a pre-transformed * version of it. So, the virtual image coordinates reference the * pixels of an imaginary image that we would get if we actually * pre-transformed m_image the way we want. * \li Widget coordinates, where this->rect() is defined. * * \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( QImage const& image, ImagePixmapUnion const& downscaled_version, ImagePresentation const& presentation, Margins const& margins = Margins()); virtual ~ImageViewBase(); /** * The idea behind this accessor is being able to share a single * downscaled pixmap between multiple image views. */ QPixmap const& 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(QImage const& image); InteractionHandler& rootInteractionHandler() { return m_rootInteractionHandler; } InteractionState& interactionState() { return m_interactionState; } InteractionState const& interactionState() const { return m_interactionState; } QTransform const& imageToVirtual() const { return m_imageToVirtual; } QTransform const& virtualToImage() const { return m_virtualToImage; } QTransform const& virtualToWidget() const { return m_virtualToWidget; } QTransform const& 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(); } QRectF const& 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(QString const& 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(QPointF const& 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(QPointF const& 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(ImagePresentation const& presentation); /** * \brief Same as updateTransform(), but adjusts the focal point * to improve screen space usage. */ void updateTransformAndFixFocalPoint( ImagePresentation const& presentation, FocalPointMode mode); /** * \brief Same as updateTransform(), but preserves the visual image scale. */ void updateTransformPreservingScale(ImagePresentation const& 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 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); static BackgroundExecutor& backgroundExecutor(); protected: virtual void paintEvent(QPaintEvent* event); virtual void keyPressEvent(QKeyEvent* event); virtual void keyReleaseEvent(QKeyEvent* event); virtual void mousePressEvent(QMouseEvent* event); virtual void mouseReleaseEvent(QMouseEvent* event); virtual void mouseMoveEvent(QMouseEvent* event); virtual void wheelEvent(QWheelEvent* event); virtual void contextMenuEvent(QContextMenuEvent* event); virtual void resizeEvent(QResizeEvent* event); virtual void enterEvent(QEvent* event); /** * Returns the maximum viewport size (as if scrollbars are hidden) * reduced by margins. */ QRectF maxViewportRect() const; private slots: void initiateBuildingHqVersion(); void updateScrollBars(); void reactToScrollBars(); private: class HqTransformTask; class TempFocalPointAdjuster; class TransformChangeWatcher; QRectF dynamicViewportRect() const; void transformChanged(); void updateWidgetTransform(); void updateWidgetTransformAndFixFocalPoint(FocalPointMode mode); QPointF getIdealWidgetFocalPoint(FocalPointMode mode) const; void setNewWidgetFP(QPointF widget_fp, bool update = false); void adjustAndSetNewWidgetFP(QPointF proposed_widget_fp, bool update = false); QPointF centeredWidgetFocalPoint() const; bool validateHqPixmap() const; void scheduleHqVersionRebuild(); void hqVersionBuilt(QPoint const& origin, QImage const& image); void updateStatusTipAndCursor(); void updateStatusTip(); void updateCursor(); void maybeQueueRedraw(); InteractionHandler m_rootInteractionHandler; InteractionState m_interactionState; /** * 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; /** * 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 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; /** * 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 pending (if any) high quality transformation task. */ IntrusivePtr m_ptrHqTransformTask; /** * 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 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; int m_transformChangeWatchersActive; int m_ignoreScrollEvents; int m_ignoreResizeEvents; bool m_hqTransformEnabled; }; #endif scantailor-RELEASE_0_9_12_2/IncompleteThumbnail.cpp000066400000000000000000000105761271170121200221370ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "IncompleteThumbnail.h" #include #include #include #include #include #include #include #include #include QPainterPath IncompleteThumbnail::m_sCachedPath; IncompleteThumbnail::IncompleteThumbnail( IntrusivePtr const& thumbnail_cache, QSizeF const& max_size, ImageId const& image_id, ImageTransformation const& image_xform) : ThumbnailBase(thumbnail_cache, max_size, image_id, image_xform) { } IncompleteThumbnail::~IncompleteThumbnail() { } void IncompleteThumbnail::drawQuestionMark(QPainter& painter, QRectF const& bounding_rect) { QString const text(QString::fromAscii("?")); // 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)); #endif } QRectF const text_rect(m_sCachedPath.boundingRect()); QTransform xform1; xform1.translate(-text_rect.left(), -text_rect.top()); QSizeF const unscaled_size(text_rect.size()); QSizeF scaled_size(unscaled_size); scaled_size.scale(bounding_rect.size() * 0.9, Qt::KeepAspectRatio); double const hscale = scaled_size.width() / unscaled_size.width(); double const vscale = scaled_size.height() / unscaled_size.height(); QTransform xform2; xform2.scale(hscale, vscale); // Position the text at the center of our bounding rect. QSizeF const 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); QPen pen(QColor(0x00, 0x00, 0x00, 60)); pen.setWidth(2); pen.setCosmetic(true); painter.setPen(pen); painter.drawPath(m_sCachedPath); } void IncompleteThumbnail::paintOverImage( QPainter& painter, QTransform const& image_to_display, QTransform const& thumb_to_display) { drawQuestionMark(painter, boundingRect()); } scantailor-RELEASE_0_9_12_2/IncompleteThumbnail.h000066400000000000000000000042551271170121200216010ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef INCOMPLETETHUMBNAIL_H_ #define INCOMPLETETHUMBNAIL_H_ #include "ThumbnailBase.h" #include "IntrusivePtr.h" #include class ThumbnailPixmapCache; class QSizeF; class QRectF; /** * \brief A thumbnail represeting a page not completely processed. * * Suppose you have switched to the Deskew filter without running all images * through the previous filters. The thumbnail view of the Deskew filter * is supposed to show already split and deskewed pages. However, because * the previous filters were not applied to all pages, we can't show them * split and deskewed. In that case, we show an image the best we can * (maybe split but not deskewed, maybe unsplit). Also we draw a translucent * question mark over that image to indicate it's not shown the way it should. * This class implements drawing of such thumbnails with question marks. */ class IncompleteThumbnail : public ThumbnailBase { public: IncompleteThumbnail( IntrusivePtr const& thumbnail_cache, QSizeF const& max_size, ImageId const& image_id, ImageTransformation const& image_xform); virtual ~IncompleteThumbnail(); static void drawQuestionMark(QPainter& painter, QRectF const& bounding_rect); protected: virtual void paintOverImage( QPainter& painter, QTransform const& image_to_display, QTransform const& thumb_to_display); private: static QPainterPath m_sCachedPath; }; #endif scantailor-RELEASE_0_9_12_2/JpegMetadataLoader.cpp000066400000000000000000000144641271170121200216510ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "JpegMetadataLoader.h" #include "ImageMetadata.h" #include "NonCopyable.h" #include "Dpi.h" #include "Dpm.h" #include #include #include #include #include #include extern "C" { #include } namespace { /*======================== JpegDecompressionHandle =======================*/ class JpegDecompressHandle { DECLARE_NON_COPYABLE(JpegDecompressHandle) public: JpegDecompressHandle(jpeg_error_mgr* err_mgr, jpeg_source_mgr* src_mgr); ~JpegDecompressHandle(); jpeg_decompress_struct* ptr() { return &m_info; } jpeg_decompress_struct* operator->() { return &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; } JpegDecompressHandle::~JpegDecompressHandle() { jpeg_destroy_decompress(&m_info); } /*============================ JpegSourceManager =========================*/ class JpegSourceManager : public jpeg_source_mgr { DECLARE_NON_COPYABLE(JpegSourceManager) public: JpegSourceManager(QIODevice& io_device); private: static void initSource(j_decompress_ptr cinfo); static boolean fillInputBuffer(j_decompress_ptr cinfo); boolean fillInputBufferImpl(); static void skipInputData(j_decompress_ptr cinfo, long num_bytes); void skipInputDataImpl(long num_bytes); static void termSource(j_decompress_ptr cinfo); static JpegSourceManager* object(j_decompress_ptr cinfo); QIODevice& m_rDevice; JOCTET m_buf[4096]; }; JpegSourceManager::JpegSourceManager(QIODevice& io_device) : 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; } void JpegSourceManager::initSource(j_decompress_ptr cinfo) { // No-op. } boolean JpegSourceManager::fillInputBuffer(j_decompress_ptr cinfo) { return object(cinfo)->fillInputBufferImpl(); } boolean JpegSourceManager::fillInputBufferImpl() { qint64 const 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; } void JpegSourceManager::skipInputData(j_decompress_ptr cinfo, long 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; } void JpegSourceManager::termSource(j_decompress_ptr cinfo) { // No-op. } JpegSourceManager* JpegSourceManager::object(j_decompress_ptr cinfo) { return static_cast(cinfo->src); } /*============================= JpegErrorManager ===========================*/ class JpegErrorManager : public jpeg_error_mgr { DECLARE_NON_COPYABLE(JpegErrorManager) public: JpegErrorManager(); jmp_buf& jmpBuf() { return m_jmpBuf; } private: static void errorExit(j_common_ptr cinfo); static JpegErrorManager* object(j_common_ptr cinfo); jmp_buf m_jmpBuf; }; JpegErrorManager::JpegErrorManager() { jpeg_std_error(this); error_exit = &JpegErrorManager::errorExit; } void JpegErrorManager::errorExit(j_common_ptr cinfo) { longjmp(object(cinfo)->jmpBuf(), 1); } JpegErrorManager* JpegErrorManager::object(j_common_ptr cinfo) { return static_cast(cinfo->err); } } // anonymous namespace /*============================= JpegMetadataLoader ==========================*/ void JpegMetadataLoader::registerMyself() { static bool registered = false; if (!registered) { ImageMetadataLoader::registerLoader( IntrusivePtr(new JpegMetadataLoader) ); registered = true; } } ImageMetadataLoader::Status JpegMetadataLoader::loadMetadata( QIODevice& io_device, VirtualFunction1& out) { if (!io_device.isReadable()) { return GENERIC_ERROR; } static unsigned char const jpeg_signature[] = { 0xff, 0xd8, 0xff }; static int const 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); int const 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; } QSize const 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; } scantailor-RELEASE_0_9_12_2/JpegMetadataLoader.h000066400000000000000000000025171271170121200213120ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef JPEGMETADATALOADER_H_ #define JPEGMETADATALOADER_H_ #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: virtual Status loadMetadata( QIODevice& io_device, VirtualFunction1& out); }; #endif scantailor-RELEASE_0_9_12_2/LoadFileTask.cpp000066400000000000000000000114601271170121200204670ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "LoadFileTask.h" #include "filters/fix_orientation/Task.h" #include "TaskStatus.h" #include "FilterResult.h" #include "ErrorWidget.h" #include "FilterUiInterface.h" #include "AbstractFilter.h" #include "FilterOptionsWidget.h" #include "ThumbnailPixmapCache.h" #include "ProjectPages.h" #include "PageInfo.h" #include "Dpi.h" #include "Dpm.h" #include "FilterData.h" #include "ImageLoader.h" #include #include #include #include #include #include // for Qt::escape() #include using namespace imageproc; class LoadFileTask::ErrorResult : public FilterResult { Q_DECLARE_TR_FUNCTIONS(LoadFileTask) public: ErrorResult(QString const& file_path); virtual void updateUI(FilterUiInterface* ui); virtual IntrusivePtr filter() { return IntrusivePtr(); } private: QString m_filePath; bool m_fileExists; }; LoadFileTask::LoadFileTask( Type type, PageInfo const& page, IntrusivePtr const& thumbnail_cache, IntrusivePtr const& pages, IntrusivePtr const& next_task) : BackgroundTask(type), m_ptrThumbnailCache(thumbnail_cache), m_imageId(page.imageId()), m_imageMetadata(page.metadata()), m_ptrPages(pages), m_ptrNextTask(next_task) { assert(m_ptrNextTask); } LoadFileTask::~LoadFileTask() { } FilterResultPtr LoadFileTask::operator()() { QImage image(ImageLoader::load(m_imageId)); try { throwIfCancelled(); if (image.isNull()) { return FilterResultPtr(new ErrorResult(m_imageId.filePath())); } else { updateImageSizeIfChanged(image); overrideDpi(image); m_ptrThumbnailCache->ensureThumbnailExists(m_imageId, image); return m_ptrNextTask->process(*this, FilterData(image)); } } catch (CancelledException const&) { return FilterResultPtr(); } } void LoadFileTask::updateImageSizeIfChanged(QImage const& 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); } } void LoadFileTask::overrideDpi(QImage& image) const { // Beware: QImage will have a default DPI when loading // an image that doesn't specify one. Dpm const dpm(m_imageMetadata.dpi()); image.setDotsPerMeterX(dpm.horizontal()); image.setDotsPerMeterY(dpm.vertical()); } /*======================= LoadFileTask::ErrorResult ======================*/ LoadFileTask::ErrorResult::ErrorResult(QString const& 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(IntrusivePtr > const& relinking_dialog_requester, QString const& text, Qt::TextFormat fmt = Qt::AutoText) : ErrorWidget(text, fmt), m_ptrRelinkingDialogRequester(relinking_dialog_requester) {} private: virtual void linkActivated(QString const&) { (*m_ptrRelinkingDialogRequester)(); } IntrusivePtr > 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(Qt::escape(m_filePath)); fmt = Qt::RichText; } ui->setImageWidget(new ErrWidget(ui->relinkingDialogRequester(), err_msg, fmt), ui->TRANSFER_OWNERSHIP); ui->setOptionsWidget(new FilterOptionsWidget, ui->TRANSFER_OWNERSHIP); } scantailor-RELEASE_0_9_12_2/LoadFileTask.h000066400000000000000000000034631271170121200201400ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef LOADFILETASK_H_ #define LOADFILETASK_H_ #include "NonCopyable.h" #include "BackgroundTask.h" #include "FilterResult.h" #include "IntrusivePtr.h" #include "ImageId.h" #include "ImageMetadata.h" class ThumbnailPixmapCache; class PageInfo; class ProjectPages; class QImage; namespace fix_orientation { class Task; } class LoadFileTask : public BackgroundTask { DECLARE_NON_COPYABLE(LoadFileTask) public: LoadFileTask(Type type, PageInfo const& page, IntrusivePtr const& thumbnail_cache, IntrusivePtr const& pages, IntrusivePtr const& next_task); virtual ~LoadFileTask(); virtual FilterResultPtr operator()(); private: class ErrorResult; void updateImageSizeIfChanged(QImage const& image); void overrideDpi(QImage& image) const; IntrusivePtr m_ptrThumbnailCache; ImageId m_imageId; ImageMetadata m_imageMetadata; IntrusivePtr const m_ptrPages; IntrusivePtr const m_ptrNextTask; }; #endif scantailor-RELEASE_0_9_12_2/LoadFilesStatusDialog.cpp000066400000000000000000000037151271170121200223570ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "LoadFilesStatusDialog.h" #include #ifndef Q_MOC_RUN #include #endif LoadFilesStatusDialog::LoadFilesStatusDialog(QWidget* parent) : QDialog(parent) { ui.setupUi(this); ui.tabWidget->setCurrentWidget(ui.failedTab); m_loadedTabNameTemplate = ui.tabWidget->tabText(0); m_failedTabNameTemplate = ui.tabWidget->tabText(1); setLoadedFiles(std::vector()); setFailedFiles(std::vector()); } void LoadFilesStatusDialog::setLoadedFiles(std::vector const& files) { ui.tabWidget->setTabText(0, m_loadedTabNameTemplate.arg(files.size())); QString text; BOOST_FOREACH(QString const& file, files) { text.append(file); text.append(QChar('\n')); } ui.loadedFiles->setPlainText(text); } void LoadFilesStatusDialog::setFailedFiles(std::vector const& files) { ui.tabWidget->setTabText(1, m_failedTabNameTemplate.arg(files.size())); QString text; BOOST_FOREACH(QString const& file, files) { text.append(file); text.append(QChar('\n')); } ui.failedFiles->setPlainText(text); } void LoadFilesStatusDialog::setOkButtonName(QString const& name) { ui.buttonBox->button(QDialogButtonBox::Ok)->setText(name); } scantailor-RELEASE_0_9_12_2/LoadFilesStatusDialog.h000066400000000000000000000024621271170121200220220ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef LOAD_FILES_STATUS_DIALOG_H_ #define LOAD_FILES_STATUS_DIALOG_H_ #include "ui_LoadFilesStatusDialog.h" #include #include class LoadFilesStatusDialog : public QDialog { public: LoadFilesStatusDialog(QWidget* parent = 0); void setLoadedFiles(std::vector const& files); void setFailedFiles(std::vector const& failed); void setOkButtonName(QString const& name); private: Ui::LoadFilesStatusDialog ui; QString m_loadedTabNameTemplate; QString m_failedTabNameTemplate; }; #endif scantailor-RELEASE_0_9_12_2/MainWindow.cpp000066400000000000000000001563271271170121200202550ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "MainWindow.h" #include "MainWindow.h.moc" #include "NewOpenProjectPanel.h" #include "RecentProjects.h" #include "WorkerThread.h" #include "ProjectPages.h" #include "PageSequence.h" #include "PageSelectionAccessor.h" #include "PageSelectionProvider.h" #include "StageSequence.h" #include "ThumbnailSequence.h" #include "PageOrderOption.h" #include "PageOrderProvider.h" #include "ProcessingTaskQueue.h" #include "FileNameDisambiguator.h" #include "OutputFileNameGenerator.h" #include "ImageInfo.h" #include "PageInfo.h" #include "ImageId.h" #include "Utils.h" #include "FilterOptionsWidget.h" #include "ErrorWidget.h" #include "AutoRemovingFile.h" #include "DebugImages.h" #include "DebugImageView.h" #include "TabbedDebugImages.h" #include "BasicImageView.h" #include "ProjectWriter.h" #include "ProjectReader.h" #include "ThumbnailPixmapCache.h" #include "ThumbnailFactory.h" #include "ContentBoxPropagator.h" #include "PageOrientationPropagator.h" #include "ProjectCreationContext.h" #include "ProjectOpeningContext.h" #include "SkinnedButton.h" #include "SystemLoadWidget.h" #include "ProcessingIndicationWidget.h" #include "ImageMetadataLoader.h" #include "SmartFilenameOrdering.h" #include "OrthogonalRotation.h" #include "FixDpiDialog.h" #include "LoadFilesStatusDialog.h" #include "SettingsDialog.h" #include "AbstractRelinker.h" #include "RelinkingDialog.h" #include "OutOfMemoryHandler.h" #include "OutOfMemoryDialog.h" #include "QtSignalForwarder.h" #include "filters/fix_orientation/Filter.h" #include "filters/fix_orientation/Task.h" #include "filters/fix_orientation/CacheDrivenTask.h" #include "filters/page_split/Filter.h" #include "filters/page_split/Task.h" #include "filters/page_split/CacheDrivenTask.h" #include "filters/deskew/Filter.h" #include "filters/deskew/Task.h" #include "filters/deskew/CacheDrivenTask.h" #include "filters/select_content/Filter.h" #include "filters/select_content/Task.h" #include "filters/select_content/CacheDrivenTask.h" #include "filters/page_layout/Filter.h" #include "filters/page_layout/Task.h" #include "filters/page_layout/CacheDrivenTask.h" #include "filters/output/Filter.h" #include "filters/output/Task.h" #include "filters/output/CacheDrivenTask.h" #include "LoadFileTask.h" #include "CompositeCacheDrivenTask.h" #include "ScopedIncDec.h" #include "ui_AboutDialog.h" #include "ui_RemovePagesDialog.h" #include "ui_BatchProcessingLowerPanel.h" #include "config.h" #include "version.h" #ifndef Q_MOC_RUN #include #include #include #endif #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 #include #include #include #include #include #include #include #include #include #include #include class MainWindow::PageSelectionProviderImpl : public PageSelectionProvider { public: PageSelectionProviderImpl(MainWindow* wnd) : m_ptrWnd(wnd) {} virtual PageSequence allPages() const { return m_ptrWnd ? m_ptrWnd->allPages() : PageSequence(); } virtual std::set selectedPages() const { return m_ptrWnd ? m_ptrWnd->selectedPages() : std::set(); } std::vector selectedRanges() const { return m_ptrWnd ? m_ptrWnd->selectedRanges() : std::vector(); } private: QPointer m_ptrWnd; }; MainWindow::MainWindow() : m_ptrPages(new ProjectPages), m_ptrStages(new StageSequence(m_ptrPages, newPageSelectionAccessor())), m_ptrWorkerThread(new WorkerThread), m_ptrInteractiveQueue(new ProcessingTaskQueue(ProcessingTaskQueue::RANDOM_ORDER)), 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.reset(new ThumbnailSequence(m_maxLogicalThumbSize)); setupUi(this); sortOptions->setVisible(false); #if !defined(ENABLE_OPENGL) // Right now the only setting is 3D acceleration, so get rid of // the whole Settings dialog, if it's inaccessible. actionSettings->setVisible(false); #endif createBatchProcessingWidget(); m_ptrProcessingIndicationWidget.reset(new ProcessingIndicationWidget); filterList->setStages(m_ptrStages); filterList->selectRow(0); setupThumbView(); // Expects m_ptrThumbSequence to be initialized. m_ptrTabbedDebugImages.reset(new TabbedDebugImages); m_debug = actionDebug->isChecked(); m_pImageFrameLayout = new QStackedLayout(imageViewFrame); m_pOptionsFrameLayout = new QStackedLayout(filterOptions); addAction(actionFirstPage); addAction(actionLastPage); addAction(actionNextPage); addAction(actionPrevPage); addAction(actionPrevPageQ); addAction(actionNextPageW); // 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( filterList->selectionModel(), SIGNAL(selectionChanged(QItemSelection const&, QItemSelection const&)), this, SLOT(filterSelectionChanged(QItemSelection const&)) ); connect( filterList, SIGNAL(launchBatchProcessing()), this, SLOT(startBatchProcessing()) ); connect( m_ptrWorkerThread.get(), SIGNAL(taskResult(BackgroundTaskPtr const&, FilterResultPtr const&)), this, SLOT(filterResult(BackgroundTaskPtr const&, FilterResultPtr const&)) ); connect( m_ptrThumbSequence.get(), SIGNAL(newSelectionLeader(PageInfo const&, QRectF const&, ThumbnailSequence::SelectionFlags)), this, SLOT(currentPageChanged(PageInfo const&, QRectF const&, ThumbnailSequence::SelectionFlags)) ); connect( m_ptrThumbSequence.get(), SIGNAL(pageContextMenuRequested(PageInfo const&, QPoint const&, bool)), this, SLOT(pageContextMenuRequested(PageInfo const&, QPoint const&, bool)) ); connect( m_ptrThumbSequence.get(), SIGNAL(pastLastPageContextMenuRequested(QPoint const&)), SLOT(pastLastPageContextMenuRequested(QPoint const&)) ); 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(actionDebug, SIGNAL(toggled(bool)), SLOT(debugToggled(bool))); connect( actionSettings, SIGNAL(triggered(bool)), this, SLOT(openSettingsDialog()) ); 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(); QSettings settings; if (settings.value("mainWindow/maximized") == false) { QVariant const geom( settings.value("mainWindow/nonMaximizedGeometry") ); if (!restoreGeometry(geom.toByteArray())) { resize(1014, 689); // A sensible value. } } } MainWindow::~MainWindow() { m_ptrInteractiveQueue->cancelAndClear(); if (m_ptrBatchQueue.get()) { m_ptrBatchQueue->cancelAndClear(); } m_ptrWorkerThread->shutdown(); removeWidgetsFromLayout(m_pImageFrameLayout); removeWidgetsFromLayout(m_pOptionsFrameLayout); m_ptrTabbedDebugImages->clear(); } PageSequence MainWindow::allPages() const { return m_ptrThumbSequence->toPageSequence(); } std::set MainWindow::selectedPages() const { return m_ptrThumbSequence->selectedItems(); } std::vector MainWindow::selectedRanges() const { return m_ptrThumbSequence->selectedRanges(); } void MainWindow::switchToNewProject( IntrusivePtr const& pages, QString const& out_dir, QString const& project_file_path, ProjectReader const* project_reader) { stopBatchProcessing(CLEAR_MAIN_AREA); m_ptrInteractiveQueue->cancelAndClear(); Utils::maybeCreateCacheDir(out_dir); m_ptrPages = pages; m_projectFile = project_file_path; if (project_reader) { m_selectedPage = project_reader->selectedPage(); } IntrusivePtr 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.reset(new StageSequence(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(QItemSelection const&, QItemSelection const&)), this, SLOT(filterSelectionChanged(QItemSelection const&)) ); } updateSortOptions(); m_ptrContentBoxPropagator.reset( new ContentBoxPropagator( m_ptrStages->pageLayoutFilter(), createCompositeCacheDrivenTask( m_ptrStages->selectContentFilterIdx() ) ) ); m_ptrPageOrientationPropagator.reset( new PageOrientationPropagator( m_ptrStages->pageSplitFilter(), createCompositeCacheDrivenTask( m_ptrStages->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()); removeFilterOptionsWidget(); updateProjectActions(); updateWindowTitle(); updateMainArea(); if (!QDir(out_dir).exists()) { showRelinkingDialog(); } } void MainWindow::showNewOpenProjectPanel() { std::auto_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(QString const&)), this, SLOT(openProject(QString const&)), 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); } void MainWindow::createBatchProcessingWidget() { using namespace boost::lambda; 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); } Ui::BatchProcessingLowerPanel ui; }; LowerPanel* lower_panel = new LowerPanel(m_ptrBatchProcessingWidget.get()); m_checkBeepWhenFinished = bind(&QCheckBox::isChecked, lower_panel->ui.beepWhenFinished); 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())); } void MainWindow::setupThumbView() { int const 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; } int const delta_x = thumbView->size().width() - inner_width; thumbView->setMinimumWidth((int)ceil(m_maxLogicalThumbSize.width() + delta_x)); thumbView->setBackgroundBrush(palette().color(QPalette::Window)); m_ptrThumbSequence->attachView(thumbView); } void MainWindow::closeEvent(QCloseEvent* const event) { 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()); 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::StandardButton const res = QMessageBox::question( this, tr("Save Project"), tr("Save this project?"), QMessageBox::Save|QMessageBox::Discard|QMessageBox::Cancel, QMessageBox::Save ); switch (res) { case QMessageBox::Save: return SAVE; case QMessageBox::Discard: return DONT_SAVE; default: return CANCEL; } } bool MainWindow::compareFiles(QString const& fpath1, QString const& fpath2) { QFile file1(fpath1); QFile file2(fpath2); 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; } } int const chunk_size = 4096; for (;;) { QByteArray const chunk1(file1.read(chunk_size)); QByteArray const chunk2(file2.read(chunk_size)); if (chunk1.size() != chunk2.size()) { return false; } else if (chunk1.size() == 0) { return true; } } } IntrusivePtr MainWindow::currentPageOrderProvider() const { int const idx = sortOptions->currentIndex(); if (idx < 0) { return IntrusivePtr(); } IntrusivePtr const filter(m_ptrStages->filterAt(m_curFilter)); return filter->pageOrderOptions()[idx].provider(); } void MainWindow::updateSortOptions() { ScopedIncDec const guard(m_ignorePageOrderingChanges); IntrusivePtr const filter(m_ptrStages->filterAt(m_curFilter)); sortOptions->clear(); BOOST_FOREACH(PageOrderOption const& opt, filter->pageOrderOptions()) { sortOptions->addItem(opt.name()); } sortOptions->setVisible(sortOptions->count() > 0); if (sortOptions->count() > 0) { sortOptions->setCurrentIndex(filter->selectedPageOrder()); } } void MainWindow::resetThumbSequence( IntrusivePtr const& page_order_provider) { if (m_ptrThumbnailCache.get()) { IntrusivePtr const task( createCompositeCacheDrivenTask(m_curFilter) ); m_ptrThumbSequence->setThumbnailFactory( IntrusivePtr( new ThumbnailFactory( m_ptrThumbnailCache, m_maxLogicalThumbSize, task ) ) ); } m_ptrThumbSequence->reset( m_ptrPages->toPageSequence(getCurrentView()), ThumbnailSequence::RESET_SELECTION, page_order_provider ); if (!m_ptrThumbnailCache.get()) { // Empty project. assert(m_ptrPages->numImages() == 0); m_ptrThumbSequence->setThumbnailFactory( IntrusivePtr() ); } PageId const 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 } else { // Last resort. m_ptrThumbSequence->setSelection(m_ptrThumbSequence->firstPage().id()); } } void MainWindow::setOptionsWidget(FilterOptionsWidget* widget, Ownership const 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 (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(PageId const&)), this, SLOT(invalidateThumbnail(PageId const&)) ); disconnect( m_ptrOptionsWidget, SIGNAL(invalidateThumbnail(PageInfo const&)), this, SLOT(invalidateThumbnail(PageInfo const&)) ); disconnect( m_ptrOptionsWidget, SIGNAL(invalidateAllThumbnails()), this, SLOT(invalidateAllThumbnails()) ); disconnect( m_ptrOptionsWidget, SIGNAL(goToPage(PageId const&)), this, SLOT(goToPage(PageId const&)) ); } 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(PageId const&)), this, SLOT(invalidateThumbnail(PageId const&)) ); connect( widget, SIGNAL(invalidateThumbnail(PageInfo const&)), this, SLOT(invalidateThumbnail(PageInfo const&)) ); connect( widget, SIGNAL(invalidateAllThumbnails()), this, SLOT(invalidateAllThumbnails()) ); connect( widget, SIGNAL(goToPage(PageId const&)), this, SLOT(goToPage(PageId const&)) ); } void MainWindow::setImageWidget( QWidget* widget, Ownership const ownership, DebugImages* debug_images) { if (isBatchProcessingInProgress() && widget != m_ptrBatchProcessingWidget.get()) { if (ownership == TRANSFER_OWNERSHIP) { delete widget; } return; } removeImageWidget(); if (ownership == TRANSFER_OWNERSHIP) { m_imageWidgetCleanup.add(widget); } if (!debug_images || debug_images->empty()) { m_pImageFrameLayout->addWidget(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()); } } void MainWindow::removeImageWidget() { removeWidgetsFromLayout(m_pImageFrameLayout); m_ptrTabbedDebugImages->clear(); // Delete the old widget we were owning, if any. m_imageWidgetCleanup.clear(); } void MainWindow::invalidateThumbnail(PageId const& page_id) { m_ptrThumbSequence->invalidateThumbnail(page_id); } void MainWindow::invalidateThumbnail(PageInfo const& page_info) { m_ptrThumbSequence->invalidateThumbnail(page_info); } void MainWindow::invalidateAllThumbnails() { m_ptrThumbSequence->invalidateAllThumbnails(); } IntrusivePtr > MainWindow::relinkingDialogRequester() { class Requester : public AbstractCommand0 { public: Requester(MainWindow* wnd) : m_ptrWnd(wnd) {} virtual void operator()() { if (MainWindow* wnd = m_ptrWnd) { wnd->showRelinkingDialog(); } } private: QPointer m_ptrWnd; }; return IntrusivePtr >(new Requester(this)); } void MainWindow::showRelinkingDialog() { if (!isProjectLoaded()) { return; } 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)); new QtSignalForwarder( dialog, SIGNAL(accepted()), boost::lambda::bind(&MainWindow::performRelinking, this, dialog->relinker()) ); dialog->show(); } void MainWindow::performRelinking(IntrusivePtr const& relinker) { assert(relinker.get()); if (!isProjectLoaded()) { return; } m_ptrPages->performRelinking(*relinker); m_ptrStages->performRelinking(*relinker); m_outFileNameGen.performRelinking(*relinker); Utils::maybeCreateCacheDir(m_outFileNameGen.outDir()); m_ptrThumbnailCache->setThumbDir(Utils::outputDirToThumbDir(m_outFileNameGen.outDir())); resetThumbSequence(currentPageOrderProvider()); m_selectedPage.set(m_ptrThumbSequence->selectionLeader().id(), getCurrentView()); reloadRequested(); } void MainWindow::goFirstPage() { if (isBatchProcessingInProgress() || !isProjectLoaded()) { return; } PageInfo const first_page(m_ptrThumbSequence->firstPage()); if (!first_page.isNull()) { goToPage(first_page.id()); } } void MainWindow::goLastPage() { if (isBatchProcessingInProgress() || !isProjectLoaded()) { return; } PageInfo const last_page(m_ptrThumbSequence->lastPage()); if (!last_page.isNull()) { goToPage(last_page.id()); } } void MainWindow::goNextPage() { if (isBatchProcessingInProgress() || !isProjectLoaded()) { return; } PageInfo const next_page( m_ptrThumbSequence->nextPage(m_ptrThumbSequence->selectionLeader().id()) ); if (!next_page.isNull()) { goToPage(next_page.id()); } } void MainWindow::goPrevPage() { if (isBatchProcessingInProgress() || !isProjectLoaded()) { return; } PageInfo const prev_page( m_ptrThumbSequence->prevPage(m_ptrThumbSequence->selectionLeader().id()) ); if (!prev_page.isNull()) { goToPage(prev_page.id()); } } void MainWindow::goToPage(PageId const& page_id) { focusButton->setChecked(true); m_ptrThumbSequence->setSelection(page_id); // If the page was already selected, it will be reloaded. // That's by design. updateMainArea(); } void MainWindow::currentPageChanged( PageInfo const& page_info, QRectF const& thumb_rect, ThumbnailSequence::SelectionFlags const flags) { 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) { if (isBatchProcessingInProgress()) { stopBatchProcessing(); } else if (!(flags & ThumbnailSequence::REDUNDANT_SELECTION)) { // Start loading / processing the newly selected page. updateMainArea(); } } } void MainWindow::pageContextMenuRequested( PageInfo const& page_info_, QPoint const& screen_pos, bool selected) { if (isBatchProcessingInProgress()) { return; } // Make a copy to prevent it from being invalidated. PageInfo const page_info(page_info_); if (!selected) { goToPage(page_info.id()); } 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 ...") ); menu.addSeparator(); 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()); } } void MainWindow::pastLastPageContextMenuRequested(QPoint const& screen_pos) { if (!isProjectLoaded()) { return; } QMenu menu; menu.addAction(QIcon(":/icons/insert-here-16.png"), tr("Insert here ...")); if (menu.exec(screen_pos)) { showInsertFileDialog(BEFORE, ImageId()); } } void MainWindow::thumbViewFocusToggled(bool const checked) { QRectF const rect(m_ptrThumbSequence->selectionLeaderSceneRect()); if (rect.isNull()) { // No selected items. return; } if (checked) { thumbView->ensureVisible(rect, 0, 0); } } void MainWindow::thumbViewScrolled() { QRectF const rect(m_ptrThumbSequence->selectionLeaderSceneRect()); if (rect.isNull()) { // No items selected. return; } QRectF const viewport_rect(thumbView->viewport()->rect()); QRectF const viewport_item_rect( thumbView->viewportTransform().mapRect(rect) ); double const 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(QItemSelection const& selected) { if (m_ignoreSelectionChanges) { return; } if (selected.empty()) { return; } m_ptrInteractiveQueue->cancelAndClear(); if (m_ptrBatchQueue.get()) { // Should not happen, but just in case. m_ptrBatchQueue->cancelAndClear(); } bool const was_below_fix_orientation = isBelowFixOrientation(m_curFilter); bool const was_below_select_content = isBelowSelectContent(m_curFilter); m_curFilter = selected.front().top(); bool const now_below_fix_orientation = isBelowFixOrientation(m_curFilter); bool const 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.get()) { 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.get()) { m_ptrPageOrientationPropagator->propagate(*m_ptrPages); } // Otherwise probably no project is loaded. } focusButton->setChecked(true); // Should go before resetThumbSequence(). resetThumbSequence(currentPageOrderProvider()); updateMainArea(); } void MainWindow::pageOrderingChanged(int idx) { if (m_ignorePageOrderingChanges) { return; } focusButton->setChecked(true); // Keep the current page in view. m_ptrStages->filterAt(m_curFilter)->selectPageOrder(idx); m_ptrThumbSequence->reset( m_ptrPages->toPageSequence(getCurrentView()), ThumbnailSequence::KEEP_SELECTION, currentPageOrderProvider() ); } void MainWindow::reloadRequested() { // Start loading / processing the current page. updateMainArea(); } void MainWindow::startBatchProcessing() { if (isBatchProcessingInProgress() || !isProjectLoaded()) { return; } m_ptrInteractiveQueue->cancelAndClear(); m_ptrBatchQueue.reset( new ProcessingTaskQueue( currentPageOrderProvider().get() ? ProcessingTaskQueue::RANDOM_ORDER : ProcessingTaskQueue::SEQUENTIAL_ORDER ) ); PageInfo page(m_ptrThumbSequence->selectionLeader()); for (; !page.isNull(); page = m_ptrThumbSequence->nextPage(page.id())) { m_ptrBatchQueue->addProcessingTask( page, createCompositeTask(page, m_curFilter, /*batch=*/true, m_debug) ); } focusButton->setChecked(true); removeFilterOptionsWidget(); filterList->setBatchProcessingInProgress(true); filterList->setEnabled(false); BackgroundTaskPtr const task(m_ptrBatchQueue->takeForProcessing()); if (task) { m_ptrWorkerThread->performTask(task); } else { stopBatchProcessing(); } page = m_ptrBatchQueue->selectedPage(); if (!page.isNull()) { m_ptrThumbSequence->setSelection(page.id()); } // Display the batch processing screen. updateMainArea(); } void MainWindow::stopBatchProcessing(MainAreaAction main_area) { if (!isBatchProcessingInProgress()) { return; } PageInfo const page(m_ptrBatchQueue->selectedPage()); if (!page.isNull()) { m_ptrThumbSequence->setSelection(page.id()); } m_ptrBatchQueue->cancelAndClear(); m_ptrBatchQueue.reset(); filterList->setBatchProcessingInProgress(false); filterList->setEnabled(true); switch (main_area) { case UPDATE_MAIN_AREA: updateMainArea(); break; case CLEAR_MAIN_AREA: removeImageWidget(); break; } } void MainWindow::filterResult(BackgroundTaskPtr const& task, FilterResultPtr const& result) { // Cancelled or not, we must mark it as finished. m_ptrInteractiveQueue->processingFinished(task); if (m_ptrBatchQueue.get()) { 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. int const 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()) { QApplication::beep(); } if (m_selectedPage.get(getCurrentView()) == m_ptrThumbSequence->lastPage().id()) { // If batch processing finished at the last page, jump to the first one. goFirstPage(); } return; } BackgroundTaskPtr const task(m_ptrBatchQueue->takeForProcessing()); if (task) { m_ptrWorkerThread->performTask(task); } PageInfo const page(m_ptrBatchQueue->selectedPage()); if (!page.isNull()) { m_ptrThumbSequence->setSelection(page.id()); } } } void MainWindow::debugToggled(bool const enabled) { m_debug = enabled; } void MainWindow::fixDpiDialogRequested() { 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); connect(m_ptrFixDpiDialog, SIGNAL(accepted()), SLOT(fixedDpiSubmitted())); m_ptrFixDpiDialog->show(); } void MainWindow::fixedDpiSubmitted() { assert(m_ptrFixDpiDialog); assert(m_ptrPages); assert(m_ptrThumbSequence.get()); PageInfo const selected_page_before(m_ptrThumbSequence->selectionLeader()); m_ptrPages->updateMetadataFrom(m_ptrFixDpiDialog->files()); // The thumbnail list also stores page metadata, including the DPI. m_ptrThumbSequence->reset( m_ptrPages->toPageSequence(getCurrentView()), ThumbnailSequence::KEEP_SELECTION, m_ptrThumbSequence->pageOrderProvider() ); PageInfo const selected_page_after(m_ptrThumbSequence->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(); } } void MainWindow::saveProjectTriggered() { if (m_projectFile.isEmpty()) { saveProjectAsTriggered(); return; } if (saveProjectWithFeedback(m_projectFile)) { updateWindowTitle(); } } void MainWindow::saveProjectAsTriggered() { // 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_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; updateWindowTitle(); QSettings settings; settings.setValue( "project/lastDir", QFileInfo(m_projectFile).absolutePath() ); RecentProjects rp; rp.read(); rp.setMostRecent(m_projectFile); rp.write(); } } void MainWindow::newProject() { 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*)) ); } void MainWindow::newProjectCreated(ProjectCreationContext* context) { IntrusivePtr pages( new ProjectPages( context->files(), ProjectPages::AUTO_PAGES, context->layoutDirection() ) ); switchToNewProject(pages, context->outDir()); } void MainWindow::openProject() { if (!closeProjectInteractive()) { return; } QSettings settings; QString const project_dir(settings.value("project/lastDir").toString()); QString const 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); } void MainWindow::openProject(QString const& project_file) { QFile file(project_file); if (!file.open(QIODevice::ReadOnly)) { QMessageBox::warning( this, tr("Error"), tr("Unable to open the project file.") ); return; } QDomDocument doc; if (!doc.setContent(&file)) { QMessageBox::warning( this, tr("Error"), tr("The project file is broken.") ); return; } file.close(); 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(); QSettings settings; settings.setValue( "project/lastDir", QFileInfo(context->projectFile()).absolutePath() ); switchToNewProject( context->projectReader()->pages(), context->projectReader()->outputDirectory(), context->projectFile(), context->projectReader() ); } void MainWindow::closeProject() { closeProjectInteractive(); } void MainWindow::openSettingsDialog() { SettingsDialog* dialog = new SettingsDialog(this); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setWindowModality(Qt::WindowModal); dialog->show(); } void MainWindow::showAboutDialog() { Ui::AboutDialog ui; QDialog* dialog = new QDialog(this); ui.setupUi(dialog); ui.version->setText(QString::fromUtf8(VERSION)); QResource license(":/GPLv3.html"); ui.licenseViewer->setHtml(QString::fromUtf8((char const*)license.data(), license.size())); 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(); m_ptrOutOfMemoryDialog->setParams( m_projectFile, m_ptrStages, m_ptrPages, m_selectedPage, m_outFileNameGen ); closeProjectWithoutSaving(); m_ptrOutOfMemoryDialog->setAttribute(Qt::WA_DeleteOnClose); m_ptrOutOfMemoryDialog.release()->show(); } /** * Note: the removed widgets are not deleted. */ void MainWindow::removeWidgetsFromLayout(QLayout* layout) { 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(); m_ptrOptionsWidget = 0; } void MainWindow::updateProjectActions() { bool const loaded = isProjectLoaded(); actionSaveProject->setEnabled(loaded); actionSaveProjectAs->setEnabled(loaded); actionFixDpi->setEnabled(loaded); actionRelinking->setEnabled(loaded); } bool MainWindow::isBatchProcessingInProgress() const { return m_ptrBatchQueue.get() != 0; } bool MainWindow::isProjectLoaded() const { return !m_outFileNameGen.outDir().isEmpty(); } bool MainWindow::isBelowSelectContent() const { return isBelowSelectContent(m_curFilter); } bool MainWindow::isBelowSelectContent(int const filter_idx) const { return (filter_idx > m_ptrStages->selectContentFilterIdx()); } bool MainWindow::isBelowFixOrientation(int filter_idx) const { return (filter_idx > m_ptrStages->fixOrientationFilterIdx()); } bool MainWindow::isOutputFilter() const { return isOutputFilter(m_curFilter); } bool MainWindow::isOutputFilter(int const filter_idx) const { return (filter_idx == m_ptrStages->outputFilterIdx()); } PageView MainWindow::getCurrentView() const { return m_ptrStages->filterAt(m_curFilter)->getView(); } void MainWindow::updateMainArea() { if (m_ptrPages->numImages() == 0) { filterList->setBatchProcessingPossible(false); showNewOpenProjectPanel(); } else if (isBatchProcessingInProgress()) { filterList->setBatchProcessingPossible(false); setImageWidget(m_ptrBatchProcessingWidget.get(), KEEP_OWNERSHIP); } else { PageInfo const page(m_ptrThumbSequence->selectionLeader()); if (page.isNull()) { filterList->setBatchProcessingPossible(false); removeImageWidget(); removeFilterOptionsWidget(); } else { // Note that loadPageInteractive may reset it to false. filterList->setBatchProcessingPossible(true); loadPageInteractive(page); } } } bool MainWindow::checkReadyForOutput(PageId const* ignore) const { return m_ptrStages->pageLayoutFilter()->checkReadyForOutput( *m_ptrPages, ignore ); } void MainWindow::loadPageInteractive(PageInfo const& page) { assert(!isBatchProcessingInProgress()); m_ptrInteractiveQueue->cancelAndClear(); if (isOutputFilter() && !checkReadyForOutput(&page.id())) { filterList->setBatchProcessingPossible(false); // Switch to the first page - the user will need // to process all pages in batch mode. m_ptrThumbSequence->setSelection(m_ptrThumbSequence->firstPage().id()); QString const 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); return; } if (!isBatchProcessingInProgress()) { if (m_pImageFrameLayout->indexOf(m_ptrProcessingIndicationWidget.get()) != -1) { m_ptrProcessingIndicationWidget->processingRestartedEffect(); } setImageWidget(m_ptrProcessingIndicationWidget.get(), KEEP_OWNERSHIP); m_ptrStages->filterAt(m_curFilter)->preUpdateUI(this, page.id()); } assert(m_ptrThumbnailCache.get()); m_ptrInteractiveQueue->cancelAndClear(); m_ptrInteractiveQueue->addProcessingTask( page, createCompositeTask(page, m_curFilter, /*batch=*/false, m_debug) ); m_ptrWorkerThread->performTask(m_ptrInteractiveQueue->takeForProcessing()); } void MainWindow::updateWindowTitle() { QString project_name; if (m_projectFile.isEmpty()) { project_name = tr("Unnamed"); } else { project_name = QFileInfo(m_projectFile).baseName(); } QString const version(QString::fromUtf8(VERSION)); setWindowTitle(tr("%2 - Scan Tailor %3 [%1bit]").arg(sizeof(void*)*8).arg(project_name, version)); } /** * \brief Closes the currently project, prompting to save it if necessary. * * \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(); return true; } QFileInfo const project_file(m_projectFile); QFileInfo const backup_file( project_file.absoluteDir(), QString::fromAscii("Backup.")+project_file.fileName() ); QString const 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; } 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; } void MainWindow::closeProjectWithoutSaving() { IntrusivePtr pages(new ProjectPages()); switchToNewProject(pages, QString()); } bool MainWindow::saveProjectWithFeedback(QString const& project_file) { ProjectWriter writer(m_ptrPages, m_selectedPage, m_outFileNameGen); if (!writer.write(project_file, m_ptrStages->filters())) { QMessageBox::warning( this, tr("Error"), tr("Error saving the project file!") ); return false; } return true; } /** * Note: showInsertFileDialog(BEFORE, ImageId()) is legal and means inserting at the end. */ void MainWindow::showInsertFileDialog(BeforeOrAfter before_or_after, ImageId const& existing) { if (isBatchProcessingInProgress() || !isProjectLoaded()) { return; } // We need to filter out files already in project. class ProxyModel : public QSortFilterProxyModel { public: ProxyModel(ProjectPages const& pages) { setDynamicSortFilter(true); PageSequence const sequence(pages.toPageSequence(IMAGE_VIEW)); unsigned const count = sequence.numPages(); for (unsigned i = 0; i < count; ++i) { PageInfo const& page = sequence.pageAt(i); m_inProjectFiles.push_back(QFileInfo(page.imageId().filePath())); } } protected: virtual bool filterAcceptsRow(int source_row, QModelIndex const& source_parent) const { QModelIndex const idx(source_parent.child(source_row, 0)); QVariant const data(idx.data(QFileSystemModel::FilePathRole)); if (data.isNull()) { return true; } return !m_inProjectFiles.contains(QFileInfo(data.toString())); } virtual bool lessThan(QModelIndex const& left, QModelIndex const& right) const { return left.row() < right.row(); } private: QFileInfoList m_inProjectFiles; }; std::auto_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; } 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()); // 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()); using namespace boost::lambda; 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) { QFileInfo const file_info(files[i]); ImageFileInfo image_file_info(file_info, std::vector()); void (std::vector::*push_back) (const ImageMetadata&) = &std::vector::push_back; ImageMetadataLoader::Status const status = ImageMetadataLoader::load( files.at(i), boost::lambda::bind(push_back, boost::ref(image_file_info.imageInfo()), boost::lambda::_1) ); 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::auto_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(), !boost::lambda::bind(&ImageFileInfo::isDpiOK, boost::lambda::_1)) != new_files.end()) { std::auto_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(); } // Actually insert the new pages. BOOST_FOREACH(ImageFileInfo const& file, new_files) { int image_num = -1; // Zero-based image number in a multi-page TIFF. BOOST_FOREACH(ImageMetadata const& metadata, file.imageInfo()) { ++image_num; int const num_sub_pages = ProjectPages::adviseNumberOfLogicalPages( metadata, OrthogonalRotation() ); ImageInfo const image_info( ImageId(file.fileInfo(), image_num), metadata, num_sub_pages, false, false ); insertImage(image_info, before_or_after, existing); } } } void MainWindow::showRemovePagesDialog(std::set const& pages) { std::auto_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())); 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); } } /** * Note: insertImage(..., BEFORE, ImageId()) is legal and means inserting at the end. */ void MainWindow::insertImage(ImageInfo const& new_image, BeforeOrAfter before_or_after, ImageId existing) { std::vector pages( m_ptrPages->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()); } BOOST_FOREACH(PageInfo const& page_info, pages) { m_outFileNameGen.disambiguator()->registerFile(page_info.imageId().filePath()); m_ptrThumbSequence->insert(page_info, before_or_after, existing); existing = page_info.imageId(); } } void MainWindow::removeFromProject(std::set const& pages) { m_ptrInteractiveQueue->cancelAndRemove(pages); if (m_ptrBatchQueue.get()) { m_ptrBatchQueue->cancelAndRemove(pages); } m_ptrPages->removePages(pages); m_ptrThumbSequence->removePages(pages); if (m_ptrThumbSequence->selectionLeader().isNull()) { m_ptrThumbSequence->setSelection(m_ptrThumbSequence->firstPage().id()); } updateMainArea(); } void MainWindow::eraseOutputFiles(std::set const& pages) { std::vector erase_variations; erase_variations.reserve(3); BOOST_FOREACH(PageId const& 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; } BOOST_FOREACH(PageId::SubPage subpage, erase_variations) { QFile::remove(m_outFileNameGen.filePathFor(PageId(page_id.imageId(), subpage))); } } } BackgroundTaskPtr MainWindow::createCompositeTask( PageInfo const& page, int const last_filter_idx, bool const batch, bool debug) { IntrusivePtr fix_orientation_task; IntrusivePtr page_split_task; IntrusivePtr deskew_task; IntrusivePtr select_content_task; IntrusivePtr page_layout_task; IntrusivePtr 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 BackgroundTaskPtr( new LoadFileTask( batch ? BackgroundTask::BATCH : BackgroundTask::INTERACTIVE, page, m_ptrThumbnailCache, m_ptrPages, fix_orientation_task ) ); } IntrusivePtr MainWindow::createCompositeCacheDrivenTask(int const last_filter_idx) { IntrusivePtr fix_orientation_task; IntrusivePtr page_split_task; IntrusivePtr deskew_task; IntrusivePtr select_content_task; IntrusivePtr page_layout_task; IntrusivePtr 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; } void MainWindow::updateDisambiguationRecords(PageSequence const& pages) { int const count = pages.numPages(); for (int i = 0; i < count; ++i) { m_outFileNameGen.disambiguator()->registerFile(pages.pageAt(i).imageId().filePath()); } } PageSelectionAccessor MainWindow::newPageSelectionAccessor() { IntrusivePtr provider(new PageSelectionProviderImpl(this)); return PageSelectionAccessor(provider); } scantailor-RELEASE_0_9_12_2/MainWindow.h000066400000000000000000000175761271170121200177240ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef MAINWINDOW_H_ #define MAINWINDOW_H_ #include "ui_MainWindow.h" #include "FilterUiInterface.h" #include "NonCopyable.h" #include "AbstractCommand.h" #include "IntrusivePtr.h" #include "BackgroundTask.h" #include "FilterResult.h" #include "ThumbnailSequence.h" #include "OutputFileNameGenerator.h" #include "PageId.h" #include "PageView.h" #include "PageRange.h" #include "SelectedPage.h" #include "BeforeOrAfter.h" #ifndef Q_MOC_RUN #include #endif #include #include #include #include #include #include #include #include class AbstractFilter; class AbstractRelinker; class ThumbnailPixmapCache; class ProjectPages; class PageSequence; class StageSequence; class PageOrderProvider; class PageSelectionAccessor; class FilterOptionsWidget; class ProcessingIndicationWidget; class ImageInfo; class PageInfo; class QStackedLayout; class WorkerThread; class ProjectReader; class DebugImages; class ContentBoxPropagator; class PageOrientationPropagator; class ProjectCreationContext; class ProjectOpeningContext; class CompositeCacheDrivenTask; class TabbedDebugImages; class ProcessingTaskQueue; class FixDpiDialog; class OutOfMemoryDialog; class QLineF; class QRectF; class QLayout; class MainWindow : public QMainWindow, private FilterUiInterface, private Ui::MainWindow { DECLARE_NON_COPYABLE(MainWindow) Q_OBJECT public: MainWindow(); virtual ~MainWindow(); PageSequence allPages() const; std::set selectedPages() const; std::vector selectedRanges() const; protected: virtual void closeEvent(QCloseEvent* event); virtual void timerEvent(QTimerEvent* event); public slots: void openProject(QString const& project_file); private: enum MainAreaAction { UPDATE_MAIN_AREA, CLEAR_MAIN_AREA }; private slots: void goFirstPage(); void goLastPage(); void goNextPage(); void goPrevPage(); void goToPage(PageId const& page_id); void currentPageChanged( PageInfo const& page_info, QRectF const& thumb_rect, ThumbnailSequence::SelectionFlags flags); void pageContextMenuRequested( PageInfo const& page_info, QPoint const& screen_pos, bool selected); void pastLastPageContextMenuRequested(QPoint const& screen_pos); void thumbViewFocusToggled(bool checked); void thumbViewScrolled(); void filterSelectionChanged(QItemSelection const& selected); void pageOrderingChanged(int idx); void reloadRequested(); void startBatchProcessing(); void stopBatchProcessing(MainAreaAction main_area = UPDATE_MAIN_AREA); void invalidateThumbnail(PageId const& page_id); void invalidateThumbnail(PageInfo const& page_info); void invalidateAllThumbnails(); void showRelinkingDialog(); void filterResult( BackgroundTaskPtr const& task, FilterResultPtr const& result); void debugToggled(bool enabled); void fixDpiDialogRequested(); void fixedDpiSubmitted(); void saveProjectTriggered(); void saveProjectAsTriggered(); void newProject(); void newProjectCreated(ProjectCreationContext* context); void openProject(); void projectOpened(ProjectOpeningContext* context); void closeProject(); void openSettingsDialog(); void showAboutDialog(); void handleOutOfMemorySituation(); private: class PageSelectionProviderImpl; enum SavePromptResult { SAVE, DONT_SAVE, CANCEL }; typedef IntrusivePtr FilterPtr; virtual void setOptionsWidget( FilterOptionsWidget* widget, Ownership ownership); virtual void setImageWidget( QWidget* widget, Ownership ownership, DebugImages* debug_images = 0); virtual IntrusivePtr > relinkingDialogRequester(); void switchToNewProject( IntrusivePtr const& pages, QString const& out_dir, QString const& project_file_path = QString(), ProjectReader const* project_reader = 0); IntrusivePtr createThumbnailCache(); void setupThumbView(); void showNewOpenProjectPanel(); SavePromptResult promptProjectSave(); static bool compareFiles(QString const& fpath1, QString const& fpath2); IntrusivePtr currentPageOrderProvider() const; void updateSortOptions(); void resetThumbSequence( IntrusivePtr const& page_order_provider); void removeWidgetsFromLayout(QLayout* layout); void removeFilterOptionsWidget(); void removeImageWidget(); void updateProjectActions(); bool isBatchProcessingInProgress() const; bool isProjectLoaded() const; bool isBelowSelectContent() const; bool isBelowSelectContent(int filter_idx) const; bool isBelowFixOrientation(int filter_idx) const; bool isOutputFilter() const; bool isOutputFilter(int filter_idx) const; PageView getCurrentView() const; void updateMainArea(); bool checkReadyForOutput(PageId const* ignore = 0) const; void loadPageInteractive(PageInfo const& page); void updateWindowTitle(); bool closeProjectInteractive(); void closeProjectWithoutSaving(); bool saveProjectWithFeedback(QString const& project_file); void showInsertFileDialog( BeforeOrAfter before_or_after, ImageId const& existig); void showRemovePagesDialog(std::set const& pages); void insertImage(ImageInfo const& new_image, BeforeOrAfter before_or_after, ImageId existing); void removeFromProject(std::set const& pages); void eraseOutputFiles(std::set const& pages); BackgroundTaskPtr createCompositeTask( PageInfo const& page, int last_filter_idx, bool batch, bool debug); IntrusivePtr createCompositeCacheDrivenTask(int last_filter_idx); void createBatchProcessingWidget(); void updateDisambiguationRecords(PageSequence const& pages); void performRelinking(IntrusivePtr const& relinker); PageSelectionAccessor newPageSelectionAccessor(); QSizeF m_maxLogicalThumbSize; IntrusivePtr m_ptrPages; IntrusivePtr m_ptrStages; QString m_projectFile; OutputFileNameGenerator m_outFileNameGen; IntrusivePtr m_ptrThumbnailCache; std::auto_ptr m_ptrThumbSequence; std::auto_ptr m_ptrWorkerThread; std::auto_ptr m_ptrBatchQueue; std::auto_ptr m_ptrInteractiveQueue; QStackedLayout* m_pImageFrameLayout; QStackedLayout* m_pOptionsFrameLayout; QPointer m_ptrOptionsWidget; QPointer m_ptrFixDpiDialog; std::auto_ptr m_ptrTabbedDebugImages; std::auto_ptr m_ptrContentBoxPropagator; std::auto_ptr m_ptrPageOrientationPropagator; std::auto_ptr m_ptrBatchProcessingWidget; std::auto_ptr m_ptrProcessingIndicationWidget; boost::function m_checkBeepWhenFinished; SelectedPage m_selectedPage; QObjectCleanupHandler m_optionsWidgetCleanup; QObjectCleanupHandler m_imageWidgetCleanup; std::auto_ptr m_ptrOutOfMemoryDialog; int m_curFilter; int m_ignoreSelectionChanges; int m_ignorePageOrderingChanges; bool m_debug; bool m_closing; bool m_beepOnBatchProcessingCompletion; }; #endif scantailor-RELEASE_0_9_12_2/Margins.h000066400000000000000000000027251271170121200172360ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef MARGINS_H_ #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; }; #endif scantailor-RELEASE_0_9_12_2/NewOpenProjectPanel.cpp000066400000000000000000000123421271170121200220470ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "NewOpenProjectPanel.h.moc" #include "RecentProjects.h" #include "Utils.h" #ifndef Q_MOC_RUN #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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( boost::lambda::bind( &NewOpenProjectPanel::addRecentProject, this, boost::lambda::_1 ) ); } connect( newProjectLabel, SIGNAL(linkActivated(QString const&)), this, SIGNAL(newProject()) ); connect( openProjectLabel, SIGNAL(linkActivated(QString const&)), this, SIGNAL(openProject()) ); } void NewOpenProjectPanel::addRecentProject(QString const& file_path) { QFileInfo const file_info(file_path); QString base_name(file_info.baseName()); if (base_name.isEmpty()) { base_name = QChar('_'); } QLabel* label = new QLabel(recentProjectsGroup); label->setWordWrap(true); label->setTextFormat(Qt::RichText); label->setText(Utils::richTextForLink(base_name, file_path)); label->setToolTip(file_path); recentProjectsGroup->layout()->addWidget(label); connect( label, SIGNAL(linkActivated(QString const&)), this, SIGNAL(openRecentProject(QString const&)) ); } void NewOpenProjectPanel::paintEvent(QPaintEvent* event) { // 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); QRect const widget_rect(rect()); QRect const except_margins( widget_rect.adjusted(left, top, -right, -bottom) ); int const border = 1; // Solid line border width. int const shadow = std::min(right, bottom) - border; QPainter painter(this); // Draw the border. painter.setPen(QPen(palette().windowText(), border)); // Note that we specify the coordinates excluding // pen width. painter.drawRect(except_margins); QColor const dark(Qt::darkGray); QColor const light(Qt::transparent); if (shadow <= 0) { return; } // Let's adjust the margins to exclude borders. left -= border; right -= border; top -= border; bottom -= border; // This rectangle extends 1 pixel into each shadow area. QRect const extended( except_margins.adjusted( -border - 1, -border - 1, border + 1, border + 1 ) ); // Right shadow. { QRect rect(widget_rect); rect.setWidth(shadow); rect.moveLeft(extended.right()); rect.adjust(0, top + shadow, 0, -bottom); QLinearGradient grad(rect.topLeft(), rect.topRight()); grad.setColorAt(0, dark); grad.setColorAt(1, light); painter.fillRect(rect, grad); } // Down shadow. { QRect rect(widget_rect); rect.setHeight(shadow); rect.moveTop(extended.bottom()); rect.adjust(left + shadow, 0, -right, 0); QLinearGradient grad(rect.topLeft(), rect.bottomLeft()); grad.setColorAt(0, dark); grad.setColorAt(1, light); painter.fillRect(rect, grad); } // Bottom-right corner. { QRect rect(0, 0, shadow, shadow); rect.moveTopLeft(extended.bottomRight()); QRadialGradient grad(rect.topLeft(), shadow); grad.setColorAt(0, dark); grad.setColorAt(1, light); painter.fillRect(rect, grad); } // Top-right corner. { QRect rect(0, 0, shadow, shadow); rect.moveTopLeft(extended.topRight() + QPoint(0, border)); QRadialGradient grad(rect.bottomLeft(), shadow); grad.setColorAt(0, dark); grad.setColorAt(1, light); painter.fillRect(rect, grad); } // Bottom-left corner. { QRect rect(0, 0, shadow, shadow); rect.moveTopLeft(extended.bottomLeft() + QPoint(border, 0)); QRadialGradient grad(rect.topRight(), shadow); grad.setColorAt(0, dark); grad.setColorAt(1, light); painter.fillRect(rect, grad); } } scantailor-RELEASE_0_9_12_2/NewOpenProjectPanel.h000066400000000000000000000024401271170121200215120ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef NEW_OPEN_PROJECT_PANEL_H_ #define NEW_OPEN_PROJECT_PANEL_H_ #include "ui_NewOpenProjectPanel.h" #include class QString; class NewOpenProjectPanel : public QWidget, private Ui::NewOpenProjectPanel { Q_OBJECT public: NewOpenProjectPanel(QWidget* parent = 0); signals: void newProject(); void openProject(); void openRecentProject(QString const& project_file); protected: virtual void paintEvent(QPaintEvent* event); private: void addRecentProject(QString const& file_path); }; #endif scantailor-RELEASE_0_9_12_2/NonOwningWidget.cpp000066400000000000000000000021211271170121200212370ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "NonOwningWidget.h" #include NonOwningWidget::NonOwningWidget(QWidget* parent) : QWidget(parent) { } NonOwningWidget::~NonOwningWidget() { BOOST_FOREACH(QObject* child, children()) { if (QWidget* widget = dynamic_cast(child)) { widget->setParent(0); } } } scantailor-RELEASE_0_9_12_2/NonOwningWidget.h000066400000000000000000000021711271170121200207110ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef NON_OWNING_WIDGET_H_ #define NON_OWNING_WIDGET_H_ #include /** * \brief Your normal QWidget, except it doesn't delete its children with itself, * rather it calls setParent(0) on them. */ class NonOwningWidget : public QWidget { public: NonOwningWidget(QWidget* parent = 0); virtual ~NonOwningWidget(); }; #endif scantailor-RELEASE_0_9_12_2/OpenGLSupport.cpp000066400000000000000000000024731271170121200207120ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "OpenGLSupport.h" #include "config.h" #include #ifdef ENABLE_OPENGL #include #include #endif bool OpenGLSupport::supported() { #ifndef ENABLE_OPENGL return false; #else if (!QGLFormat::hasOpenGL()) { return false; } QGLFormat format; format.setSampleBuffers(true); format.setStencil(true); format.setAlpha(true); QGLWidget widget(format); format = widget.format(); if (!format.sampleBuffers()) { return false; } if (!format.stencil()) { return false; } if (!format.alpha()) { return false; } return true; #endif } scantailor-RELEASE_0_9_12_2/OpenGLSupport.h000066400000000000000000000017131271170121200203530ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OPENGL_SUPPORT_H_ #define OPENGL_SUPPORT_H_ class OpenGLSupport { public: /** * \brief Returns true if OpenGL support is present and provides the necessary features. */ static bool supported(); }; #endif scantailor-RELEASE_0_9_12_2/OrthogonalRotation.cpp000066400000000000000000000061641271170121200220260ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "OrthogonalRotation.h" #include #include #include #include #include void OrthogonalRotation::nextClockwiseDirection() { m_degrees += 90; if (m_degrees == 360) { m_degrees = 0; } } void OrthogonalRotation::prevClockwiseDirection() { m_degrees -= 90; if (m_degrees == -90) { m_degrees = 270; } } QSize OrthogonalRotation::rotate(QSize const& dimensions) const { if (m_degrees == 90 || m_degrees == 270) { return QSize(dimensions.height(), dimensions.width()); } else { return dimensions; } } QSize OrthogonalRotation::unrotate(QSize const& dimensions) const { return rotate(dimensions); } QSizeF OrthogonalRotation::rotate(QSizeF const& dimensions) const { if (m_degrees == 90 || m_degrees == 270) { return QSizeF(dimensions.height(), dimensions.width()); } else { return dimensions; } } QSizeF OrthogonalRotation::unrotate(QSizeF const& dimensions) const { return rotate(dimensions); } QPointF OrthogonalRotation::rotate( QPointF const& point, double const xmax, double const 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 OrthogonalRotation::unrotate( QPointF const& point, double const xmax, double const 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; } QTransform OrthogonalRotation::transform(QSizeF const& 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; } scantailor-RELEASE_0_9_12_2/OrthogonalRotation.h000066400000000000000000000034311271170121200214650ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef ORTHOGONALROTATION_H_ #define ORTHOGONALROTATION_H_ class QSize; class QSizeF; class QPointF; class QTransform; class OrthogonalRotation { public: OrthogonalRotation() : m_degrees(0) {} int toDegrees() const { return m_degrees; } void nextClockwiseDirection(); void prevClockwiseDirection(); QSize rotate(QSize const& dimensions) const; QSize unrotate(QSize const& dimensions) const; QSizeF rotate(QSizeF const& dimensions) const; QSizeF unrotate(QSizeF const& dimensions) const; QPointF rotate(QPointF const& point, double xmax, double ymax) const; QPointF unrotate(QPointF const& point, double xmax, double ymax) const; QTransform transform(QSizeF const& dimensions) const; private: int m_degrees; }; inline bool operator==(OrthogonalRotation const& lhs, OrthogonalRotation const& rhs) { return lhs.toDegrees() == rhs.toDegrees(); } inline bool operator!=(OrthogonalRotation const& lhs, OrthogonalRotation const& rhs) { return lhs.toDegrees() != rhs.toDegrees(); } #endif scantailor-RELEASE_0_9_12_2/OutOfMemoryDialog.cpp000066400000000000000000000065541271170121200215420ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "OutOfMemoryDialog.h" #include "OutOfMemoryDialog.h.moc" #include "ProjectWriter.h" #include "RecentProjects.h" #include #include #include #include #include OutOfMemoryDialog::OutOfMemoryDialog(QWidget* parent) : QDialog(parent) { ui.setupUi(this); if (sizeof(void*) > 32) { ui.only_32bit_1->hide(); ui.only_32bit_2->hide(); } 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())); } void OutOfMemoryDialog::setParams( QString const& project_file, IntrusivePtr const& stages, IntrusivePtr const& pages, SelectedPage const& selected_page, OutputFileNameGenerator const& out_file_name_gen) { m_projectFile = project_file; m_ptrStages = stages; m_ptrPages = pages; m_selectedPage = selected_page; m_outFileNameGen = out_file_name_gen; ui.saveProjectBtn->setVisible(!project_file.isEmpty()); } void OutOfMemoryDialog::saveProject() { 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(); } } bool OutOfMemoryDialog::saveProjectWithFeedback(QString const& project_file) { ProjectWriter writer(m_ptrPages, m_selectedPage, m_outFileNameGen); if (!writer.write(project_file, m_ptrStages->filters())) { QMessageBox::warning( this, tr("Error"), tr("Error saving the project file!") ); return false; } return true; } void OutOfMemoryDialog::showSaveSuccessScreen() { ui.topLevelStack->setCurrentWidget(ui.saveSuccessPage); } scantailor-RELEASE_0_9_12_2/OutOfMemoryDialog.h000066400000000000000000000034101271170121200211730ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUT_OF_MEMORY_DIALOG_H_ #define OUT_OF_MEMORY_DIALOG_H_ #include "ui_OutOfMemoryDialog.h" #include "OutputFileNameGenerator.h" #include "IntrusivePtr.h" #include "StageSequence.h" #include "ProjectPages.h" #include "SelectedPage.h" #include #include class OutOfMemoryDialog : public QDialog { Q_OBJECT public: OutOfMemoryDialog(QWidget* parent = 0); void setParams( QString const& project_file, // may be empty IntrusivePtr const& stages, IntrusivePtr const& pages, SelectedPage const& selected_page, OutputFileNameGenerator const& out_file_name_gen); private slots: void saveProject(); void saveProjectAs(); private: bool saveProjectWithFeedback(QString const& project_file); void showSaveSuccessScreen(); Ui::OutOfMemoryDialog ui; QString m_projectFile; IntrusivePtr m_ptrStages; IntrusivePtr m_ptrPages; SelectedPage m_selectedPage; OutputFileNameGenerator m_outFileNameGen; }; #endif scantailor-RELEASE_0_9_12_2/OutOfMemoryHandler.cpp000066400000000000000000000040241271170121200217060ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "OutOfMemoryHandler.h" #include "OutOfMemoryHandler.h.moc" #include #include #include 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; } void OutOfMemoryHandler::allocateEmergencyMemory(size_t bytes) { QMutexLocker const locker(&m_mutex); boost::scoped_array(new char[bytes]).swap(m_emergencyBuffer); } void OutOfMemoryHandler::handleOutOfMemorySituation() { QMutexLocker const locker(&m_mutex); if (m_hadOOM) { return; } m_hadOOM = true; boost::scoped_array().swap(m_emergencyBuffer); QMetaObject::invokeMethod (this, "outOfMemory", Qt::QueuedConnection); } bool OutOfMemoryHandler::hadOutOfMemorySituation() const { QMutexLocker const locker(&m_mutex); return m_hadOOM; } scantailor-RELEASE_0_9_12_2/OutOfMemoryHandler.h000066400000000000000000000030721271170121200213550ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUT_OF_MEMORY_HANDLER_H_ #define OUT_OF_MEMORY_HANDLER_H_ #include "NonCopyable.h" #include #include #ifndef Q_MOC_RUN #include #endif #include class OutOfMemoryHandler : public QObject { Q_OBJECT DECLARE_NON_COPYABLE(OutOfMemoryHandler) public: static OutOfMemoryHandler& instance(); /** * To be called once, before any OOM situations can occur. */ void allocateEmergencyMemory(size_t bytes); /** May be called from any thread. */ void handleOutOfMemorySituation(); bool hadOutOfMemorySituation() const; signals: /** Will be dispatched from the main thread. */ void outOfMemory(); private: OutOfMemoryHandler(); mutable QMutex m_mutex; boost::scoped_array m_emergencyBuffer; bool m_hadOOM; }; #endif scantailor-RELEASE_0_9_12_2/OutputFileNameGenerator.cpp000066400000000000000000000051201271170121200227310ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "OutputFileNameGenerator.h" #include "PageId.h" #include "RelinkablePath.h" #include "AbstractRelinker.h" #include #include #include OutputFileNameGenerator::OutputFileNameGenerator() : m_ptrDisambiguator(new FileNameDisambiguator), m_outDir(), m_layoutDirection(Qt::LeftToRight) { } OutputFileNameGenerator::OutputFileNameGenerator( IntrusivePtr const& disambiguator, QString const& out_dir, Qt::LayoutDirection layout_direction) : m_ptrDisambiguator(disambiguator), m_outDir(out_dir), m_layoutDirection(layout_direction) { assert(m_ptrDisambiguator.get()); } void OutputFileNameGenerator::performRelinking(AbstractRelinker const& relinker) { m_ptrDisambiguator->performRelinking(relinker); m_outDir = relinker.substitutionPathFor(RelinkablePath(m_outDir, RelinkablePath::Dir)); } QString OutputFileNameGenerator::fileNameFor(PageId const& page) const { bool const ltr = (m_layoutDirection == Qt::LeftToRight); PageId::SubPage const sub_page = page.subPage(); int const label = m_ptrDisambiguator->getLabel(page.imageId().filePath()); QString name(QFileInfo(page.imageId().filePath()).completeBaseName()); if (label != 0) { name += QString::fromAscii("(%1)").arg(label); } if (page.imageId().isMultiPageFile()) { name += QString::fromAscii("_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::fromAscii(".tif"); return name; } QString OutputFileNameGenerator::filePathFor(PageId const& page) const { QString const file_name(fileNameFor(page)); return QDir(m_outDir).absoluteFilePath(file_name); } scantailor-RELEASE_0_9_12_2/OutputFileNameGenerator.h000066400000000000000000000034721271170121200224060ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_FILE_NAME_GENERATOR_H_ #define OUTPUT_FILE_NAME_GENERATOR_H_ #include "FileNameDisambiguator.h" #include "IntrusivePtr.h" #include #include class PageId; class AbstractRelinker; class OutputFileNameGenerator { // Member-wise copying is OK. public: OutputFileNameGenerator(); OutputFileNameGenerator( IntrusivePtr const& disambiguator, QString const& out_dir, Qt::LayoutDirection layout_direction); void performRelinking(AbstractRelinker const& relinker); Qt::LayoutDirection layoutDirection() const { return m_layoutDirection; } QString const& outDir() const { return m_outDir; } FileNameDisambiguator* disambiguator() { return m_ptrDisambiguator.get(); } FileNameDisambiguator const* disambiguator() const { return m_ptrDisambiguator.get(); } QString fileNameFor(PageId const& page) const; QString filePathFor(PageId const& page) const; private: IntrusivePtr m_ptrDisambiguator; QString m_outDir; Qt::LayoutDirection m_layoutDirection; }; #endif scantailor-RELEASE_0_9_12_2/PageId.cpp000066400000000000000000000041371271170121200173210ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "PageId.h" #include #include PageId::PageId() : m_subPage(SINGLE_PAGE) { } PageId::PageId(ImageId const& image_id, SubPage subpage) : m_imageId(image_id), m_subPage(subpage) { } QString PageId::subPageToString(SubPage const sub_page) { char const* str = 0; 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::fromAscii(str); } PageId::SubPage PageId::subPageFromString(QString const& 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 operator==(PageId const& lhs, PageId const& rhs) { return lhs.subPage() == rhs.subPage() && lhs.imageId() == rhs.imageId(); } bool operator!=(PageId const& lhs, PageId const& rhs) { return !(lhs == rhs); } bool operator<(PageId const& lhs, PageId const& rhs) { if (lhs.imageId() < rhs.imageId()) { return true; } else if (rhs.imageId() < lhs.imageId()) { return false; } else { return lhs.subPage() < rhs.subPage(); } } scantailor-RELEASE_0_9_12_2/PageId.h000066400000000000000000000040061271170121200167610ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGEID_H_ #define PAGEID_H_ #include "ImageId.h" class QString; /** * \brief A logical page on an image. * * 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 }; 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(ImageId const& image_id, SubPage subpage = SINGLE_PAGE); bool isNull() const { return m_imageId.isNull(); } ImageId& imageId() { return m_imageId; } ImageId const& imageId() const { return m_imageId; } SubPage subPage() const { return m_subPage; } QString subPageAsString() const { return subPageToString(m_subPage); } static QString subPageToString(SubPage sub_page); static SubPage subPageFromString(QString const& string, bool* ok = 0); private: ImageId m_imageId; SubPage m_subPage; }; bool operator==(PageId const& lhs, PageId const& rhs); bool operator!=(PageId const& lhs, PageId const& rhs); bool operator<(PageId const& lhs, PageId const& rhs); #endif scantailor-RELEASE_0_9_12_2/PageInfo.cpp000066400000000000000000000023201271170121200176500ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "PageInfo.h" PageInfo::PageInfo() : m_imageSubPages(0), m_leftHalfRemoved(false), m_rightHalfRemoved(false) { } PageInfo::PageInfo( PageId const& page_id, ImageMetadata const& 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) { } scantailor-RELEASE_0_9_12_2/PageInfo.h000066400000000000000000000032361271170121200173240ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGEINFO_H_ #define PAGEINFO_H_ #include "PageId.h" #include "ImageMetadata.h" class PageInfo { // Member-wise copying is OK. public: PageInfo(); PageInfo(PageId const& page_id, ImageMetadata const& metadata, int image_sub_pages, bool left_half_removed, bool right_half_removed); bool isNull() const { return m_pageId.isNull(); } PageId const& id() const { return m_pageId; } void setId(PageId const& id) { m_pageId = id; } ImageId const& imageId() const { return m_pageId.imageId(); } ImageMetadata const& 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; }; #endif scantailor-RELEASE_0_9_12_2/PageOrderOption.h000066400000000000000000000027031271170121200206730ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_ORDER_OPTION_H_ #define PAGE_ORDER_OPTION_H_ #include "IntrusivePtr.h" #include "PageOrderProvider.h" #include class PageOrderOption { // Member-wise copying is OK. public: typedef IntrusivePtr ProviderPtr; PageOrderOption(QString const& name, ProviderPtr const& provider) : m_name(name), m_ptrProvider(provider) {} QString const& name() const { return m_name; } /** * Returns the ordering information provider. * A null provider is OK and is to be interpreted as default order. */ ProviderPtr const& provider() const { return m_ptrProvider; } private: QString m_name; ProviderPtr m_ptrProvider; }; #endif scantailor-RELEASE_0_9_12_2/PageOrderProvider.h000066400000000000000000000024731271170121200212210ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_ORDER_PROVIDER_H_ #define PAGE_ORDER_PROVIDER_H_ #include "RefCountable.h" class PageId; /** * A base class for different page ordering strategies. */ class PageOrderProvider : public RefCountable { 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( PageId const& lhs_page, bool lhs_incomplete, PageId const& rhs_page, bool rhs_incomplete) const = 0; }; #endif scantailor-RELEASE_0_9_12_2/PageOrientationPropagator.cpp000066400000000000000000000041351271170121200233150ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "PageOrientationPropagator.h" #include "CompositeCacheDrivenTask.h" #include "OrthogonalRotation.h" #include "ProjectPages.h" #include "PageSequence.h" #include "PageView.h" #include "PageInfo.h" #include "filters/page_split/Filter.h" #include "filter_dc/PageOrientationCollector.h" class PageOrientationPropagator::Collector : public PageOrientationCollector { public: virtual void process(OrthogonalRotation const& orientation) { m_orientation = orientation; } OrthogonalRotation const& orientation() const { return m_orientation; } private: OrthogonalRotation m_orientation; }; PageOrientationPropagator::PageOrientationPropagator( IntrusivePtr const& page_split_filter, IntrusivePtr const& task) : m_ptrPageSplitFilter(page_split_filter), m_ptrTask(task) { } PageOrientationPropagator::~PageOrientationPropagator() { } void PageOrientationPropagator::propagate(ProjectPages const& pages) { PageSequence const sequence(pages.toPageSequence(PAGE_VIEW)); size_t const num_pages = sequence.numPages(); for (size_t i = 0; i < num_pages; ++i) { PageInfo const& page_info = sequence.pageAt(i); Collector collector; m_ptrTask->process(page_info, &collector); m_ptrPageSplitFilter->pageOrientationUpdate( page_info.imageId(), collector.orientation() ); } } scantailor-RELEASE_0_9_12_2/PageOrientationPropagator.h000066400000000000000000000034401271170121200227600ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_ORIENTATION_PROPAGATOR_H_ #define PAGE_ORIENTATION_PROPAGATOR_H_ #include "IntrusivePtr.h" #include class CompositeCacheDrivenTask; class ProjectPages; namespace page_split { class Filter; } /** * \brief Propagates page orientations from the "Fix Orientation" * to the "Split Pages" stage. * * This is necessary because the decision of whether to treat a scan * as two pages or one needs to be made collectively by the "Fix Orientation" * and "Split Pages" stages. "Split Pages" might or might not know the definite * answer, while "Fix Orientation" provides a hint. */ class PageOrientationPropagator { public: PageOrientationPropagator( IntrusivePtr const& page_split_filter, IntrusivePtr const& task); ~PageOrientationPropagator(); void propagate(ProjectPages const& pages); private: class Collector; IntrusivePtr m_ptrPageSplitFilter; IntrusivePtr m_ptrTask; }; #endif scantailor-RELEASE_0_9_12_2/PageRange.cpp000066400000000000000000000025751271170121200200250ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "PageRange.h" #include std::set PageRange::selectEveryOther(PageId const& base) const { std::set selection; std::vector::const_iterator it(pages.begin()); std::vector::const_iterator const end(pages.end()); for (; it != end && *it != base; ++it) { // Continue until we have a match. } if (it == end) { return selection; } int const base_idx = it - pages.begin(); int idx = 0; BOOST_FOREACH(PageId const& page_id, pages) { if (((idx - base_idx) & 1) == 0) { selection.insert(page_id); } ++idx; } return selection; } scantailor-RELEASE_0_9_12_2/PageRange.h000066400000000000000000000020721271170121200174620ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_RANGE_H_ #define PAGE_RANGE_H_ #include "PageId.h" #include #include class PageRange { public: /** * \brief Ordered list of consecutive pages. */ std::vector pages; std::set selectEveryOther(PageId const& base) const; }; #endif scantailor-RELEASE_0_9_12_2/PageSelectionAccessor.cpp000066400000000000000000000024401271170121200223700ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "PageSelectionAccessor.h" #include "PageSequence.h" PageSelectionAccessor::PageSelectionAccessor( IntrusivePtr const& provider) : m_ptrProvider(provider) { } PageSequence PageSelectionAccessor::allPages() const { return m_ptrProvider->allPages(); } std::set PageSelectionAccessor::selectedPages() const { return m_ptrProvider->selectedPages(); } std::vector PageSelectionAccessor::selectedRanges() const { return m_ptrProvider->selectedRanges(); } scantailor-RELEASE_0_9_12_2/PageSelectionAccessor.h000066400000000000000000000025651271170121200220450ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_SELECTION_ACCESSOR_H_ #define PAGE_SELECTION_ACCESSOR_H_ #include "PageSelectionProvider.h" #include "PageId.h" #include "PageRange.h" #include "IntrusivePtr.h" #include #include class PageSequence; class PageSelectionAccessor { // Member-wise copying is OK. public: explicit PageSelectionAccessor( IntrusivePtr const& provider); PageSequence allPages() const; std::set selectedPages() const; std::vector selectedRanges() const; private: IntrusivePtr m_ptrProvider; }; #endif scantailor-RELEASE_0_9_12_2/PageSelectionProvider.h000066400000000000000000000022751271170121200220730ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_SELECTION_PROVIDER_H_ #define PAGE_SELECTION_PROVIDER_H_ #include "RefCountable.h" #include #include class PageSequence; class PageId; class PageRange; class PageSelectionProvider : public RefCountable { public: virtual PageSequence allPages() const = 0; virtual std::set selectedPages() const = 0; virtual std::vector selectedRanges() const = 0; }; #endif scantailor-RELEASE_0_9_12_2/PageSequence.cpp000066400000000000000000000042761271170121200205410ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "PageSequence.h" #include void PageSequence::append(PageInfo const& page_info) { m_pages.push_back(page_info); } PageInfo const& PageSequence::pageAt(size_t const idx) const { return m_pages.at(idx); // may throw } std::set PageSequence::selectAll() const { std::set selection; BOOST_FOREACH(PageInfo const& page_info, m_pages) { selection.insert(page_info.id()); } return selection; } std::set PageSequence::selectPagePlusFollowers(PageId const& page) const { std::set selection; std::vector::const_iterator it(m_pages.begin()); std::vector::const_iterator const 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(PageId const& base) const { std::set selection; std::vector::const_iterator it(m_pages.begin()); std::vector::const_iterator const end(m_pages.end()); for (; it != end && it->id() != base; ++it) { // Continue until we have a match. } if (it == end) { return selection; } int const base_idx = it - m_pages.begin(); int idx = 0; BOOST_FOREACH(PageInfo const& page_info, m_pages) { if (((idx - base_idx) & 1) == 0) { selection.insert(page_info.id()); } ++idx; } return selection; } scantailor-RELEASE_0_9_12_2/PageSequence.h000066400000000000000000000025051271170121200201770ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_SEQUENCE_H_ #define PAGE_SEQUENCE_H_ #include "PageInfo.h" #include #include #include class PageSequence { // Member-wise copying is OK. public: void append(PageInfo const& page_info); size_t numPages() const { return m_pages.size(); } PageInfo const& pageAt(size_t idx) const; std::set selectAll() const; std::set selectPagePlusFollowers(PageId const& page) const; std::set selectEveryOther(PageId const& base) const; private: std::vector m_pages; }; #endif scantailor-RELEASE_0_9_12_2/PageView.h000066400000000000000000000015731271170121200173450ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_VIEW_H_ #define PAGE_VIEW_H_ enum PageView { IMAGE_VIEW, PAGE_VIEW }; #endif scantailor-RELEASE_0_9_12_2/PayloadEvent.h000066400000000000000000000021501271170121200202210ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAYLOAD_EVENT_H_ #define PAYLOAD_EVENT_H_ #include template class PayloadEvent : public QEvent { public: PayloadEvent(T const& payload) : QEvent(User), m_payload(payload) {} T const& payload() const { return m_payload; } T& payload() { return m_payload; } private: T m_payload; }; #endif scantailor-RELEASE_0_9_12_2/PhysicalTransformation.cpp000066400000000000000000000022561271170121200226730ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "PhysicalTransformation.h" #include "Dpi.h" #include "imageproc/Constants.h" using namespace imageproc::constants; PhysicalTransformation::PhysicalTransformation(Dpi const& dpi) { double const xscale = dpi.horizontal() * (DPI2DPM / 1000.0); double const yscale = dpi.vertical() * (DPI2DPM / 1000.0); m_mmToPixels.scale(xscale, yscale); m_pixelsToMM.scale(1.0 / xscale, 1.0 / yscale); } scantailor-RELEASE_0_9_12_2/PhysicalTransformation.h000066400000000000000000000022371271170121200223370ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PHYSICALTRANSFORMATION_H_ #define PHYSICALTRANSFORMATION_H_ #include class Dpi; class PhysicalTransformation { public: PhysicalTransformation(Dpi const& dpi); QTransform const& pixelsToMM() const { return m_pixelsToMM; } QTransform const& mmToPixels() const { return m_mmToPixels; } private: QTransform m_pixelsToMM; QTransform m_mmToPixels; }; #endif scantailor-RELEASE_0_9_12_2/PixmapRenderer.cpp000066400000000000000000000104331271170121200211110ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "PixmapRenderer.h" #include "config.h" #include #include #include #include #include #include #include #include #include #ifdef ENABLE_OPENGL # include #endif #ifdef Q_WS_X11 # include # include # include #endif void PixmapRenderer::drawPixmap( QPainter& painter, QPixmap const& pixmap) { #if !defined(Q_WS_X11) drawPixmapNoXRender(painter, pixmap); #else QPaintDevice* const dev = painter.device(); QPoint offset; // Both x and y will be either zero or negative. QPaintDevice* const redir_dev = QPainter::redirected(painter.device(), &offset); QPaintDevice* const paint_dev = redir_dev ? redir_dev : dev; #if defined(ENABLE_OPENGL) if (dynamic_cast(paint_dev)) { drawPixmapNoXRender(painter, pixmap); return; } #endif QRect const device_rect( QRect(0, 0, dev->width(), dev->height()).translated(-offset) ); QRectF const src_rect(pixmap.rect()); Display* const dpy = QX11Info::display(); Picture const src_pict = pixmap.x11PictureHandle(); Picture dst_pict = 0; if (QWidget* widget = dynamic_cast(paint_dev)) { dst_pict = widget->x11PictureHandle(); } else if (QPixmap* pixmap = dynamic_cast(paint_dev)) { dst_pict = pixmap->x11PictureHandle(); } if (!dst_pict) { drawPixmapNoXRender(painter, pixmap); return; } // Note that device transform already accounts for offset // within a destination surface. QTransform const src_to_dst(painter.deviceTransform()); QTransform const dst_to_src(src_to_dst.inverted()); QPolygonF const dst_poly(src_to_dst.map(src_rect)); XTransform xform = {{ { XDoubleToFixed(dst_to_src.m11()), XDoubleToFixed(dst_to_src.m21()), XDoubleToFixed(dst_to_src.m31()) }, { XDoubleToFixed(dst_to_src.m12()), XDoubleToFixed(dst_to_src.m22()), XDoubleToFixed(dst_to_src.m32()) }, { XDoubleToFixed(dst_to_src.m13()), XDoubleToFixed(dst_to_src.m23()), XDoubleToFixed(dst_to_src.m33()) } }}; XRenderSetPictureTransform(dpy, src_pict, &xform); char const* filter = "fast"; if (painter.testRenderHint(QPainter::SmoothPixmapTransform)) { filter = "good"; } XRenderSetPictureFilter(dpy, src_pict, filter, 0, 0); QRectF const dst_rect_precise(dst_poly.boundingRect()); QRect const dst_rect_fitting( QPoint( int(ceil(dst_rect_precise.left())), int(ceil(dst_rect_precise.top())) ), QPoint( int(floor(dst_rect_precise.right())) - 1, int(floor(dst_rect_precise.bottom())) - 1 ) ); QRect dst_bounding_rect(device_rect); if (painter.hasClipping()) { QRect const clip_rect( src_to_dst.map(painter.clipPath()).boundingRect().toRect() ); dst_bounding_rect = dst_bounding_rect.intersected(clip_rect); } QRect const dst_rect(dst_rect_fitting.intersect(dst_bounding_rect)); // Note that XRenderComposite() expects destination coordinates // everywhere, even for source picture origin. XRenderComposite( dpy, PictOpSrc, src_pict, 0, dst_pict, dst_rect.left(), dst_rect.top(), 0, 0, dst_rect.left(), dst_rect.top(), dst_rect.width(), dst_rect.height() ); #endif } void PixmapRenderer::drawPixmapNoXRender(QPainter& painter, QPixmap const& pixmap) { QTransform const inv_transform(painter.worldTransform().inverted()); QRectF const src_rect(inv_transform.map(QRectF(painter.viewport())).boundingRect()); QRectF const bounded_src_rect(src_rect.intersected(pixmap.rect())); painter.drawPixmap(bounded_src_rect, pixmap, bounded_src_rect); } scantailor-RELEASE_0_9_12_2/PixmapRenderer.h000066400000000000000000000034441271170121200205620ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PIXMAPRENDERER_H_ #define PIXMAPRENDERER_H_ class QPainter; class QPixmap; class PixmapRenderer { public: /** * \brief Workarounds some problems with QPainter::drawPixmap(). * * This method is more or less equivalent to: * \code * QPainter::drawPixmap(0, 0, pixmap); * \endcode * However, there are two problems with the above code:\n * 1. On X11, QPainter doesn't (as of Qt 4.4.0) use XRender if * a transformation is applied. We call XRender manually, but * note that not all features of QPainter are implemented. * In particular, clipping will be done by the bounding box * of the requested clip region.\n * 2. On Windows, the above code is very slow if a large zoom is * specified in QPainter. To fix that we calculate the region of * the image to be displayed and pass it to the rendering engine. */ static void drawPixmap(QPainter& painter, QPixmap const& pixmap); private: static void drawPixmapNoXRender(QPainter& painter, QPixmap const& pixmap); }; #endif scantailor-RELEASE_0_9_12_2/PngMetadataLoader.cpp000066400000000000000000000061011271170121200214750ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "PngMetadataLoader.h" #include "ImageMetadata.h" #include "NonCopyable.h" #include "Dpi.h" #include "Dpm.h" #include #include #include // for std::bad_alloc #include #include namespace { class PngHandle { DECLARE_NON_COPYABLE(PngHandle) public: PngHandle(); ~PngHandle(); png_structp handle() const { return m_pPng; } png_infop info() const { return m_pInfo; } private: png_structp m_pPng; png_infop m_pInfo; }; PngHandle::PngHandle() { m_pPng = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); if (!m_pPng) { throw std::bad_alloc(); } m_pInfo = png_create_info_struct(m_pPng); if (!m_pInfo) { throw std::bad_alloc(); } } PngHandle::~PngHandle() { png_destroy_read_struct(&m_pPng, &m_pInfo, 0); } } // anonymous namespace static void readFn(png_structp png_ptr, png_bytep data, png_size_t length) { QIODevice* io_device = (QIODevice*)png_get_io_ptr(png_ptr); while (length > 0) { qint64 const 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( IntrusivePtr(new PngMetadataLoader) ); registered = true; } } ImageMetadataLoader::Status PngMetadataLoader::loadMetadata( QIODevice& io_device, VirtualFunction1& 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); } } out(ImageMetadata(size, dpi)); return LOADED; } scantailor-RELEASE_0_9_12_2/PngMetadataLoader.h000066400000000000000000000025141271170121200211460ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PNGMETADATALOADER_H_ #define PNGMETADATALOADER_H_ #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: virtual Status loadMetadata( QIODevice& io_device, VirtualFunction1& out); }; #endif scantailor-RELEASE_0_9_12_2/ProcessingIndicationWidget.cpp000066400000000000000000000051301271170121200234440ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ProcessingIndicationWidget.h" #include "imageproc/ColorInterpolation.h" #include #include #include #include #include #include #include using namespace imageproc; static double const distinction_increase = 1.0 / 5.0; static double const 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 = palette().color(QPalette::Window).darker(200); m_tailColor = palette().color(QPalette::Window).darker(130); } void ProcessingIndicationWidget::resetAnimation() { m_distinction = 1.0; m_distinctionDelta = distinction_increase; } void ProcessingIndicationWidget::processingRestartedEffect() { 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); return; } 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; } QPainter painter(this); m_animation.nextFrame(head_color, m_tailColor, &painter, animation_rect); if (m_timerId == 0) { m_timerId = startTimer(180); } } void ProcessingIndicationWidget::timerEvent(QTimerEvent* event) { killTimer(event->timerId()); m_timerId = 0; update(animationRect()); } QRect ProcessingIndicationWidget::animationRect() const { QRect r(0, 0, 80, 80); r.moveCenter(rect().center()); r &= rect(); return r; } scantailor-RELEASE_0_9_12_2/ProcessingIndicationWidget.h000066400000000000000000000032741271170121200231200ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PROCESSING_INDICATION_WIDGET_H_ #define PROCESSING_INDICATION_WIDGET_H_ #include "BubbleAnimation.h" #include #include class QRect; /** * \brief This widget is displayed in the central area od the main window * when an image is being processed. */ class ProcessingIndicationWidget : public QWidget { public: ProcessingIndicationWidget(QWidget* parent = 0); /** * \brief Resets animation to the state it had just after * constructing this object. */ void resetAnimation(); /** * \brief Launch the "processing restarted" effect. */ void processingRestartedEffect(); protected: virtual void paintEvent(QPaintEvent* event); virtual void timerEvent(QTimerEvent* event); private: QRect animationRect() const; BubbleAnimation m_animation; QColor m_headColor; QColor m_tailColor; double m_distinction; double m_distinctionDelta; int m_timerId; }; #endif scantailor-RELEASE_0_9_12_2/ProcessingTaskQueue.cpp000066400000000000000000000064321271170121200221340ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ProcessingTaskQueue.h" #include ProcessingTaskQueue::Entry::Entry( PageInfo const& page_info, BackgroundTaskPtr const& tsk) : pageInfo(page_info), task(tsk), takenForProcessing(false) { } ProcessingTaskQueue::ProcessingTaskQueue(Order order) : m_order(order) { } void ProcessingTaskQueue::addProcessingTask( PageInfo const& page_info, BackgroundTaskPtr const& task) { m_queue.push_back(Entry(page_info, task)); } BackgroundTaskPtr ProcessingTaskQueue::takeForProcessing() { BOOST_FOREACH(Entry& ent, m_queue) { if (!ent.takenForProcessing) { ent.takenForProcessing = true; if (m_order == RANDOM_ORDER) { // 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 BackgroundTaskPtr(); } void ProcessingTaskQueue::processingFinished(BackgroundTaskPtr const& task) { std::list::iterator it(m_queue.begin()); std::list::iterator const 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; } } // If we reached this point, it means we've found our entry and // have pointing to it. if (m_order == SEQUENTIAL_ORDER) { // In this mode we select the page that was just processed, // rather than the one currently being processed. This way // we can avoid question marks on selected pages. m_selectedPage = it->pageInfo; } m_queue.erase(it); } PageInfo ProcessingTaskQueue::selectedPage() const { return m_selectedPage; } bool ProcessingTaskQueue::allProcessed() const { return m_queue.empty(); } void ProcessingTaskQueue::cancelAndRemove(std::set const& pages) { std::list::iterator it(m_queue.begin()); std::list::iterator const end(m_queue.end()); while (it != end) { if (pages.find(it->pageInfo.id()) != pages.end()) { if (it->takenForProcessing) { it->task->cancel(); } if (m_selectedPage.id() == it->pageInfo.id()) { m_selectedPage = PageInfo(); } m_queue.erase(it++); } else { ++it; } } } void ProcessingTaskQueue::cancelAndClear() { while (!m_queue.empty()) { Entry& ent = m_queue.front(); if (ent.takenForProcessing) { ent.task->cancel(); } m_queue.pop_front(); } m_selectedPage = PageInfo(); } scantailor-RELEASE_0_9_12_2/ProcessingTaskQueue.h000066400000000000000000000044331271170121200216000ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PROCESSING_TASK_QUEUE_H_ #define PROCESSING_TASK_QUEUE_H_ #include "NonCopyable.h" #include "BackgroundTask.h" #include "PageInfo.h" #include "PageId.h" #include #include class ProcessingTaskQueue { DECLARE_NON_COPYABLE(ProcessingTaskQueue) public: /** * Order only affects the result of selectedPage(). * For single-task queues and for custom-sorted sequences, * use RANDOM_ORDER, otherwise use SEQUENTIAL_ORDER. */ enum Order { SEQUENTIAL_ORDER, RANDOM_ORDER }; ProcessingTaskQueue(Order order); void addProcessingTask(PageInfo const& page_info, BackgroundTaskPtr const& 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(); void processingFinished(BackgroundTaskPtr const& 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; bool allProcessed() const; void cancelAndRemove(std::set const& pages); void cancelAndClear(); private: struct Entry { PageInfo pageInfo; BackgroundTaskPtr task; bool takenForProcessing; Entry(PageInfo const& page_info, BackgroundTaskPtr const& task); }; std::list m_queue; PageInfo m_selectedPage; Order m_order; }; #endif scantailor-RELEASE_0_9_12_2/ProjectCreationContext.cpp000066400000000000000000000067261271170121200226360ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ProjectCreationContext.h.moc" #include "ProjectFilesDialog.h" #include "FixDpiDialog.h" #include "ImageFileInfo.h" #include #include #ifndef Q_MOC_RUN #include #include #endif #include #include ProjectCreationContext::ProjectCreationContext(QWidget* parent) : m_layoutDirection(Qt::LeftToRight), m_pParent(parent) { showProjectFilesDialog(); } ProjectCreationContext::~ProjectCreationContext() { // Deleting a null pointer is OK. delete m_ptrProjectFilesDialog; delete m_ptrFixDpiDialog; } namespace { template bool allDpisOK(T const& container) { using namespace boost::lambda; 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; } if (!m_ptrProjectFilesDialog->isDpiFixingForced() && allDpisOK(m_files)) { emit done(this); } else { showFixDpiDialog(); } } void ProjectCreationContext::projectFilesDialogDestroyed() { if (!m_ptrFixDpiDialog) { deleteLater(); } } void ProjectCreationContext::fixedDpiSubmitted() { m_files = m_ptrFixDpiDialog->files(); emit done(this); } void ProjectCreationContext::fixDpiDialogDestroyed() { 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(); } 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(); } scantailor-RELEASE_0_9_12_2/ProjectCreationContext.h000066400000000000000000000036531271170121200222770ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PROJECTCREATIONCONTEXT_H_ #define PROJECTCREATIONCONTEXT_H_ #include "NonCopyable.h" #include "ImageFileInfo.h" #include #include #include #include #include class ProjectFilesDialog; class FixDpiDialog; class QWidget; class ProjectCreationContext : public QObject { Q_OBJECT DECLARE_NON_COPYABLE(ProjectCreationContext) public: ProjectCreationContext(QWidget* parent); virtual ~ProjectCreationContext(); std::vector const& files() const { return m_files; } QString const& outDir() const { return m_outDir; } Qt::LayoutDirection layoutDirection() const { return m_layoutDirection; } signals: void done(ProjectCreationContext* context); private slots: void projectFilesSubmitted(); void projectFilesDialogDestroyed(); void fixedDpiSubmitted(); void fixDpiDialogDestroyed(); private: void showProjectFilesDialog(); void showFixDpiDialog(); QPointer m_ptrProjectFilesDialog; QPointer m_ptrFixDpiDialog; QString m_outDir; std::vector m_files; Qt::LayoutDirection m_layoutDirection; QWidget* m_pParent; }; #endif scantailor-RELEASE_0_9_12_2/ProjectFilesDialog.cpp000066400000000000000000000500551271170121200217010ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ProjectFilesDialog.h" #include "ProjectFilesDialog.h.moc" #include "NonCopyable.h" #include "ImageMetadata.h" #include "ImageMetadataLoader.h" #include "SmartFilenameOrdering.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 #include class ProjectFilesDialog::Item { public: enum Status { STATUS_DEFAULT, STATUS_LOAD_OK, STATUS_LOAD_FAILED }; Item(QFileInfo const& file_info, Qt::ItemFlags flags) : m_fileInfo(file_info), m_flags(flags), m_status(STATUS_DEFAULT) {} QFileInfo const& fileInfo() const { return m_fileInfo; } Qt::ItemFlags flags() const { return m_flags; } Status status() const { return m_status; } void setStatus(Status status) { m_status = status; } std::vector const& perPageMetadata() const { return m_perPageMetadata; } std::vector& perPageMetadata() { return m_perPageMetadata; } private: QFileInfo m_fileInfo; Qt::ItemFlags m_flags; std::vector m_perPageMetadata; Status m_status; }; class ProjectFilesDialog::FileList : private QAbstractListModel { DECLARE_NON_COPYABLE(FileList) public: enum LoadStatus { LOAD_OK, LOAD_FAILED, NO_MORE_FILES }; FileList(); virtual ~FileList(); QAbstractItemModel* model() { return this; } template void files(OutFunc out) const; Item const& item(QModelIndex const& index) { return m_items[index.row()]; } template void items(OutFunc out) const; template void items(QItemSelection const& selection, OutFunc out) const; size_t count() const { return m_items.size(); } void clear(); template void append(It begin, It end); template void assign(It begin, It end); void remove(QItemSelection const& selection); void prepareForLoadingFiles(); LoadStatus loadNextFile(); private: virtual int rowCount(QModelIndex const& parent) const; virtual QVariant data(QModelIndex const& index, int role) const; virtual Qt::ItemFlags flags(QModelIndex const& index) const; std::vector m_items; std::deque m_itemsToLoad; }; class ProjectFilesDialog::SortedFileList : private QSortFilterProxyModel { DECLARE_NON_COPYABLE(SortedFileList) public: SortedFileList(FileList& delegate); QAbstractProxyModel* model() { return this; } private: virtual bool lessThan(QModelIndex const& lhs, QModelIndex const& rhs) const; FileList& m_rDelegate; }; class ProjectFilesDialog::ItemVisualOrdering { public: bool operator()(Item const& lhs, Item const& rhs) const; }; template void ProjectFilesDialog::FileList::files(OutFunc out) const { std::vector::const_iterator it(m_items.begin()); std::vector::const_iterator const end(m_items.end()); for (; it != end; ++it) { out(it->fileInfo()); } } template void ProjectFilesDialog::FileList::items(OutFunc out) const { std::for_each(m_items.begin(), m_items.end(), out); } template void ProjectFilesDialog::FileList::items(QItemSelection const& selection, OutFunc out) const { QListIterator it(selection); while (it.hasNext()) { QItemSelectionRange const& range = it.next(); for (int row = range.top(); row <= range.bottom(); ++row) { out(m_items[row]); } } } template void ProjectFilesDialog::FileList::append(It begin, It end) { if (begin == end) { return; } size_t const count = std::distance(begin, end); beginInsertRows(QModelIndex(), m_items.size(), m_items.size() + count - 1); m_items.insert(m_items.end(), begin, end); endInsertRows(); } template void ProjectFilesDialog::FileList::assign(It begin, It 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(QString const&)), this, SLOT(inpDirEdited(QString const&)) ); connect( outDirLine, SIGNAL(textEdited(QString const&)), this, SLOT(outDirEdited(QString const&)) ); connect(addToProjectBtn, SIGNAL(clicked()), this, SLOT(addToProject())); connect(removeFromProjectBtn, SIGNAL(clicked()), this, SLOT(removeFromProject())); connect(buttonBox, SIGNAL(accepted()), this, SLOT(onOK())); } ProjectFilesDialog::~ProjectFilesDialog() { } QString ProjectFilesDialog::inputDirectory() const { return inpDirLine->text(); } QString ProjectFilesDialog::outputDirectory() const { return outDirLine->text(); } namespace { template void pushFileInfo(std::vector& files, Item const& item) { files.push_back(ImageFileInfo(item.fileInfo(), item.perPageMetadata())); } bool imageFileInfoLess(ImageFileInfo const& lhs, ImageFileInfo const& rhs) { return SmartFilenameOrdering()(lhs.fileInfo(), rhs.fileInfo()); } } // anonymous namespace std::vector ProjectFilesDialog::inProjectFiles() const { using namespace boost; using namespace boost::lambda; std::vector files; m_ptrInProjectFiles->items(boost::lambda::bind(&pushFileInfo, boost::ref(files), _1)); std::sort(files.begin(), files.end(), imageFileInfoLess); return files; } bool ProjectFilesDialog::isRtlLayout() const { return rtlLayoutCB->isChecked(); } bool ProjectFilesDialog::isDpiFixingForced() const { return forceFixDpi->isChecked(); } QString ProjectFilesDialog::sanitizePath(QString const& path) { QString trimmed(path.trimmed()); if (trimmed.startsWith(QChar('"')) && trimmed.endsWith(QChar('"'))) { trimmed.chop(1); if (!trimmed.isEmpty()) { trimmed.remove(0, 1); } } 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(); } } QString const 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 const dir( QFileDialog::getExistingDirectory( this, tr("Output Directory"), initial_dir ) ); if (!dir.isEmpty()) { setOutputDir(dir); } } void ProjectFilesDialog::inpDirEdited(QString const& text) { setInputDir(sanitizePath(text), /* auto_add_files= */false); } void ProjectFilesDialog::outDirEdited(QString const& text) { m_autoOutDir = false; } namespace { struct FileInfoLess { bool operator()(QFileInfo const& lhs, QFileInfo const& rhs) const { if (lhs == rhs) { // This takes into account filesystem's case sensitivity. return false; } return lhs.absoluteFilePath() < rhs.absoluteFilePath(); } }; template void pushItemWithFlags( QFileInfo const& file, ItemList& items, QSet const& supported_extensions) { Qt::ItemFlags flags; if (supported_extensions.contains(file.suffix().toLower())) { flags = Qt::ItemIsSelectable|Qt::ItemIsEnabled; } items.push_back(Item(file, flags)); } } // anonymous namespace void ProjectFilesDialog::setInputDir(QString const& dir, bool const auto_add_files) { using namespace boost; using namespace boost::lambda; 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; void (std::vector::*push_back) (const QFileInfo&) = &std::vector::push_back; m_ptrInProjectFiles->files( boost::lambda::bind(push_back, var(existing_files), _1) ); 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; std::for_each( files.begin(), files.end(), boost::lambda::bind( &pushItemWithFlags, _1, boost::ref(items), cref(m_supportedExtensions) ) ); m_ptrOffProjectFiles->assign(items.begin(), items.end()); if (auto_add_files && m_ptrInProjectFiles->count() == 0) { offProjectList->selectAll(); addToProject(); } } void ProjectFilesDialog::setOutputDir(QString const& dir) { outDirLine->setText(QDir::toNativeSeparators(dir)); } void ProjectFilesDialog::addToProject() { using namespace boost::lambda; QItemSelection const selection( m_ptrOffProjectFilesSorted->model()->mapSelectionToSource( offProjectList->selectionModel()->selection() ) ); typedef std::vector ItemList; ItemList items; void (ItemList::*push_back) (const Item&) = &ItemList::push_back; m_ptrOffProjectFiles->items(selection, boost::lambda::bind(push_back, var(items), _1)); m_ptrInProjectFiles->append(items.begin(), items.end()); m_ptrOffProjectFiles->remove(selection); } namespace { template void pushItemIfSameDir(C& items, T const& item, QDir const& dir) { if (item.fileInfo().dir() == dir) { items.push_back(item); } } } // anonymous namespace void ProjectFilesDialog::removeFromProject() { using namespace boost; using namespace boost::lambda; QDir const input_dir(inpDirLine->text()); QItemSelection const selection( m_ptrInProjectFilesSorted->model()->mapSelectionToSource( inProjectList->selectionModel()->selection() ) ); typedef std::vector ItemList; ItemList items; m_ptrInProjectFiles->items( selection, boost::lambda::bind( &pushItemIfSameDir, boost::ref(items), _1, cref(input_dir) ) ); m_ptrOffProjectFiles->append(items.begin(), items.end()); m_ptrInProjectFiles->remove(selection); } void ProjectFilesDialog::onOK() { if (m_ptrInProjectFiles->count() == 0) { QMessageBox::warning( this, tr("Error"), tr("No files in project!") ); return; } QDir const 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; } QDir const 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; } } 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(); } void ProjectFilesDialog::startLoadingMetadata() { m_ptrInProjectFiles->prepareForLoadingFiles(); progressBar->setMaximum(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; } 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; } } 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(); } /*====================== ProjectFilesDialog::FileList ====================*/ ProjectFilesDialog::FileList::FileList() { } ProjectFilesDialog::FileList::~FileList() { } void ProjectFilesDialog::FileList::clear() { if (m_items.empty()) { return; } beginRemoveRows(QModelIndex(), 0, m_items.size() - 1); m_items.clear(); endRemoveRows(); } void ProjectFilesDialog::FileList::remove(QItemSelection const& selection) { using namespace boost::lambda; if (selection.isEmpty()) { return; } typedef std::pair Range; QVector sorted_ranges; std::transform( selection.begin(), selection.end(), std::back_inserter(sorted_ranges), boost::lambda::bind( constructor(), boost::lambda::bind(&QItemSelectionRange::top, _1), boost::lambda::bind(&QItemSelectionRange::bottom, _1) ) ); // This hack is required to make it build with boost 1.44. typedef int const Range::* IntMemPtr; std::sort( sorted_ranges.begin(), sorted_ranges.end(), boost::lambda::bind((IntMemPtr)&Range::first, _1) < boost::lambda::bind((IntMemPtr)&Range::first, _2) ); QVectorIterator it(sorted_ranges); int rows_removed = 0; while (it.hasNext()) { Range const& range = it.next(); int const first = range.first - rows_removed; int const 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; } } int ProjectFilesDialog::FileList::rowCount(QModelIndex const&) const { return m_items.size(); } QVariant ProjectFilesDialog::FileList::data(QModelIndex const& index, int const role) const { Item const& 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; } return QVariant(); } Qt::ItemFlags ProjectFilesDialog::FileList::flags(QModelIndex const& index) const { return m_items[index.row()].flags(); } void ProjectFilesDialog::FileList::prepareForLoadingFiles() { using namespace boost::lambda; std::deque item_indexes; int const num_items = m_items.size(); for (int i = 0; i < num_items; ++i) { item_indexes.push_back(i); } std::sort( item_indexes.begin(), item_indexes.end(), boost::lambda::bind( &ItemVisualOrdering::operator(), ItemVisualOrdering(), var(m_items)[_1], var(m_items)[_2] ) ); m_itemsToLoad.swap(item_indexes); } ProjectFilesDialog::FileList::LoadStatus ProjectFilesDialog::FileList::loadNextFile() { using namespace boost::lambda; if (m_itemsToLoad.empty()) { return NO_MORE_FILES; } int const item_idx = m_itemsToLoad.front(); Item& item = m_items[item_idx]; std::vector per_page_metadata; QString const file_path(item.fileInfo().absoluteFilePath()); void (std::vector::*push_back) (const ImageMetadata&) = &std::vector::push_back; ImageMetadataLoader::Status const st = ImageMetadataLoader::load( file_path, boost::lambda::bind( push_back, var(per_page_metadata), _1 ) ); 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); } QModelIndex const idx(index(item_idx, 0)); emit dataChanged(idx, idx); m_itemsToLoad.pop_front(); return status; } /*================= ProjectFilesDialog::SortedFileList ===================*/ ProjectFilesDialog::SortedFileList::SortedFileList(FileList& delegate) : m_rDelegate(delegate) { setSourceModel(delegate.model()); setDynamicSortFilter(true); sort(0); } bool ProjectFilesDialog::SortedFileList::lessThan( QModelIndex const& lhs, QModelIndex const& rhs) const { Item const& lhs_item = m_rDelegate.item(lhs); Item const& rhs_item = m_rDelegate.item(rhs); return ItemVisualOrdering()(lhs_item, rhs_item); } /*=============== ProjectFilesDialog::ItemVisualOrdering =================*/ bool ProjectFilesDialog::ItemVisualOrdering::operator()( Item const& lhs, Item const& rhs) const { bool const lhs_failed = (lhs.status() == Item::STATUS_LOAD_FAILED); bool const 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()); } scantailor-RELEASE_0_9_12_2/ProjectFilesDialog.h000066400000000000000000000043511271170121200213440ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PROJECTFILESDIALOG_H_ #define PROJECTFILESDIALOG_H_ #include "ui_ProjectFilesDialog.h" #include "ImageFileInfo.h" #include #include #include #include #include class ProjectFilesDialog : public QDialog, private Ui::ProjectFilesDialog { Q_OBJECT public: ProjectFilesDialog(QWidget* parent = 0); virtual ~ProjectFilesDialog(); QString inputDirectory() const; QString outputDirectory() const; std::vector inProjectFiles() const; bool isRtlLayout() const; bool isDpiFixingForced() const; private slots: static QString sanitizePath(QString const& path); void inpDirBrowse(); void outDirBrowse(); void inpDirEdited(QString const& text); void outDirEdited(QString const& text); void addToProject(); void removeFromProject(); void onOK(); private: class Item; class FileList; class SortedFileList; class ItemVisualOrdering; void setInputDir(QString const& dir, bool auto_add_files = true); void setOutputDir(QString const& dir); void startLoadingMetadata(); virtual void timerEvent(QTimerEvent* event); void finishLoadingMetadata(); QSet m_supportedExtensions; std::auto_ptr m_ptrOffProjectFiles; std::auto_ptr m_ptrOffProjectFilesSorted; std::auto_ptr m_ptrInProjectFiles; std::auto_ptr m_ptrInProjectFilesSorted; int m_loadTimerId; bool m_metadataLoadFailed; bool m_autoOutDir; }; #endif scantailor-RELEASE_0_9_12_2/ProjectOpeningContext.cpp000066400000000000000000000047121271170121200224620ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ProjectOpeningContext.h" #include "ProjectOpeningContext.h.moc" #include "FixDpiDialog.h" #include "ProjectPages.h" #include #include #include #include #include #include #include ProjectOpeningContext::ProjectOpeningContext( QWidget* parent, QString const& project_file, QDomDocument const& doc) : m_projectFile(project_file), m_reader(doc), m_pParent(parent) { } ProjectOpeningContext::~ProjectOpeningContext() { // Deleting a null pointer is OK. delete m_ptrFixDpiDialog; } void ProjectOpeningContext::proceed() { if (!m_reader.success()) { deleteLater(); QMessageBox::warning( m_pParent, tr("Error"), tr("Unable to interpret the project file.") ); return; } if (m_reader.pages()->validateDpis()) { deleteLater(); emit done(this); return; } showFixDpiDialog(); } void ProjectOpeningContext::fixedDpiSubmitted() { m_reader.pages()->updateMetadataFrom(m_ptrFixDpiDialog->files()); emit done(this); } void ProjectOpeningContext::fixDpiDialogDestroyed() { 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(); } scantailor-RELEASE_0_9_12_2/ProjectOpeningContext.h000066400000000000000000000033431271170121200221260ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PROJECTOPENINGCONTEXT_H_ #define PROJECTOPENINGCONTEXT_H_ #include "NonCopyable.h" #include "ProjectReader.h" #include "ImageFileInfo.h" #include #include #include #include #include class FixDpiDialog; class QWidget; class QDomDocument; class ProjectOpeningContext : public QObject { Q_OBJECT DECLARE_NON_COPYABLE(ProjectOpeningContext) public: ProjectOpeningContext( QWidget* parent, QString const& project_file, QDomDocument const& doc); virtual ~ProjectOpeningContext(); void proceed(); QString const& projectFile() const { return m_projectFile; } ProjectReader* projectReader() { return &m_reader; } signals: void done(ProjectOpeningContext* context); private slots: void fixedDpiSubmitted(); void fixDpiDialogDestroyed(); private: void showFixDpiDialog(); QString m_projectFile; ProjectReader m_reader; QPointer m_ptrFixDpiDialog; QWidget* m_pParent; }; #endif scantailor-RELEASE_0_9_12_2/ProjectPages.cpp000066400000000000000000000417011271170121200205540ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ProjectPages.h" #include "ProjectPages.h.moc" #include "ImageFileInfo.h" #include "ImageMetadata.h" #include "ImageInfo.h" #include "OrthogonalRotation.h" #include "PageSequence.h" #include "RelinkablePath.h" #include "AbstractRelinker.h" #ifndef Q_MOC_RUN #include #include #include #include #include #endif #include #include #include #include #include #include #include #include ProjectPages::ProjectPages(Qt::LayoutDirection const layout_direction) { initSubPagesInOrder(layout_direction); } ProjectPages::ProjectPages( std::vector const& info, Qt::LayoutDirection const layout_direction) { initSubPagesInOrder(layout_direction); BOOST_FOREACH(ImageInfo const& 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( std::vector const& files, Pages const pages, Qt::LayoutDirection const layout_direction) { initSubPagesInOrder(layout_direction); BOOST_FOREACH(ImageFileInfo const& file, files) { QString const& file_path = file.fileInfo().absoluteFilePath(); std::vector const& images = file.imageInfo(); int const num_images = images.size(); int const multi_page_base = num_images > 1 ? 1 : 0; for (int i = 0; i < num_images; ++i) { ImageMetadata const& metadata = images[i]; ImageId const id(file_path, multi_page_base + i); m_images.push_back(ImageDesc(id, metadata, pages)); } } } ProjectPages::~ProjectPages() { } Qt::LayoutDirection ProjectPages::layoutDirection() const { if (m_subPagesInOrder[0] == PageId::LEFT_PAGE) { return Qt::LeftToRight; } else { assert(m_subPagesInOrder[0] == PageId::RIGHT_PAGE); return Qt::RightToLeft; } } void ProjectPages::initSubPagesInOrder(Qt::LayoutDirection const 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; } } PageSequence ProjectPages::toPageSequence(PageView const view) const { PageSequence pages; if (view == PAGE_VIEW) { QMutexLocker locker(&m_mutex); int const num_images = m_images.size(); for (int i = 0; i < num_images; ++i) { ImageDesc const& image = m_images[i]; assert(image.numLogicalPages >= 1 && image.numLogicalPages <= 2); for (int j = 0; j < image.numLogicalPages; ++j) { PageId const 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); int const num_images = m_images.size(); for (int i = 0; i < num_images; ++i) { ImageDesc const& image = m_images[i]; PageId const id(image.id, PageId::SINGLE_PAGE); pages.append( PageInfo( id, image.metadata, image.numLogicalPages, image.leftHalfRemoved, image.rightHalfRemoved ) ); } } return pages; } void ProjectPages::listRelinkablePaths(VirtualFunction1& 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; { QMutexLocker locker(&m_mutex); files.reserve(m_images.size()); BOOST_FOREACH(ImageDesc const& image, m_images) { files.push_back(image.id.filePath()); } } BOOST_FOREACH(QString const& file, files) { sink(RelinkablePath(file, RelinkablePath::File)); } } void ProjectPages::performRelinking(AbstractRelinker const& relinker) { QMutexLocker locker(&m_mutex); BOOST_FOREACH(ImageDesc& image, m_images) { RelinkablePath const old_path(image.id.filePath(), RelinkablePath::File); QString const new_path(relinker.substitutionPathFor(old_path)); image.id.setFilePath(new_path); } } void ProjectPages::setLayoutTypeFor(ImageId const& image_id, LayoutType const layout) { bool was_modified = false; { QMutexLocker locker(&m_mutex); setLayoutTypeForImpl(image_id, layout, &was_modified); } if (was_modified) { emit modified(); } } void ProjectPages::setLayoutTypeForAllPages(LayoutType const layout) { bool was_modified = false; { QMutexLocker locker(&m_mutex); setLayoutTypeForAllPagesImpl(layout, &was_modified); } if (was_modified) { emit modified(); } } void ProjectPages::autoSetLayoutTypeFor( ImageId const& image_id, OrthogonalRotation const rotation) { bool was_modified = false; { QMutexLocker locker(&m_mutex); autoSetLayoutTypeForImpl(image_id, rotation, &was_modified); } if (was_modified) { emit modified(); } } void ProjectPages::updateImageMetadata( ImageId const& image_id, ImageMetadata const& metadata) { bool was_modified = false; { QMutexLocker locker(&m_mutex); updateImageMetadataImpl(image_id, metadata, &was_modified); } if (was_modified) { emit modified(); } } int ProjectPages::adviseNumberOfLogicalPages( ImageMetadata const& metadata, OrthogonalRotation const rotation) { QSize const size(rotation.rotate(metadata.size())); QSize const 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); return m_images.size(); } std::vector ProjectPages::insertImage( ImageInfo const& new_image, BeforeOrAfter before_or_after, ImageId const& existing, PageView const view) { bool was_modified = false; { QMutexLocker locker(&m_mutex); return insertImageImpl( new_image, before_or_after, existing, view, was_modified ); } if (was_modified) { emit modified(); } } void ProjectPages::removePages(std::set const& pages) { bool was_modified = false; { QMutexLocker locker(&m_mutex); removePagesImpl(pages, was_modified); } if (was_modified) { emit modified(); } } PageInfo ProjectPages::unremovePage(PageId const& page_id) { bool was_modified = false; PageInfo page_info; { QMutexLocker locker(&m_mutex); page_info = unremovePageImpl(page_id, was_modified); } if (was_modified) { emit modified(); } return page_info; } bool ProjectPages::validateDpis() const { QMutexLocker locker(&m_mutex); BOOST_FOREACH(ImageDesc const& image, m_images) { if (!image.metadata.isDpiOK()) { return false; } } return true; } namespace { struct File { QString fileName; mutable std::vector metadata; File(QString const& fname) : fileName(fname) {} operator ImageFileInfo() const { return ImageFileInfo(fileName, metadata); } }; } // anonymous namespace std::vector ProjectPages::toImageFileInfo() const { using namespace boost::multi_index; multi_index_container< File, indexed_by< ordered_unique >, sequenced<> > > files; { QMutexLocker locker(&m_mutex); BOOST_FOREACH(ImageDesc const& image, m_images) { File const file(image.id.filePath()); files.insert(file).first->metadata.push_back(image.metadata); } } return std::vector(files.get<1>().begin(), files.get<1>().end()); } void ProjectPages::updateMetadataFrom(std::vector const& files) { typedef std::map MetadataMap; MetadataMap metadata_map; BOOST_FOREACH(ImageFileInfo const& file, files) { QString const file_path(file.fileInfo().absoluteFilePath()); int page = 0; BOOST_FOREACH(ImageMetadata const& metadata, file.imageInfo()) { metadata_map[ImageId(file_path, page)] = metadata; ++page; } } QMutexLocker locker(&m_mutex); BOOST_FOREACH(ImageDesc& image, m_images) { MetadataMap::const_iterator const it(metadata_map.find(image.id)); if (it != metadata_map.end()) { image.metadata = it->second; } } } void ProjectPages::setLayoutTypeForImpl( ImageId const& image_id, LayoutType const layout, bool* modified) { int const num_pages = (layout == TWO_PAGE_LAYOUT ? 2 : 1); int const num_images = 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; } int const delta = adjusted_num_pages - image.numLogicalPages; if (delta == 0) { break; } image.numLogicalPages = adjusted_num_pages; *modified = true; break; } } } void ProjectPages::setLayoutTypeForAllPagesImpl( LayoutType const layout, bool* modified) { int const num_pages = (layout == TWO_PAGE_LAYOUT ? 2 : 1); int const num_images = 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; } int const delta = adjusted_num_pages - image.numLogicalPages; if (delta == 0) { continue; } image.numLogicalPages = adjusted_num_pages; *modified = true; } } void ProjectPages::autoSetLayoutTypeForImpl( ImageId const& image_id, OrthogonalRotation const rotation, bool* modified) { int const num_images = 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; } int const delta = num_pages - image.numLogicalPages; if (delta == 0) { break; } image.numLogicalPages = num_pages; *modified = true; break; } } } void ProjectPages::updateImageMetadataImpl( ImageId const& image_id, ImageMetadata const& metadata, bool* modified) { int const num_images = 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( ImageInfo const& new_image, BeforeOrAfter before_or_after, ImageId const& existing, PageView const view, bool& modified) { std::vector logical_pages; std::vector::iterator it(m_images.begin()); std::vector::iterator const 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; } 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); } } return logical_pages; } void ProjectPages::removePagesImpl(std::set const& to_remove, bool& modified) { std::set::const_iterator const to_remove_end(to_remove.end()); std::vector new_images; new_images.reserve(m_images.size()); int new_total_logical_pages = 0; int const num_old_images = 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); } PageInfo ProjectPages::unremovePageImpl(PageId const& page_id, bool& modified) { if (page_id.subPage() == PageId::SINGLE_PAGE) { // These can't be unremoved. return PageInfo(); } std::vector::iterator it(m_images.begin()); std::vector::iterator const 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::ImageDesc ======================*/ ProjectPages::ImageDesc::ImageDesc(ImageInfo const& image_info) : id(image_info.id()), metadata(image_info.metadata()), numLogicalPages(image_info.numSubPages()), leftHalfRemoved(image_info.leftHalfRemoved()), rightHalfRemoved(image_info.rightHalfRemoved()) { } ProjectPages::ImageDesc::ImageDesc( ImageId const& id, ImageMetadata const& metadata, Pages const 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; } } PageId::SubPage ProjectPages::ImageDesc::logicalPageToSubPage( int const logical_page, PageId::SubPage const* 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; } } else { return sub_pages_in_order[logical_page]; } } scantailor-RELEASE_0_9_12_2/ProjectPages.h000066400000000000000000000130171271170121200202200ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PROJECT_PAGES_H_ #define PROJECT_PAGES_H_ #include "NonCopyable.h" #include "RefCountable.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 class ImageFileInfo; class ImageInfo; class OrthogonalRotation; class PageSequence; class RelinkablePath; class AbstractRelinker; class QDomElement; class ProjectPages : public QObject, public RefCountable { Q_OBJECT DECLARE_NON_COPYABLE(ProjectPages) public: enum Pages { ONE_PAGE, TWO_PAGES, AUTO_PAGES }; enum LayoutType { ONE_PAGE_LAYOUT, TWO_PAGE_LAYOUT }; ProjectPages(Qt::LayoutDirection layout_direction = Qt::LeftToRight); ProjectPages(std::vector const& images, Qt::LayoutDirection layout_direction); ProjectPages(std::vector const& files, Pages pages, Qt::LayoutDirection layout_direction); virtual ~ProjectPages(); Qt::LayoutDirection layoutDirection() const; PageSequence toPageSequence(PageView view) const; void listRelinkablePaths(VirtualFunction1& 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(AbstractRelinker const& relinker); void setLayoutTypeFor(ImageId const& image_id, LayoutType layout); void setLayoutTypeForAllPages(LayoutType layout); void autoSetLayoutTypeFor( ImageId const& image_id, OrthogonalRotation rotation); void updateImageMetadata( ImageId const& image_id, ImageMetadata const& metadata); static int adviseNumberOfLogicalPages( ImageMetadata const& metadata, OrthogonalRotation rotation); 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(ImageInfo const& new_image, BeforeOrAfter before_or_after, ImageId const& existing, PageView view); void removePages(std::set const& 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(PageId const& 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; std::vector toImageFileInfo() const; void updateMetadataFrom(std::vector const& files); signals: 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. ImageDesc(ImageInfo const& image_info); ImageDesc(ImageId const& id, ImageMetadata const& metadata, Pages pages); PageId::SubPage logicalPageToSubPage(int logical_page, PageId::SubPage const* sub_pages_in_order) const; }; void initSubPagesInOrder(Qt::LayoutDirection layout_direction); void setLayoutTypeForImpl( ImageId const& image_id, LayoutType layout, bool* modified); void setLayoutTypeForAllPagesImpl( LayoutType layout, bool* modified); void autoSetLayoutTypeForImpl( ImageId const& image_id, OrthogonalRotation rotation, bool* modified); void updateImageMetadataImpl( ImageId const& image_id, ImageMetadata const& metadata, bool* modified); std::vector insertImageImpl( ImageInfo const& new_image, BeforeOrAfter before_or_after, ImageId const& existing, PageView view, bool& modified); void removePagesImpl(std::set const& pages, bool& modified); PageInfo unremovePageImpl(PageId const& page_id, bool& modified); mutable QMutex m_mutex; std::vector m_images; PageId::SubPage m_subPagesInOrder[2]; }; #endif scantailor-RELEASE_0_9_12_2/ProjectReader.cpp000066400000000000000000000204451271170121200207210ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ProjectReader.h" #include "ProjectPages.h" #include "FileNameDisambiguator.h" #include "AbstractFilter.h" #include "XmlUnmarshaller.h" #include "Dpi.h" #include #include #include #include #ifndef Q_MOC_RUN #include #endif #include ProjectReader::ProjectReader(QDomDocument const& doc) : m_doc(doc), m_ptrDisambiguator(new FileNameDisambiguator) { QDomElement project_el(m_doc.documentElement()); m_outDir = project_el.attribute("outputDirectory"); Qt::LayoutDirection layout_direction = Qt::LeftToRight; if (project_el.attribute("layoutDirection") == "RTL") { layout_direction = Qt::RightToLeft; } QDomElement const dirs_el(project_el.namedItem("directories").toElement()); if (dirs_el.isNull()) { return; } processDirectories(dirs_el); QDomElement const files_el(project_el.namedItem("files").toElement()); if (files_el.isNull()) { return; } processFiles(files_el); QDomElement const images_el(project_el.namedItem("images").toElement()); if (images_el.isNull()) { return; } processImages(images_el, layout_direction); QDomElement const 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. QDomElement const disambig_el( project_el.namedItem("file-name-disambiguation").toElement() ); m_ptrDisambiguator.reset( new FileNameDisambiguator( disambig_el, boost::bind(&ProjectReader::expandFilePath, this, _1) ) ); } ProjectReader::~ProjectReader() { } void ProjectReader::readFilterSettings(std::vector const& filters) const { QDomElement project_el(m_doc.documentElement()); QDomElement filters_el(project_el.namedItem("filters").toElement()); std::vector::const_iterator it(filters.begin()); std::vector::const_iterator const end(filters.end()); for (; it != end; ++it) { (*it)->loadSettings(*this, filters_el); } } void ProjectReader::processDirectories(QDomElement const& dirs_el) { QString const 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; int const id = el.attribute("id").toInt(&ok); if (!ok) { continue; } QString const path(el.attribute("path")); if (path.isEmpty()) { continue; } m_dirMap.insert(DirMap::value_type(id, path)); } } void ProjectReader::processFiles(QDomElement const& files_el) { QString const 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; int const id = el.attribute("id").toInt(&ok); if (!ok) { continue; } int const dir_id = el.attribute("dirId").toInt(&ok); if (!ok) { continue; } QString const name(el.attribute("name")); if (name.isEmpty()) { continue; } QString const dir_path(getDirPath(dir_id)); if (dir_path.isEmpty()) { continue; } // Backwards compatibility. bool const compat_multi_page = (el.attribute("multiPage") == "1"); QString const file_path(QDir(dir_path).filePath(name)); FileRecord const rec(file_path, compat_multi_page); m_fileMap.insert(FileMap::value_type(id, rec)); } } void ProjectReader::processImages( QDomElement const& images_el, Qt::LayoutDirection const layout_direction) { QString const 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; int const id = el.attribute("id").toInt(&ok); if (!ok) { continue; } int const sub_pages = el.attribute("subPages").toInt(&ok); if (!ok) { continue; } int const file_id = el.attribute("fileId").toInt(&ok); if (!ok) { continue; } int const file_image = el.attribute("fileImage").toInt(&ok); if (!ok) { continue; } QString const removed(el.attribute("removed")); bool const left_half_removed = (removed == "L"); bool const right_half_removed = (removed == "R"); FileRecord const file_record(getFileRecord(file_id)); if (file_record.filePath.isEmpty()) { continue; } ImageId const image_id( file_record.filePath, file_image + int(file_record.compatMultiPage) ); ImageMetadata const metadata(processImageMetadata(el)); ImageInfo const 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_ptrPages.reset(new ProjectPages(images, layout_direction)); } } ImageMetadata ProjectReader::processImageMetadata(QDomElement const& image_el) { QSize size; Dpi dpi; QDomElement const size_el(image_el.namedItem("size").toElement()); if (!size_el.isNull()) { size = XmlUnmarshaller::size(size_el); } QDomElement const dpi_el(image_el.namedItem("dpi").toElement()); if (!dpi_el.isNull()) { dpi = XmlUnmarshaller::dpi(dpi_el); } return ImageMetadata(size, dpi); } void ProjectReader::processPages(QDomElement const& pages_el) { QString const 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; int const id = el.attribute("id").toInt(&ok); if (!ok) { continue; } int const image_id = el.attribute("imageId").toInt(&ok); if (!ok) { continue; } PageId::SubPage const sub_page = PageId::subPageFromString( el.attribute("subPage"), &ok ); if (!ok) { continue; } ImageInfo const image(getImageInfo(image_id)); if (image.id().filePath().isEmpty()) { continue; } PageId const 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); } } } QString ProjectReader::getDirPath(int const id) const { DirMap::const_iterator const it(m_dirMap.find(id)); if (it != m_dirMap.end()) { return it->second; } return QString(); } ProjectReader::FileRecord ProjectReader::getFileRecord(int id) const { FileMap::const_iterator const it(m_fileMap.find(id)); if (it != m_fileMap.end()) { return it->second; } return FileRecord(); } QString ProjectReader::expandFilePath(QString const& path_shorthand) const { bool ok = false; int const file_id = path_shorthand.toInt(&ok); if (!ok) { return QString(); } return getFileRecord(file_id).filePath; } ImageInfo ProjectReader::getImageInfo(int id) const { ImageMap::const_iterator it(m_imageMap.find(id)); if (it != m_imageMap.end()) { return it->second; } return ImageInfo(); } ImageId ProjectReader::imageId(int const numeric_id) const { ImageMap::const_iterator it(m_imageMap.find(numeric_id)); if (it != m_imageMap.end()) { return it->second.id(); } return ImageId(); } PageId ProjectReader::pageId(int numeric_id) const { PageMap::const_iterator it(m_pageMap.find(numeric_id)); if (it != m_pageMap.end()) { return it->second; } return PageId(); } scantailor-RELEASE_0_9_12_2/ProjectReader.h000066400000000000000000000060031271170121200203600ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PROJECTREADER_H_ #define PROJECTREADER_H_ #include "ImageId.h" #include "PageId.h" #include "ImageInfo.h" #include "ImageMetadata.h" #include "SelectedPage.h" #include "IntrusivePtr.h" #include #include #include #include #include class QDomElement; class ProjectData; class ProjectPages; class FileNameDisambiguator; class AbstractFilter; class ProjectReader { public: typedef IntrusivePtr FilterPtr; ProjectReader(QDomDocument const& doc); ~ProjectReader(); void readFilterSettings(std::vector const& filters) const; bool success() const { return m_ptrPages.get() != 0; } QString const& outputDirectory() const { return m_outDir; } IntrusivePtr const& pages() const { return m_ptrPages; } SelectedPage const& selectedPage() const { return m_selectedPage; } IntrusivePtr const& namingDisambiguator() const { return m_ptrDisambiguator; } ImageId imageId(int numeric_id) const; PageId pageId(int numeric_id) const; private: struct FileRecord { QString filePath; bool compatMultiPage; // Backwards compatibility. FileRecord() : compatMultiPage(false) {} FileRecord(QString const& file_path, bool compat_multi_page) : filePath(file_path), compatMultiPage(compat_multi_page) {} }; typedef std::map DirMap; typedef std::map FileMap; typedef std::map ImageMap; typedef std::map PageMap; void processDirectories(QDomElement const& dirs_el); void processFiles(QDomElement const& files_el); void processImages(QDomElement const& images_el, Qt::LayoutDirection layout_direction); ImageMetadata processImageMetadata(QDomElement const& image_el); void processPages(QDomElement const& pages_el); QString getDirPath(int id) const; FileRecord getFileRecord(int id) const; QString expandFilePath(QString const& path_shorthand) const; ImageInfo getImageInfo(int id) const; QDomDocument m_doc; QString m_outDir; DirMap m_dirMap; FileMap m_fileMap; ImageMap m_imageMap; PageMap m_pageMap; SelectedPage m_selectedPage; IntrusivePtr m_ptrPages; IntrusivePtr m_ptrDisambiguator; }; #endif scantailor-RELEASE_0_9_12_2/ProjectWriter.cpp000066400000000000000000000203161271170121200207700ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ProjectWriter.h" #include "ProjectPages.h" #include "PageView.h" #include "PageInfo.h" #include "PageId.h" #include "ImageId.h" #include "ImageMetadata.h" #include "AbstractFilter.h" #include "FileNameDisambiguator.h" #include "compat/boost_multi_index_foreach_fix.h" #include #include #include #include #ifndef Q_MOC_RUN #include #include #endif #include #include ProjectWriter::ProjectWriter( IntrusivePtr const& page_sequence, SelectedPage const& selected_page, OutputFileNameGenerator const& 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; size_t const num_pages = m_pageSequence.numPages(); for (size_t i = 0; i < num_pages; ++i) { PageInfo const& page = m_pageSequence.pageAt(i); PageId const& page_id = page.id(); ImageId const& image_id = page_id.imageId(); QString const& file_path = image_id.filePath(); QFileInfo const file_info(file_path); QString const 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; } } } ProjectWriter::~ProjectWriter() { } bool ProjectWriter::write(QString const& file_path, std::vector const& filters) const { QDomDocument doc; QDomElement root_el(doc.createElement("project")); doc.appendChild(root_el); 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); std::vector::const_iterator it(filters.begin()); std::vector::const_iterator const 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; } QDomElement ProjectWriter::processDirectories(QDomDocument& doc) const { QDomElement dirs_el(doc.createElement("directories")); BOOST_FOREACH(Directory const& 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; } QDomElement ProjectWriter::processFiles(QDomDocument& doc) const { QDomElement files_el(doc.createElement("files")); BOOST_FOREACH(File const& file, m_files.get()) { QFileInfo const file_info(file.path); QString const& 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")); BOOST_FOREACH(Image const& 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; } void ProjectWriter::writeImageMetadata( QDomDocument& doc, QDomElement& image_el, ImageId const& image_id) const { MetadataByImage::const_iterator it(m_metadataByImage.find(image_id)); assert(it != m_metadataByImage.end()); ImageMetadata const& 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")); PageId const sel_opt_1(m_selectedPage.get(IMAGE_VIEW)); PageId const sel_opt_2(m_selectedPage.get(PAGE_VIEW)); size_t const num_pages = m_pageSequence.numPages(); for (size_t i = 0; i < num_pages; ++i) { PageInfo const& page = m_pageSequence.pageAt(i); PageId const& 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_el.setAttribute("selected", "selected"); } pages_el.appendChild(page_el); } return pages_el; } int ProjectWriter::dirId(QString const& dir_path) const { Directories::const_iterator const it(m_dirs.find(dir_path)); assert(it != m_dirs.end()); return it->numericId; } int ProjectWriter::fileId(QString const& file_path) const { Files::const_iterator const it(m_files.find(file_path)); if (it != m_files.end()) { return it->numericId; } else { return -1; } } QString ProjectWriter::packFilePath(QString const& file_path) const { Files::const_iterator const it(m_files.find(file_path)); if (it != m_files.end()) { return QString::number(it->numericId); } else { return QString(); } } int ProjectWriter::imageId(ImageId const& image_id) const { Images::const_iterator const it(m_images.find(image_id)); assert(it != m_images.end()); return it->numericId; } int ProjectWriter::pageId(PageId const& page_id) const { Pages::const_iterator const it(m_pages.find(page_id)); assert(it != m_pages.end()); return it->numericId; } void ProjectWriter::enumImagesImpl(VirtualFunction2& out) const { BOOST_FOREACH(Image const& image, m_images.get()) { out(image.id, image.numericId); } } void ProjectWriter::enumPagesImpl(VirtualFunction2& out) const { BOOST_FOREACH(Page const& page, m_pages.get()) { out(page.id, page.numericId); } } /*======================== ProjectWriter::Image =========================*/ ProjectWriter::Image::Image(PageInfo const& page, int numeric_id) : id(page.imageId()), numericId(numeric_id), numSubPages(page.imageSubPages()), leftHalfRemoved(page.leftHalfRemoved()), rightHalfRemoved(page.rightHalfRemoved()) { } scantailor-RELEASE_0_9_12_2/ProjectWriter.h000066400000000000000000000122101271170121200204270ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PROJECTWRITER_H_ #define PROJECTWRITER_H_ #include "IntrusivePtr.h" #include "PageSequence.h" #include "OutputFileNameGenerator.h" #include "ImageId.h" #include "PageId.h" #include "SelectedPage.h" #include "VirtualFunction.h" #ifndef Q_MOC_RUN #include #include #include #include #endif #include #include #include #include class AbstractFilter; class ProjectPages; class PageInfo; class QDomDocument; class QDomElement; class ProjectWriter { DECLARE_NON_COPYABLE(ProjectWriter) public: typedef IntrusivePtr FilterPtr; ProjectWriter( IntrusivePtr const& page_sequence, SelectedPage const& selected_page, OutputFileNameGenerator const& out_file_name_gen); ~ProjectWriter(); bool write(QString const& file_path, std::vector const& 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(LogicalPageId, numeric_page_id) */ template void enumPages(OutFunc out) const; private: struct Directory { QString path; int numericId; Directory(QString const& path, int numeric_id) : path(path), numericId(numeric_id) {} }; struct File { QString path; int numericId; File(QString const& path, int numeric_id) : path(path), numericId(numeric_id) {} }; struct Image { ImageId id; int numericId; int numSubPages; bool leftHalfRemoved; bool rightHalfRemoved; Image(PageInfo const& page_info, int numeric_id); }; struct Page { PageId id; int numericId; Page(PageId const& id, int numeric_id) : id(id), numericId(numeric_id) {} }; class Sequenced; typedef std::map MetadataByImage; typedef boost::multi_index::multi_index_container< Directory, boost::multi_index::indexed_by< boost::multi_index::ordered_unique< boost::multi_index::member >, 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::member >, 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::member >, 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::member >, boost::multi_index::sequenced > > > Pages; QDomElement processDirectories(QDomDocument& doc) const; QDomElement processFiles(QDomDocument& doc) const; QDomElement processImages(QDomDocument& doc) const; QDomElement processPages(QDomDocument& doc) const; void writeImageMetadata( QDomDocument& doc, QDomElement& image_el, ImageId const& image_id) const; int dirId(QString const& dir_path) const; int fileId(QString const& file_path) const; QString packFilePath(QString const& file_path) const; int imageId(ImageId const& image_id) const; int pageId(PageId const& page_id) const; void enumImagesImpl(VirtualFunction2& out) const; void enumPagesImpl(VirtualFunction2& 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; }; template void ProjectWriter::enumImages(OutFunc out) const { ProxyFunction2 proxy(out); enumImagesImpl(proxy); } template void ProjectWriter::enumPages(OutFunc out) const { ProxyFunction2 proxy(out); enumPagesImpl(proxy); } #endif scantailor-RELEASE_0_9_12_2/RecentProjects.cpp000066400000000000000000000044411271170121200211200ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "RecentProjects.h" #ifndef Q_MOC_RUN #include #endif #include #include #include void RecentProjects::read() { QSettings settings; std::list new_list; int const size = settings.beginReadArray("project/recent"); for (int i = 0; i < size; ++i) { settings.setArrayIndex(i); QString const path(settings.value("path").toString()); new_list.push_back(path); } settings.endArray(); m_projectFiles.swap(new_list); } void RecentProjects::write(int const max_items) const { QSettings settings; settings.beginWriteArray("project/recent"); int idx = 0; BOOST_FOREACH(QString const& path, m_projectFiles) { if (idx >= max_items) { break; } settings.setArrayIndex(idx); settings.setValue("path", path); ++idx; } settings.endArray(); } bool RecentProjects::validate() { bool all_ok = true; std::list::iterator it(m_projectFiles.begin()); std::list::iterator const end(m_projectFiles.end()); while (it != end) { if (QFile::exists(*it)) { ++it; } else { m_projectFiles.erase(it++); all_ok = false; } } return all_ok; } void RecentProjects::setMostRecent(QString const& file_path) { std::list::iterator const begin(m_projectFiles.begin()); std::list::iterator const end(m_projectFiles.end()); std::list::iterator it(std::find(begin, end, file_path)); if (it != end) { m_projectFiles.splice(begin, m_projectFiles, it); } else { m_projectFiles.push_front(file_path); } } scantailor-RELEASE_0_9_12_2/RecentProjects.h000066400000000000000000000043211271170121200205620ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef RECENT_PROJECTS_H_ #define RECENT_PROJECTS_H_ #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(QString const& file_path); void write(int max_items = DEFAULT_MAX_ITEMS) const; bool isEmpty() const { return m_projectFiles.empty(); } /** * \brief Calls out((QString const&)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 void RecentProjects::enumerate(Out out, int max_items) const { std::list::const_iterator it(m_projectFiles.begin()); std::list::const_iterator const end(m_projectFiles.end()); for (; it != end && max_items > 0; ++it, --max_items) { out(*it); } } #endif scantailor-RELEASE_0_9_12_2/RelinkablePath.cpp000066400000000000000000000040701271170121200210510ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "RelinkablePath.h" #include #ifndef Q_MOC_RUN #include #endif RelinkablePath::RelinkablePath(QString const& path, Type type) : m_normalizedPath(normalize(path)) , m_type(type) { } QString RelinkablePath::normalize(QString const& path) { QString front_slashes(path); front_slashes.replace(QChar('\\'), QLatin1String("/")); QStringList new_components; BOOST_FOREACH(QString const& comp, front_slashes.split(QChar('/'), QString::KeepEmptyParts)) { if (comp.isEmpty()) { if (new_components.isEmpty() #if _WIN32 || (new_components.size() == 1 && new_components.front().isEmpty()) #endif ) { // This will create a leading slash or maybe two (for "\\host\share" type of paths). 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. } QString const& 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('/')); } scantailor-RELEASE_0_9_12_2/RelinkablePath.h000066400000000000000000000032171271170121200205200ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef RELINKABLE_PATH_H_ #define RELINKABLE_PATH_H_ #include /** * \brief Represents a file or directory. */ class RelinkablePath { // Member-wise copying is OK. public: enum Type { File, Dir }; RelinkablePath(QString const& path, Type type); QString const& 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(QString const& path); private: QString m_normalizedPath; Type m_type; }; #endif scantailor-RELEASE_0_9_12_2/RelinkablePathVisualization.cpp000066400000000000000000000220621271170121200236340ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "RelinkablePathVisualization.h" #include "RelinkablePathVisualization.h.moc" #include "RelinkablePath.h" #include "QtSignalForwarder.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef Q_MOC_RUN #include #include #endif #include 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(QString const& lbl, QString const& prefix_path, QString const& suffix_path, RelinkablePath::Type t) : label(lbl), prefixPath(prefix_path), suffixPath(suffix_path), type(t), exists(false) {} }; class RelinkablePathVisualization::ComponentButton : public QPushButton { public: ComponentButton(QWidget* parent = 0) : QPushButton(parent) {} protected: virtual void paintEvent(QPaintEvent* evt); }; RelinkablePathVisualization::RelinkablePathVisualization(QWidget* parent) : QWidget(parent) , m_pLayout(new QHBoxLayout(this)) { m_pLayout->setSpacing(0); m_pLayout->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; } } void RelinkablePathVisualization::setPath(RelinkablePath const& path, bool clickable) { clear(); 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; 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()); for (QStringList::const_iterator it(components.begin()); it != components.end(); ++it) { QString const& 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.push_back(PathComponent(component, prefix_path, suffix_path, RelinkablePath::Dir)); } // The last path component is either a file or a dir, while all the previous ones are dirs. path_components.back().type = path.type(); checkForExistence(path_components); int component_idx = -1; BOOST_FOREACH(PathComponent& path_component, path_components) { ++component_idx; ComponentButton* 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); new QtSignalForwarder( btn, SIGNAL(clicked()), boost::bind( &RelinkablePathVisualization::onClicked, this, component_idx, path_component.prefixPath, path_component.suffixPath, path_component.type ) ); } m_pLayout->addStretch(); } void RelinkablePathVisualization::stylePathComponentButton(QAbstractButton* btn, bool exists) { QColor const border_color(palette().color(QPalette::Window).darker(150)); QString style = "QAbstractButton {\n" " border: 2px solid " + border_color.name() + ";\n" " border-radius: 0.5em;\n" " padding: 0.2em;\n" " margin-left: 1px;\n" " margin-right: 1px;\n" " min-width: 2em;\n" " font-weight: bold;\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" " 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" "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 #bbb);\n" "}\n" "QAbstractButton:pressed {\n" " color: #333;\n" " background: qradialgradient(cx: 0.4, cy: -0.1, fx: 0.4, fy: -0.1, radius: 1.35, stop: 0 #fff, stop: 1 #ddd);\n" "}\n"; btn->setStyleSheet(style); } void RelinkablePathVisualization::paintEvent(QPaintEvent* evt) { int const 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; } 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) { bool const 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) { bool const highlight = false; if (widget->property("forceHighlight").toBool() != highlight) { widget->setProperty("forceHighlight", highlight); widget->update(); } } } break; } } } void RelinkablePathVisualization::onClicked( int component_idx, QString const& prefix_path, QString const& suffix_path, int type) { // 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); } } emit clicked(prefix_path, suffix_path, type); // Note that clear() or setPath() might have been called by a signal handler. int const 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); } } } 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; } if (QFile::exists(components.back().prefixPath)) { BOOST_FOREACH(PathComponent& comp, components) { comp.exists = true; } return; } int left = -1; // Existing component (unless -1). int right = components.size() - 1; // Non-existing component (we checked it above). while (right - left > 1) { int const mid = (left + right + 1) >> 1; if (QFile::exists(components[mid].prefixPath)) { left = mid; } else { right = mid; } } for (int i = components.size() - 1; i >= 0; --i) { components[i].exists = (i < right); } } /*============================ 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); } scantailor-RELEASE_0_9_12_2/RelinkablePathVisualization.h000066400000000000000000000033101271170121200232740ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef RELINKABLE_PATH_VISUALIZATION_H_ #define RELINKABLE_PATH_VISUALIZATION_H_ #include #include class RelinkablePath; class QHBoxLayout; class QAbstractButton; class RelinkablePathVisualization : public QWidget { Q_OBJECT public: RelinkablePathVisualization(QWidget* parent = 0); void clear(); void setPath(RelinkablePath const& path, bool clickable); signals: /** \p type is either RelinkablePath::File or RelinkablePath::Dir */ void clicked(QString const& prefix_path, QString const& suffix_path, int type); protected: virtual void paintEvent(QPaintEvent* evt); private: struct PathComponent; class ComponentButton; void onClicked(int component_idx, QString const& prefix_path, QString const& suffix_path, int type); void stylePathComponentButton(QAbstractButton* btn, bool exists); static void checkForExistence(std::vector& components); QHBoxLayout* m_pLayout; }; #endif scantailor-RELEASE_0_9_12_2/RelinkingDialog.cpp000066400000000000000000000116571271170121200212370ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "RelinkingDialog.h" #include "RelinkingDialog.h.moc" #include "RelinkingSortingModel.h" #include #include #include #ifndef Q_MOC_RUN #include #endif #include RelinkingDialog::RelinkingDialog(QString const& 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(QItemSelection const&, QItemSelection const&)), SLOT(selectionChanged(QItemSelection const&, QItemSelection const&)) ); connect( ui.pathVisualization, SIGNAL(clicked(QString const&, QString const&, int)), SLOT(pathButtonClicked(QString const&, QString const&, int)) ); connect(ui.undoButton, SIGNAL(clicked()), SLOT(undoButtonClicked())); disconnect(ui.buttonBox, SIGNAL(accepted())); connect(ui.buttonBox, SIGNAL(accepted()), SLOT(commitChanges())); } void RelinkingDialog::selectionChanged(QItemSelection const& selected, QItemSelection const& deselected) { if (selected.isEmpty()) { ui.pathVisualization->clear(); ui.pathVisualization->setVisible(false); } else { ui.undoButton->setVisible(false); QModelIndex const index(selected.front().topLeft()); QString const path(index.data(m_model.UncommittedPathRole).toString()); int const 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( QString const& prefix_path, QString const& suffix_path, int const 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) { QDir const 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(), 0, QFileDialog::DontUseNativeDialog ); } else { QDir const 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(); } void RelinkingDialog::undoButtonClicked() { m_model.rollbackChanges(); // Has to go before selectionChanged() selectionChanged(ui.listView->selectionModel()->selection(), QItemSelection()); } void RelinkingDialog::commitChanges() { m_model.commitChanges(); accept(); } scantailor-RELEASE_0_9_12_2/RelinkingDialog.h000066400000000000000000000041701271170121200206740ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef RELINKING_DIALOG_H_ #define RELINKING_DIALOG_H_ #include "ui_RelinkingDialog.h" #include "RelinkingModel.h" #include "RelinkablePath.h" #include "AbstractRelinker.h" #include "IntrusivePtr.h" #include class RelinkingSortingModel; class QAbstractButton; class QItemSelection; class QString; class RelinkingDialog : public QDialog { Q_OBJECT public: explicit RelinkingDialog(QString const& project_file_path, QWidget* parent = 0); VirtualFunction1& pathCollector() { return m_model; } /** * This method guarantees that * \code * dialog->relinker().get() == dialog->relinker().get() * \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. */ IntrusivePtr relinker() const { return m_model.relinker(); } private slots: void selectionChanged(QItemSelection const& selected, QItemSelection const& deselected); /** \p type is either RelinkablePath::File or RelinkablePath::Dir */ void pathButtonClicked(QString const& prefix_path, QString const& suffix_path, int type); void undoButtonClicked(); void commitChanges(); private: Ui::RelinkingDialog ui; RelinkingModel m_model; RelinkingSortingModel* m_pSortingModel; QString m_projectFileDir; }; #endif scantailor-RELEASE_0_9_12_2/RelinkingListView.cpp000066400000000000000000000123401271170121200215740ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "RelinkingListView.h" #include "RelinkingModel.h" #include #include #include #include #include #include #include #include #include #ifndef Q_MOC_RUN #include #endif #include class RelinkingListView::Delegate : public QStyledItemDelegate { public: Delegate(RelinkingListView* owner) : QStyledItemDelegate(owner), m_pOwner(owner) {} virtual void paint(QPainter* painter, QStyleOptionViewItem const& option, QModelIndex const& index) const { m_pOwner->maybeDrawStatusLayer(painter, index, option.rect); QStyledItemDelegate::paint(painter, option, index); } private: RelinkingListView* m_pOwner; }; class RelinkingListView::IndicationGroup { public: QRect rect; int status; IndicationGroup(QRect const& r, int st) : rect(r), status(st) {} }; class RelinkingListView::GroupAggregator { public: void process(QRect const& rect, int status); std::vector const& groups() const { return m_groups; } private: std::vector m_groups; }; RelinkingListView::RelinkingListView(QWidget* parent) : QListView(parent) , m_statusLayerDrawn(false) { setItemDelegate(new Delegate(this)); } void RelinkingListView::paintEvent(QPaintEvent* e) { m_statusLayerDrawn = false; QListView::paintEvent(e); } void RelinkingListView::maybeDrawStatusLayer( QPainter* painter, QModelIndex const& item_index, QRect const& 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; } void RelinkingListView::drawStatusLayer(QPainter* painter) { QRect const 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; int const rows = top_index.model()->rowCount(top_index.parent()); for (int row = top_index.row(); row < rows; ++row) { QModelIndex const index(top_index.sibling(row, 0)); QRect const 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()); int const 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); BOOST_FOREACH(IndicationGroup const& group, group_aggregator.groups()) { if (group.status == status) { qreal const 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)); } } void RelinkingListView::GroupAggregator::process(QRect const& rect, int status) { if (m_groups.empty() || m_groups.back().status != status) { m_groups.push_back(IndicationGroup(rect, status)); } else { m_groups.back().rect |= rect; } } scantailor-RELEASE_0_9_12_2/RelinkingListView.h000066400000000000000000000025021271170121200212400ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef RELINKING_LIST_VIEW_H_ #define RELINKING_LIST_VIEW_H_ #include class QPainter; class QRect; class QModelIndex; class RelinkingListView : public QListView { public: RelinkingListView(QWidget* parent = 0); protected: virtual void paintEvent(QPaintEvent* e); private: class Delegate; class IndicationGroup; class GroupAggregator; void maybeDrawStatusLayer(QPainter* painter, QModelIndex const& item_index, QRect const& item_paint_rect); void drawStatusLayer(QPainter* painter); bool m_statusLayerDrawn; }; #endif scantailor-RELEASE_0_9_12_2/RelinkingModel.cpp000066400000000000000000000276231271170121200211000ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "RelinkingModel.h" #include "PayloadEvent.h" #include "OutOfMemoryHandler.h" #include #include #include #include #include #include #include #include #ifndef Q_MOC_RUN #include #include #include #include #include #endif #include #include #include #include class RelinkingModel::StatusUpdateResponse { public: StatusUpdateResponse(QString const& path, int row, Status status) : m_path(path), m_row(row), m_status(status) {} QString const& path() const { return m_path; } int row() const { return m_row; } Status status() const { return m_status; } private: QString m_path; int m_row; Status m_status; }; class RelinkingModel::StatusUpdateThread : private QThread { public: StatusUpdateThread(RelinkingModel* owner); /** This will signal the thread to stop and wait for it to happen. */ virtual ~StatusUpdateThread(); /** * 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(QString const& path, int row); private: struct Task { QString path; int row; Task(QString const& 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::tag, boost::multi_index::member >, boost::multi_index::sequenced< boost::multi_index::tag > > > TaskList; typedef TaskList::index::type TasksByPath; typedef TaskList::index::type TasksByPriority; virtual void run(); RelinkingModel* m_pOwner; TaskList m_tasks; TasksByPath& m_rTasksByPath; TasksByPriority& m_rTasksByPriority; 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) { } RelinkingModel::~RelinkingModel() { } int RelinkingModel::rowCount(QModelIndex const& parent) const { if (!parent.isValid()) { return m_items.size(); } else { return 0; } } QVariant RelinkingModel::data(QModelIndex const& index, int role) const { Item const& 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); } return QVariant(); } void RelinkingModel::addPath(RelinkablePath const& path) { QString const normalized_path(path.normalizedPath()); std::pair::iterator, bool> const ins( m_origPathSet.insert(path.normalizedPath()) ); if (!ins.second) { // Not inserted because identical path is already there. return; } beginInsertRows(QModelIndex(), m_items.size(), m_items.size()); m_items.push_back(Item(path)); endInsertRows(); requestStatusUpdate(index(m_items.size() - 1)); } void RelinkingModel::replacePrefix( QString const& prefix, QString const& replacement, RelinkablePath::Type type) { QString slash_terminated_prefix(prefix); ensureEndsWithSlash(slash_terminated_prefix); int modified_rowspan_begin = -1; int row = -1; BOOST_FOREACH(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)) { int const 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; } } } if (modified_rowspan_begin != -1) { emit dataChanged(index(modified_rowspan_begin), index(row)); } } bool RelinkingModel::checkForMerges() const { std::vector new_paths; new_paths.reserve(m_items.size()); BOOST_FOREACH(Item const& item, m_items) { new_paths.push_back(item.uncommittedPath); } std::sort(new_paths.begin(), 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(*m_ptrRelinker); int modified_rowspan_begin = -1; int row = -1; BOOST_FOREACH(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; } void RelinkingModel::rollbackChanges() { if (!m_haveUncommittedChanges) { return; } int modified_rowspan_begin = -1; int row = -1; BOOST_FOREACH(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; } void RelinkingModel::ensureEndsWithSlash(QString& str) { if (!str.endsWith(QChar('/'))) { str += QChar('/'); } } void RelinkingModel::requestStatusUpdate(QModelIndex const& index) { assert(index.isValid()); Item& item = m_items[index.row()]; item.uncommittedStatus = StatusUpdatePending; m_ptrStatusUpdateThread->requestStatusUpdate(item.uncommittedPath, index.row()); } void RelinkingModel::customEvent(QEvent* event) { typedef PayloadEvent ResponseEvent; ResponseEvent* evt = dynamic_cast(event); assert(evt); StatusUpdateResponse const& 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) { } RelinkingModel::StatusUpdateThread::~StatusUpdateThread() { { QMutexLocker locker(&m_mutex); m_exiting = true; } m_cond.wakeAll(); wait(); } void RelinkingModel::StatusUpdateThread::requestStatusUpdate(QString const& path, int row) { QMutexLocker const locker(&m_mutex); if (m_exiting) { return; } if (path == m_pathBeingProcessed) { // This task is currently in progress. return; } std::pair const ins( m_rTasksByPath.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) ); if (!isRunning()) { start(); } m_cond.wakeOne(); } void RelinkingModel::StatusUpdateThread::run() try { QMutexLocker const locker(&m_mutex); class MutexUnlocker { public: MutexUnlocker(QMutex* mutex) : m_pMutex(mutex) { mutex->unlock(); } ~MutexUnlocker() { m_pMutex->lock(); } private: QMutex* const m_pMutex; }; for (;;) { if (m_exiting) { break; } if (m_tasks.empty()) { m_cond.wait(&m_mutex); } if (m_tasks.empty()) { continue; } Task const task(m_rTasksByPriority.front()); m_pathBeingProcessed = task.path; m_rTasksByPriority.pop_front(); { MutexUnlocker const unlocker(&m_mutex); bool const exists = QFile::exists(task.path); StatusUpdateResponse const response(task.path, task.row, exists ? Exists : Missing); QCoreApplication::postEvent(m_pOwner, new PayloadEvent(response)); } m_pathBeingProcessed.clear(); } } catch (std::bad_alloc const&) { OutOfMemoryHandler::instance().handleOutOfMemorySituation(); } /*================================ Item =================================*/ RelinkingModel::Item::Item(RelinkablePath const& path) : origPath(path.normalizedPath()) , committedPath(path.normalizedPath()) , uncommittedPath(path.normalizedPath()) , type(path.type()) , committedStatus(StatusUpdatePending) , uncommittedStatus(StatusUpdatePending) { } /*============================== Relinker ================================*/ void RelinkingModel::Relinker::addMapping(QString const& from, QString const& to) { m_mappings[from] = to; } QString RelinkingModel::Relinker::substitutionPathFor(RelinkablePath const& path) const { std::map::const_iterator const 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); } scantailor-RELEASE_0_9_12_2/RelinkingModel.h000066400000000000000000000073241271170121200205410ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef RELINKING_MODEL_H_ #define RELINKING_MODEL_H_ #include "NonCopyable.h" #include "VirtualFunction.h" #include "RelinkablePath.h" #include "AbstractRelinker.h" #include "IntrusivePtr.h" #include #include #include #include #include #include #include #include #include #include class RelinkingModel : public QAbstractListModel, public VirtualFunction1 { DECLARE_NON_COPYABLE(RelinkingModel) public: enum Status { Exists, Missing, StatusUpdatePending }; enum { TypeRole = Qt::UserRole, UncommittedPathRole, UncommittedStatusRole }; RelinkingModel(); virtual ~RelinkingModel(); virtual int rowCount(QModelIndex const& parent = QModelIndex()) const; virtual QVariant data(QModelIndex const& index, int role = Qt::DisplayRole) const; /** * This method guarantees that * \code * model.relinker().get() == model.relinker().get() * \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. */ IntrusivePtr relinker() const { return m_ptrRelinker; } virtual void operator()(RelinkablePath const& path) { addPath(path); } void addPath(RelinkablePath const& path); void replacePrefix(QString const& prefix, QString const& 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; void commitChanges(); void rollbackChanges(); void requestStatusUpdate(QModelIndex const& index); protected: virtual void customEvent(QEvent* event); private: class StatusUpdateThread; class StatusUpdateResponse; /** 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; /**< Same as committedPath when m_haveUncommittedChanges == false. */ RelinkablePath::Type type; Status committedStatus; Status uncommittedStatus; /**< Same as committedStatus when m_haveUncommittedChanges == false. */ Item(RelinkablePath const& path); }; class Relinker : public AbstractRelinker { public: void addMapping(QString const& from, QString const& to); /** Returns path.normalizedPath() if there is no mapping for it. */ virtual QString substitutionPathFor(RelinkablePath const& path) const; void swap(Relinker& other); private: std::map m_mappings; }; static void ensureEndsWithSlash(QString& str); QPixmap m_fileIcon; QPixmap m_folderIcon; std::vector m_items; std::set m_origPathSet; IntrusivePtr const m_ptrRelinker; std::auto_ptr m_ptrStatusUpdateThread; bool m_haveUncommittedChanges; }; #endif scantailor-RELEASE_0_9_12_2/RelinkingSortingModel.cpp000066400000000000000000000046661271170121200224500ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "RelinkingSortingModel.h" #include "RelinkingModel.h" #include "RelinkablePath.h" #include #include RelinkingSortingModel::RelinkingSortingModel(QObject* parent) : QSortFilterProxyModel(parent) { setDynamicSortFilter(true); sort(0); } bool RelinkingSortingModel::lessThan(QModelIndex const& left, QModelIndex const& right) const { int const left_status = left.data(RelinkingModel::UncommittedStatusRole).toInt(); int const 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"); } int const left_type = left.data(RelinkingModel::TypeRole).toInt(); int const right_type = right.data(RelinkingModel::TypeRole).toInt(); QString const left_path(left.data(RelinkingModel::UncommittedPathRole).toString()); QString const right_path(right.data(RelinkingModel::UncommittedPathRole).toString()); if (left_type != right_type) { // Directories go above their siblings. QString const left_dir(left_path.left(left_path.lastIndexOf(QChar('/')))); QString const 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; } scantailor-RELEASE_0_9_12_2/RelinkingSortingModel.h000066400000000000000000000021411271170121200220770ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef RELINKING_SORTING_MODEL_H_ #define RELINKING_SORTING_MODEL_H_ #include class RelinkingSortingModel : public QSortFilterProxyModel { public: RelinkingSortingModel(QObject* parent = 0); protected: virtual bool lessThan(QModelIndex const& left, QModelIndex const& right) const; }; #endif scantailor-RELEASE_0_9_12_2/SelectedPage.cpp000066400000000000000000000023331271170121200205110ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "SelectedPage.h" SelectedPage::SelectedPage(PageId const& page_id, PageView view) { set(page_id, view); } void SelectedPage::set(PageId const& page_id, PageView view) { 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); } } scantailor-RELEASE_0_9_12_2/SelectedPage.h000066400000000000000000000033061271170121200201570ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SELECTED_PAGE_H_ #define SELECTED_PAGE_H_ #include "PageId.h" #include "PageView.h" /** * The whole point of this class can be demonstrated with a few lines of code: * \code * ImageId image_id = ...; * SelectedPage page; * page.set(PageId(image_id, PageId::RIGHT_PAGE), PAGE_VIEW); * page.set(PageId(image_id, PageId::SINGLE_PAGE), IMAGE_VIEW); * page.get(PAGE_VIEW); // Returns a RIGHT_PAGE PageId. * \endcode * As seen above, this class remembers the sub-page as long as image id * stays the same. Note that set(..., PAGE_VIEW) will always overwrite * the sub-page, while get(IMAGE_VIEW) will always return SINGLE_PAGE sub-pages. */ class SelectedPage { public: SelectedPage() {} SelectedPage(PageId const& page_id, PageView view); bool isNull() const { return m_pageId.isNull(); } void set(PageId const& page_id, PageView view); PageId get(PageView view) const; private: PageId m_pageId; }; #endif scantailor-RELEASE_0_9_12_2/SettingsDialog.cpp000066400000000000000000000035351271170121200211110ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "SettingsDialog.h" #include "SettingsDialog.h.moc" #include "OpenGLSupport.h" #include "config.h" #include #include SettingsDialog::SettingsDialog(QWidget* parent) : QDialog(parent) { ui.setupUi(this); QSettings settings; #ifndef ENABLE_OPENGL ui.use3DAcceleration->setChecked(false); ui.use3DAcceleration->setEnabled(false); ui.use3DAcceleration->setToolTip(tr("Compiled without OpenGL support.")); #else if (!OpenGLSupport::supported()) { ui.use3DAcceleration->setChecked(false); ui.use3DAcceleration->setEnabled(false); ui.use3DAcceleration->setToolTip(tr("Your hardware / driver don't provide the necessary features.")); } else { ui.use3DAcceleration->setChecked( settings.value("settings/use_3d_acceleration", false).toBool() ); } #endif connect(ui.buttonBox, SIGNAL(accepted()), SLOT(commitChanges())); } SettingsDialog::~SettingsDialog() { } void SettingsDialog::commitChanges() { QSettings settings; #ifdef ENABLE_OPENGL settings.setValue("settings/use_3d_acceleration", ui.use3DAcceleration->isChecked()); #endif } scantailor-RELEASE_0_9_12_2/SettingsDialog.h000066400000000000000000000021271271170121200205520ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SETTINGS_DIALOG_H_ #define SETTINGS_DIALOG_H_ #include "ui_SettingsDialog.h" #include class SettingsDialog : public QDialog { Q_OBJECT public: SettingsDialog(QWidget* parent = 0); virtual ~SettingsDialog(); private slots: void commitChanges(); private: Ui::SettingsDialog ui; }; #endif scantailor-RELEASE_0_9_12_2/SkinnedButton.cpp000066400000000000000000000044421271170121200207560ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "SkinnedButton.h" #include SkinnedButton::SkinnedButton(QString const& file, QWidget* parent) : QToolButton(parent), m_normalStatePixmap(file), m_normalStateFile(file) { updateStyleSheet(); } SkinnedButton::SkinnedButton( QString const& normal_state_file, QString const& hover_state_file, QString const& 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(); } void SkinnedButton::setHoverImage(QString const& file) { m_hoverStateFile = file; updateStyleSheet(); } void SkinnedButton::setPressedImage(QString const& file) { m_pressedStateFile = file; updateStyleSheet(); } void SkinnedButton::setMask() { setMask(m_normalStatePixmap.mask()); } QSize SkinnedButton::sizeHint() const { 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); 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); } setStyleSheet(style); } scantailor-RELEASE_0_9_12_2/SkinnedButton.h000066400000000000000000000065571271170121200204340ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SKINNEDBUTTON_H_ #define SKINNEDBUTTON_H_ #include #include #include /** * \brief A button represented by a set of images. * * The button won't have any borders or effects other than * those represented by set of 3 images: * \li The normal state image. * \li The hover state image. * \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. */ SkinnedButton(QString const& file, QWidget* parent = 0); /** * \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( QString const& normal_state_file, QString const& hover_state_file, QString const& pressed_state_file, QWidget* parent = 0); /** * \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(QString const& 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(QString const& 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. */ virtual QSize sizeHint() const; private: void updateStyleSheet(); QPixmap m_normalStatePixmap; QString m_normalStateFile; QString m_hoverStateFile; QString m_pressedStateFile; }; #endif scantailor-RELEASE_0_9_12_2/SmartFilenameOrdering.cpp000066400000000000000000000046761271170121200224210ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "SmartFilenameOrdering.h" #include #include #include bool SmartFilenameOrdering::operator()(QFileInfo const& lhs, QFileInfo const& rhs) const { // First compare directories. if (int comp = lhs.absolutePath().compare(rhs.absolutePath())) { return comp < 0; } QString const lhs_fname(lhs.fileName()); QString const rhs_fname(rhs.fileName()); QChar const* lhs_ptr = lhs_fname.constData(); QChar const* rhs_ptr = rhs_fname.constData(); while (!lhs_ptr->isNull() && !rhs_ptr->isNull()) { bool const lhs_is_digit = lhs_ptr->isDigit(); bool const 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 < *rhs_ptr; } ++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; } scantailor-RELEASE_0_9_12_2/SmartFilenameOrdering.h000066400000000000000000000026351271170121200220570ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SMARTFILENAMEORDERING_H_ #define SMARTFILENAMEORDERING_H_ class QFileInfo; class SmartFilenameOrdering { public: SmartFilenameOrdering() {} /** * \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()(QFileInfo const& lhs, QFileInfo const& rhs) const; }; #endif scantailor-RELEASE_0_9_12_2/StageListView.cpp000066400000000000000000000274771271170121200207360ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "StageListView.h" #include "StageListView.h.moc" #include "StageSequence.h" #include "ChangedStateItemDelegate.h" #include "SkinnedButton.h" #include "BubbleAnimation.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef Q_MOC_RUN #include #endif #include #include #include class StageListView::Model : public QAbstractTableModel { public: Model(QObject* parent, IntrusivePtr const& stages); void updateBatchProcessingAnimation( int selected_row, QPixmap const& animation_frame); void disableBatchProcessingAnimation(); virtual int columnCount(QModelIndex const& parent) const; virtual int rowCount(QModelIndex const& parent) const; virtual QVariant data(QModelIndex const& index, int role) const; private: IntrusivePtr m_ptrStages; QPixmap m_curAnimationFrame; int m_curSelectedRow; }; class StageListView::LeftColDelegate : public ChangedStateItemDelegate { public: LeftColDelegate(StageListView* view); virtual void paint( QPainter* painter, QStyleOptionViewItem const& option, QModelIndex const& index) const; private: typedef ChangedStateItemDelegate SuperClass; StageListView* m_pView; }; class StageListView::RightColDelegate : public ChangedStateItemDelegate { public: RightColDelegate(QObject* parent = 0); virtual void paint( QPainter* painter, QStyleOptionViewItem const& option, QModelIndex const& index) const; private: typedef ChangedStateItemDelegate SuperClass; }; StageListView::StageListView(QWidget* parent) : QTableView(parent), m_sizeHint(QTableView::sizeHint()), m_pModel(0), 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->setResizeMode(QHeaderView::Stretch); h_header->hide(); QHeaderView* v_header = verticalHeader(); v_header->setResizeMode(QHeaderView::ResizeToContents); v_header->setMovable(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 ); } StageListView::~StageListView() { } void StageListView::setStages(IntrusivePtr const& 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->setResizeMode(0, QHeaderView::Stretch); h_header->setResizeMode(1, QHeaderView::Fixed); if (v_header->count() != 0) { // Make the cells in the last column square. int const square_side = v_header->sectionSize(0); h_header->resizeSection(1, square_side); int const 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(); } void StageListView::setBatchProcessingPossible(bool const possible) { if (m_batchProcessingPossible == possible) { return; } m_batchProcessingPossible = possible; if (possible) { placeLaunchButton(selectedRow()); } else { removeLaunchButton(selectedRow()); } } void StageListView::setBatchProcessingInProgress(bool const 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; } } void StageListView::timerEvent(QTimerEvent* event) { if (event->timerId() != m_timerId) { QTableView::timerEvent(event); return; } initiateBatchAnimationFrameRendering(); } void StageListView::initiateBatchAnimationFrameRendering() { if (!m_pModel || !m_batchProcessingInProgress) { return; } int const 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; } } void StageListView::selectionChanged( QItemSelection const& selected, QItemSelection const& deselected) { // Call the base version. QTableView::selectionChanged(selected, deselected); if (!deselected.isEmpty()) { removeLaunchButton(deselected.front().topLeft().row()); } if (!selected.isEmpty()) { placeLaunchButton(selected.front().topLeft().row()); } } void StageListView::ensureSelectedRowVisible() { // This loop won't run more than one iteration. BOOST_FOREACH(QModelIndex const& idx, selectionModel()->selectedRows(0)) { scrollTo(idx, EnsureVisible); } } void StageListView::removeLaunchButton(int const row) { if (row == -1) { return; } m_pLaunchBtn->hide(); } void StageListView::placeLaunchButton(int row) { if (row == -1) { return; } QModelIndex const idx(m_pModel->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()); m_pLaunchBtn->setGeometry(button_geometry); m_pLaunchBtn->show(); } void StageListView::createBatchAnimationSequence(int const square_side) { int const num_frames = 8; m_batchAnimationPixmaps.resize(num_frames); QColor const head_color(palette().color(QPalette::Window).darker(200)); QColor const tail_color(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; } int const count = m_pModel->rowCount(QModelIndex()); for (int i = 0; i < count; ++i) { setSpan(i, 0, 1, m_batchProcessingInProgress ? 1 : 2); } } int StageListView::selectedRow() const { QModelIndexList const selection(selectionModel()->selectedRows(0)); if (selection.empty()) { return - 1; } return selection.front().row(); } /*========================= StageListView::Model ======================*/ StageListView::Model::Model( QObject* parent, IntrusivePtr const& stages) : QAbstractTableModel(parent), m_ptrStages(stages), m_curSelectedRow(0) { assert(m_ptrStages.get()); } void StageListView::Model::updateBatchProcessingAnimation( int const selected_row, QPixmap const& animation_frame) { int const 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)); } int StageListView::Model::columnCount(QModelIndex const& parent) const { return 2; } int StageListView::Model::rowCount(QModelIndex const& parent) const { return m_ptrStages->count(); } QVariant StageListView::Model::data(QModelIndex const& index, int const role) const { if (role == Qt::DisplayRole) { if (index.column() == 0) { return m_ptrStages->filterAt(index.row())->getName(); } } if (role == Qt::UserRole) { if (index.column() == 1) { if (index.row() <= m_curSelectedRow) { return m_curAnimationFrame; } } } return QVariant(); } /*================= StageListView::LeftColDelegate ===================*/ StageListView::LeftColDelegate::LeftColDelegate(StageListView* view) : SuperClass(view), m_pView(view) { } void StageListView::LeftColDelegate::paint( QPainter* painter, QStyleOptionViewItem const& option, QModelIndex const& index) const { 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); } } /*================= StageListView::RightColDelegate ===================*/ StageListView::RightColDelegate::RightColDelegate(QObject* parent) : SuperClass(parent) { } void StageListView::RightColDelegate::paint( QPainter* painter, QStyleOptionViewItem const& option, QModelIndex const& index) const { SuperClass::paint(painter, option, index); QVariant const var(index.data(Qt::UserRole)); if (!var.isNull()) { QPixmap const pixmap(var.value()); if (!pixmap.isNull()) { QRect r(pixmap.rect()); r.moveCenter(option.rect.center()); painter->drawPixmap(r, pixmap); } } } scantailor-RELEASE_0_9_12_2/StageListView.h000066400000000000000000000042301271170121200203610ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef STAGELISTVIEW_H_ #define STAGELISTVIEW_H_ #include "IntrusivePtr.h" #include #include #include class StageSequence; class StageListView : public QTableView { Q_OBJECT public: StageListView(QWidget* parent); virtual ~StageListView(); void setStages(IntrusivePtr const& stages); virtual QSize sizeHint() const { return m_sizeHint; } signals: void launchBatchProcessing(); public slots: void setBatchProcessingPossible(bool possible); void setBatchProcessingInProgress(bool in_progress); protected slots: virtual void selectionChanged( QItemSelection const& selected, QItemSelection const& deselected); private slots: void ensureSelectedRowVisible(); protected: virtual void timerEvent(QTimerEvent* event); private: class Model; class LeftColDelegate; class RightColDelegate; void removeLaunchButton(int row); void placeLaunchButton(int row); void initiateBatchAnimationFrameRendering(); void createBatchAnimationSequence(int square_side); void updateRowSpans(); 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; }; #endif scantailor-RELEASE_0_9_12_2/StageSequence.cpp000066400000000000000000000044611271170121200207240ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "StageSequence.h" #include "ProjectPages.h" #include StageSequence::StageSequence(IntrusivePtr const& pages, PageSelectionAccessor const& 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 = m_filters.size(); m_filters.push_back(m_ptrFixOrientationFilter); m_pageSplitFilterIdx = m_filters.size(); m_filters.push_back(m_ptrPageSplitFilter); m_deskewFilterIdx = m_filters.size(); m_filters.push_back(m_ptrDeskewFilter); m_selectContentFilterIdx = m_filters.size(); m_filters.push_back(m_ptrSelectContentFilter); m_pageLayoutFilterIdx = m_filters.size(); m_filters.push_back(m_ptrPageLayoutFilter); m_outputFilterIdx = m_filters.size(); m_filters.push_back(m_ptrOutputFilter); } void StageSequence::performRelinking(AbstractRelinker const& relinker) { BOOST_FOREACH(FilterPtr& filter, m_filters) { filter->performRelinking(relinker); } } int StageSequence::findFilter(FilterPtr const& filter) const { int idx = 0; BOOST_FOREACH(FilterPtr const& f, m_filters) { if (f == filter) { return idx; } ++idx; } return -1; } scantailor-RELEASE_0_9_12_2/StageSequence.h000066400000000000000000000065751271170121200204010ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef STAGESEQUENCE_H_ #define STAGESEQUENCE_H_ #include "NonCopyable.h" #include "RefCountable.h" #include "IntrusivePtr.h" #include "AbstractFilter.h" #include "filters/fix_orientation/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 class PageId; class ProjectPages; class PageSelectionAccessor; class AbstractRelinker; class StageSequence : public RefCountable { DECLARE_NON_COPYABLE(StageSequence) public: typedef IntrusivePtr FilterPtr; StageSequence(IntrusivePtr const& pages, PageSelectionAccessor const& page_selection_accessor); void performRelinking(AbstractRelinker const& relinker); std::vector const& filters() const { return m_filters; } int count() const { return m_filters.size(); } FilterPtr const& filterAt(int idx) const { return m_filters[idx]; } int findFilter(FilterPtr const& filter) const; IntrusivePtr const& fixOrientationFilter() const { return m_ptrFixOrientationFilter; } IntrusivePtr const& pageSplitFilter() const { return m_ptrPageSplitFilter; } IntrusivePtr const& deskewFilter() const { return m_ptrDeskewFilter; } IntrusivePtr const& selectContentFilter() const { return m_ptrSelectContentFilter; } IntrusivePtr const& pageLayoutFilter() const { return m_ptrPageLayoutFilter; } IntrusivePtr const& 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: IntrusivePtr m_ptrFixOrientationFilter; IntrusivePtr m_ptrPageSplitFilter; IntrusivePtr m_ptrDeskewFilter; IntrusivePtr m_ptrSelectContentFilter; IntrusivePtr m_ptrPageLayoutFilter; IntrusivePtr m_ptrOutputFilter; std::vector m_filters; int m_fixOrientationFilterIdx; int m_pageSplitFilterIdx; int m_deskewFilterIdx; int m_selectContentFilterIdx; int m_pageLayoutFilterIdx; int m_outputFilterIdx; }; #endif scantailor-RELEASE_0_9_12_2/SystemLoadWidget.cpp000066400000000000000000000056311271170121200214200ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "SystemLoadWidget.h" #include "SystemLoadWidget.h.moc" #include "ThreadPriority.h" #include SystemLoadWidget::SystemLoadWidget(QWidget* parent) : QWidget(parent) { ui.setupUi(this); ThreadPriority const prio(ThreadPriority::load("settings/batch_processing_priority")); ui.slider->setRange(ThreadPriority::Minimum, ThreadPriority::Maximum); ui.slider->setValue(prio.value()); 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(decreasePriority())); connect(ui.plusBtn, SIGNAL(clicked()), SLOT(increasePriority())); } void SystemLoadWidget::sliderPressed() { showHideToolTip(ui.slider->value()); } void SystemLoadWidget::sliderMoved(int prio) { showHideToolTip(prio); } void SystemLoadWidget::valueChanged(int prio) { ThreadPriority((ThreadPriority::Priority)prio).save("settings/batch_processing_priority"); } void SystemLoadWidget::decreasePriority() { ui.slider->setValue(ui.slider->value() - 1); showHideToolTip(ui.slider->value()); } void SystemLoadWidget::increasePriority() { ui.slider->setValue(ui.slider->value() + 1); showHideToolTip(ui.slider->value()); } void SystemLoadWidget::showHideToolTip(int prio) { QString const tooltip_text(tooltipText(prio)); if (tooltip_text.isEmpty()) { QToolTip::hideText(); return; } // Show the tooltip immediately. QPoint const 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, tooltip_text, ui.slider); } QString SystemLoadWidget::tooltipText(int prio) { if (prio == ThreadPriority::Minimum) { return tr("Minimal"); } else if (prio == ThreadPriority::Normal) { return tr("Normal"); } else { return QString(); } } scantailor-RELEASE_0_9_12_2/SystemLoadWidget.h000066400000000000000000000024051271170121200210610ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SYSTEM_LOAD_WIDGET_H_ #define SYSTEM_LOAD_WIDGET_H_ #include "ui_SystemLoadWidget.h" #include class SystemLoadWidget : public QWidget { Q_OBJECT public: SystemLoadWidget(QWidget* parent = 0); private slots: void sliderPressed(); void sliderMoved(int prio); void valueChanged(int prio); void decreasePriority(); void increasePriority(); private: void showHideToolTip(int prio); static QString tooltipText(int prio); Ui::SystemLoadWidget ui; }; #endif scantailor-RELEASE_0_9_12_2/TabbedDebugImages.cpp000066400000000000000000000026541271170121200214500ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "TabbedDebugImages.h.moc" #include "DebugImageView.h" TabbedDebugImages::TabbedDebugImages(QWidget* parent) : QTabWidget(parent) { setDocumentMode(true); connect(this, SIGNAL(currentChanged(int)), SLOT(currentTabChanged(int))); } void TabbedDebugImages::currentTabChanged(int const idx) { if (DebugImageView* div = dynamic_cast(widget(idx))) { div->unlink(); m_liveViews.push_back(*div); removeExcessLiveViews(); div->setLive(true); } } void TabbedDebugImages::removeExcessLiveViews() { int remaining = m_liveViews.size(); for (; remaining > MAX_LIVE_VIEWS; --remaining) { m_liveViews.front().setLive(false); m_liveViews.erase(m_liveViews.begin()); } } scantailor-RELEASE_0_9_12_2/TabbedDebugImages.h000066400000000000000000000027161271170121200211140ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef TABBED_DEBUG_IMAGES_H_ #define TABBED_DEBUG_IMAGES_H_ #include "DebugImageView.h" #include #ifndef Q_MOC_RUN #include #endif class TabbedDebugImages : public QTabWidget { Q_OBJECT public: TabbedDebugImages(QWidget* parent = 0); private slots: void currentTabChanged(int idx); private: typedef boost::intrusive::list< DebugImageView, boost::intrusive::constant_time_size > DebugViewList; enum { MAX_LIVE_VIEWS = 3 }; 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; }; #endif scantailor-RELEASE_0_9_12_2/TaskStatus.h000066400000000000000000000017771271170121200177520ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef TASKSTATUS_H_ #define TASKSTATUS_H_ class TaskStatus { public: virtual ~TaskStatus() {} virtual void cancel() = 0; virtual bool isCancelled() const = 0; virtual void throwIfCancelled() const = 0; }; #endif scantailor-RELEASE_0_9_12_2/ThreadPriority.cpp000066400000000000000000000044461271170121200211440ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ThreadPriority.h" #include #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; } 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; } ThreadPriority ThreadPriority::load( QSettings const& settings, QString const& key, Priority dflt) { QString const 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(QString const& key, Priority dflt) { QSettings settings; return load(settings, key, dflt); } void ThreadPriority::save(QSettings& settings, QString const& key) { char const* 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::fromAscii(str)); } void ThreadPriority::save(QString const& key) { QSettings settings; save(settings, key); } scantailor-RELEASE_0_9_12_2/ThreadPriority.h000066400000000000000000000030731271170121200206040ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef THREAD_PRIORITY_H_ #define THREAD_PRIORITY_H_ #include #include class QSettings; class ThreadPriority { // Member-wise copying is OK. public: enum Priority { Minimum, Idle = Minimum, Lowest, Low, Normal, Maximum = Normal }; ThreadPriority(Priority prio) : m_prio(prio) {} void setValue(Priority prio) { m_prio = prio; } Priority value() const { return m_prio; } QThread::Priority toQThreadPriority() const; int toPosixNiceLevel() const; static ThreadPriority load( QSettings const& settings, QString const& key, Priority dflt = Normal); static ThreadPriority load(QString const& key, Priority dflt = Normal); void save(QSettings& settings, QString const& key); void save(QString const& key); private: Priority m_prio; }; #endif scantailor-RELEASE_0_9_12_2/ThumbnailBase.cpp000066400000000000000000000172351271170121200207110ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ThumbnailBase.h" #include "ThumbnailPixmapCache.h" #include "ThumbnailLoadResult.h" #include "NonCopyable.h" #include "AbstractCommand.h" #include "PixmapRenderer.h" #include "imageproc/PolygonUtils.h" #ifndef Q_MOC_RUN #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace imageproc; class ThumbnailBase::LoadCompletionHandler : public AbstractCommand1 { DECLARE_NON_COPYABLE(LoadCompletionHandler) public: LoadCompletionHandler(ThumbnailBase* thumb) : m_pThumb(thumb) {} virtual void operator()(ThumbnailLoadResult const& result) { m_pThumb->handleLoadResult(result); } private: ThumbnailBase* m_pThumb; }; ThumbnailBase::ThumbnailBase( IntrusivePtr const& thumbnail_cache, QSizeF const& max_size, ImageId const& image_id, ImageTransformation const& image_xform) : m_ptrThumbnailCache(thumbnail_cache), m_maxSize(max_size), m_imageId(image_id), m_imageXform(image_xform), m_extendedClipArea(false) { setImageXform(m_imageXform); } ThumbnailBase::~ThumbnailBase() { } QRectF ThumbnailBase::boundingRect() const { return m_boundingRect; } void ThumbnailBase::paint(QPainter* painter, QStyleOptionGraphicsItem const* option, QWidget *widget) { QPixmap pixmap; if (!m_ptrCompletionHandler.get()) { boost::shared_ptr handler( new LoadCompletionHandler(this) ); ThumbnailPixmapCache::Status const status = m_ptrThumbnailCache->loadRequest(m_imageId, pixmap, handler); if (status == ThumbnailPixmapCache::QUEUED) { m_ptrCompletionHandler.swap(handler); } } QTransform const image_to_display(m_postScaleXform * painter->worldTransform()); QTransform const thumb_to_display(painter->worldTransform()); if (pixmap.isNull()) { double const border = 1.0; double const shadow = 2.0; QRectF rect(m_boundingRect); rect.adjust(border, border, -(border + shadow), -(border + shadow)); painter->fillRect(m_boundingRect, QColor(0x00, 0x00, 0x00)); painter->fillRect(rect, QColor(0xff, 0xff, 0xff)); paintOverImage(*painter, image_to_display, thumb_to_display); return; } QSizeF const orig_image_size(m_imageXform.origRect().size()); double const x_pre_scale = orig_image_size.width() / pixmap.width(); double const y_pre_scale = orig_image_size.height() / pixmap.height(); QTransform pre_scale_xform; pre_scale_xform.scale(x_pre_scale, y_pre_scale); QTransform const pixmap_to_thumb( pre_scale_xform * m_imageXform.transform() * m_postScaleXform ); // The polygon to draw into in original image coordinates. QPolygonF image_poly(PolygonUtils::round(m_imageXform.resultingPostCropArea())); if (!m_extendedClipArea) { image_poly = image_poly.intersected( PolygonUtils::round( m_imageXform.transform().map(m_imageXform.origRect()) ) ); } // The polygon to draw into in display coordinates. QPolygonF display_poly(image_to_display.map(image_poly)); QRectF display_rect(display_poly.boundingRect()); display_rect.setTop(floor(display_rect.top())); display_rect.setLeft(floor(display_rect.left())); display_rect.setBottom(ceil(display_rect.bottom())); display_rect.setRight(ceil(display_rect.right())); QPixmap temp_pixmap; QString const cache_key(QString::fromAscii("ThumbnailBase::temp_pixmap")); if (!QPixmapCache::find(cache_key, temp_pixmap) || temp_pixmap.width() < display_rect.width() || temp_pixmap.height() < display_rect.width()) { int w = (int)display_rect.width(); int h = (int)display_rect.height(); // Add some extra, to avoid rectreating the pixmap too often. w += w / 10; h += h / 10; temp_pixmap = QPixmap(w, h); if (!temp_pixmap.hasAlphaChannel()) { // This actually forces the alpha channel to be created. temp_pixmap.fill(Qt::transparent); } QPixmapCache::insert(cache_key, temp_pixmap); } QPainter temp_painter; temp_painter.begin(&temp_pixmap); QTransform temp_adjustment; temp_adjustment.translate(-display_rect.left(), -display_rect.top()); temp_painter.setWorldTransform( pixmap_to_thumb * thumb_to_display * temp_adjustment ); // Turn off alpha compositing. temp_painter.setCompositionMode(QPainter::CompositionMode_Source); temp_painter.setRenderHint(QPainter::SmoothPixmapTransform); temp_painter.setRenderHint(QPainter::Antialiasing); PixmapRenderer::drawPixmap(temp_painter, pixmap); // 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.setPen(Qt::NoPen); temp_painter.setBrush(Qt::white); temp_painter.setWorldTransform(temp_adjustment); #ifndef Q_WS_X11 // That's how it's supposed to be. temp_painter.setCompositionMode(QPainter::CompositionMode_Clear); #else // QPainter::CompositionMode_Clear doesn't work for arbitrarily shaped // objects on X11, as well as CompositionMode_Source with a transparent // brush. Fortunately, CompositionMode_DestinationOut with a non-transparent // brush does actually work. temp_painter.setCompositionMode(QPainter::CompositionMode_DestinationOut); #endif temp_painter.drawPolygon( QPolygonF(display_rect).subtracted(PolygonUtils::round(display_poly)) ); temp_painter.end(); painter->setWorldTransform(QTransform()); painter->setClipRect(display_rect); painter->setRenderHint(QPainter::SmoothPixmapTransform, false); painter->setCompositionMode(QPainter::CompositionMode_SourceOver); painter->drawPixmap(display_rect.topLeft(), temp_pixmap); } void ThumbnailBase::setImageXform(ImageTransformation const& image_xform) { m_imageXform = image_xform; QSizeF const unscaled_size( image_xform.resultingRect().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); double const x_post_scale = scaled_size.width() / unscaled_size.width(); double const y_post_scale = scaled_size.height() / unscaled_size.height(); m_postScaleXform.reset(); m_postScaleXform.scale(x_post_scale, y_post_scale); } void ThumbnailBase::handleLoadResult(ThumbnailLoadResult const& 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(); } } scantailor-RELEASE_0_9_12_2/ThumbnailBase.h000066400000000000000000000077441271170121200203620ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef THUMBNAILBASE_H_ #define THUMBNAILBASE_H_ #include "NonCopyable.h" #include "ImageId.h" #include "ImageTransformation.h" #include "IntrusivePtr.h" #include "ThumbnailPixmapCache.h" #ifndef Q_MOC_RUN #include #endif #include #include #include #include class ThumbnailLoadResult; class ThumbnailBase : public QGraphicsItem { DECLARE_NON_COPYABLE(ThumbnailBase) public: ThumbnailBase( IntrusivePtr const& thumbnail_cache, QSizeF const& max_size, ImageId const& image_id, ImageTransformation const& image_xform); virtual ~ThumbnailBase(); virtual QRectF boundingRect() const; virtual void paint(QPainter* painter, QStyleOptionGraphicsItem const* option, QWidget *widget); 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, QTransform const& image_to_display, QTransform const& thumb_to_display) {} /** * 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(ImageTransformation const& image_xform); ImageTransformation const& 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. */ QTransform const& virtToThumb() const { return m_postScaleXform; } private: class LoadCompletionHandler; void handleLoadResult(ThumbnailLoadResult const& result); IntrusivePtr m_ptrThumbnailCache; QSizeF m_maxSize; ImageId m_imageId; ImageTransformation m_imageXform; QRectF m_boundingRect; /** * Transforms virtual image coordinates into thumbnail coordinates. * Valid thumbnail coordinates lie within this->boundingRect(). */ QTransform m_postScaleXform; boost::shared_ptr m_ptrCompletionHandler; bool m_extendedClipArea; }; #endif scantailor-RELEASE_0_9_12_2/ThumbnailFactory.cpp000066400000000000000000000050131271170121200214350ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ThumbnailFactory.h" #include "CompositeCacheDrivenTask.h" #include "filter_dc/ThumbnailCollector.h" #include #include class ThumbnailFactory::Collector : public ThumbnailCollector { public: Collector(IntrusivePtr const& cache, QSizeF const& max_size); virtual void processThumbnail(std::auto_ptr thumbnail); virtual IntrusivePtr thumbnailCache(); virtual QSizeF maxLogicalThumbSize() const; std::auto_ptr retrieveThumbnail() { return m_ptrThumbnail; } private: IntrusivePtr m_ptrCache; QSizeF m_maxSize; std::auto_ptr m_ptrThumbnail; }; ThumbnailFactory::ThumbnailFactory( IntrusivePtr const& pixmap_cache, QSizeF const& max_size, IntrusivePtr const& task) : m_ptrPixmapCache(pixmap_cache), m_maxSize(max_size), m_ptrTask(task) { } ThumbnailFactory::~ThumbnailFactory() { } std::auto_ptr ThumbnailFactory::get(PageInfo const& page_info) { Collector collector(m_ptrPixmapCache, m_maxSize); m_ptrTask->process(page_info, &collector); return collector.retrieveThumbnail(); } /*======================= ThumbnailFactory::Collector ======================*/ ThumbnailFactory::Collector::Collector( IntrusivePtr const& cache, QSizeF const& max_size) : m_ptrCache(cache), m_maxSize(max_size) { } void ThumbnailFactory::Collector::processThumbnail( std::auto_ptr thumbnail) { m_ptrThumbnail = thumbnail; } IntrusivePtr ThumbnailFactory::Collector::thumbnailCache() { return m_ptrCache; } QSizeF ThumbnailFactory::Collector::maxLogicalThumbSize() const { return m_maxSize; } scantailor-RELEASE_0_9_12_2/ThumbnailFactory.h000066400000000000000000000030611271170121200211030ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef THUMBNAILFACTORY_H_ #define THUMBNAILFACTORY_H_ #include "NonCopyable.h" #include "RefCountable.h" #include "IntrusivePtr.h" #include "ThumbnailPixmapCache.h" #include #include class PageInfo; class CompositeCacheDrivenTask; class QGraphicsItem; class ThumbnailFactory : public RefCountable { DECLARE_NON_COPYABLE(ThumbnailFactory) public: ThumbnailFactory( IntrusivePtr const& pixmap_cache, QSizeF const& max_size, IntrusivePtr const& task); virtual ~ThumbnailFactory(); std::auto_ptr get(PageInfo const& page_info); private: class Collector; IntrusivePtr m_ptrPixmapCache; QSizeF m_maxSize; IntrusivePtr m_ptrTask; }; #endif scantailor-RELEASE_0_9_12_2/ThumbnailLoadResult.h000066400000000000000000000041341271170121200215540ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef THUMBNAILLOADRESULT_H_ #define THUMBNAILLOADRESULT_H_ #include class ThumbnailLoadResult { public: enum Status { /** * \brief Thumbnail loaded successfully. Pixmap is not null. */ LOADED, /** * \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 }; ThumbnailLoadResult(Status status, QPixmap const& pixmap) : m_pixmap(pixmap), m_status(status) {} Status status() const { return m_status; } QPixmap const& pixmap() const { return m_pixmap; } private: QPixmap m_pixmap; Status m_status; }; #endif scantailor-RELEASE_0_9_12_2/ThumbnailPixmapCache.cpp000066400000000000000000000626251271170121200222240ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #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 #ifndef Q_MOC_RUN #include #include #include #include #include #endif #include #include #include 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, /** * 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 could not be loaded. */ LOAD_FAILED }; ImageId imageId; mutable QPixmap pixmap; /**< Guaranteed to be set if status is LOADED */ mutable std::vector< boost::weak_ptr > 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; mutable Status status; Item(ImageId const& image_id, int preceding_load_attepmts, Status status); Item(Item const& other); private: Item& operator=(Item const& other); // Assignment is forbidden. }; class ThumbnailPixmapCache::Impl : public QThread { public: Impl(QString const& thumb_dir, QSize const& max_thumb_size, int max_cached_pixmaps, int expiration_threshold); ~Impl(); void setThumbDir(QString const& thumb_dir); Status request( ImageId const& image_id, QPixmap& pixmap, bool load_now = false, boost::weak_ptr const* completion_handler = 0); void ensureThumbnailExists(ImageId const& image_id, QImage const& image); void recreateThumbnail(ImageId const& image_id, QImage const& image); protected: virtual void run(); virtual void customEvent(QEvent* e); private: class LoadResultEvent; class ItemsByKeyTag; class LoadQueueTag; class RemoveQueueTag; typedef multi_index_container< Item, indexed_by< ordered_unique, member >, sequenced >, sequenced > > > Container; typedef Container::index::type ItemsByKey; typedef Container::index::type LoadQueue; typedef Container::index::type RemoveQueue; class BackgroundLoader : public QObject { public: BackgroundLoader(Impl& owner); protected: virtual void customEvent(QEvent* e); private: Impl& m_rOwner; }; void backgroundProcessing(); static QImage loadSaveThumbnail( ImageId const& image_id, QString const& thumb_dir, QSize const& max_thumb_size); static QString getThumbFilePath( ImageId const& image_id, QString const& thumb_dir); static QImage makeThumbnail( QImage const& image, QSize const& max_thumb_size); void queuedToInProgress(LoadQueue::iterator const& lq_it); void postLoadResult( LoadQueue::iterator const& lq_it, QImage const& image, ThumbnailLoadResult::Status status); void processLoadResult(LoadResultEvent* result); void removeExcessLocked(); void removeItemLocked(RemoveQueue::iterator const& it); void cachePixmapUnlocked(ImageId const& image_id, QPixmap const& pixmap); void cachePixmapLocked(ImageId const& image_id, QPixmap const& pixmap); 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 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; QString m_thumbDir; QSize m_maxThumbSize; int m_maxCachedPixmaps; /** * \see ThumbnailPixmapCache::ThumbnailPixmapCache() */ int m_expirationThreshold; int m_numQueuedItems; int m_numLoadedItems; /** * Total image loading attempts so far. Used for request expiration. * \see ThumbnailLoadResult::REQUEST_EXPIRED */ int m_totalLoadAttempts; bool m_threadStarted; bool m_shuttingDown; }; class ThumbnailPixmapCache::Impl::LoadResultEvent : public QEvent { public: LoadResultEvent(Impl::LoadQueue::iterator const& lq_t, QImage const& image, ThumbnailLoadResult::Status status); virtual ~LoadResultEvent(); Impl::LoadQueue::iterator lqIter() const { return m_lqIter; } QImage const& image() const { return m_image; } void releaseImage() { m_image = QImage(); } ThumbnailLoadResult::Status status() const { return m_status; } private: Impl::LoadQueue::iterator m_lqIter; QImage m_image; ThumbnailLoadResult::Status m_status; }; /*========================== ThumbnailPixmapCache ===========================*/ ThumbnailPixmapCache::ThumbnailPixmapCache( QString const& thumb_dir, QSize const& max_thumb_size, int const max_cached_pixmaps, int const expiration_threshold) : m_ptrImpl( new Impl( RelinkablePath::normalize(thumb_dir), max_thumb_size, max_cached_pixmaps, expiration_threshold ) ) { } ThumbnailPixmapCache::~ThumbnailPixmapCache() { } void ThumbnailPixmapCache::setThumbDir(QString const& thumb_dir) { m_ptrImpl->setThumbDir(RelinkablePath::normalize(thumb_dir)); } ThumbnailPixmapCache::Status ThumbnailPixmapCache::loadFromCache(ImageId const& image_id, QPixmap& pixmap) { return m_ptrImpl->request(image_id, pixmap); } ThumbnailPixmapCache::Status ThumbnailPixmapCache::loadNow(ImageId const& image_id, QPixmap& pixmap) { return m_ptrImpl->request(image_id, pixmap, true); } ThumbnailPixmapCache::Status ThumbnailPixmapCache::loadRequest( ImageId const& image_id, QPixmap& pixmap, boost::weak_ptr const& completion_handler) { return m_ptrImpl->request(image_id, pixmap, false, &completion_handler); } void ThumbnailPixmapCache::ensureThumbnailExists( ImageId const& image_id, QImage const& image) { m_ptrImpl->ensureThumbnailExists(image_id, image); } void ThumbnailPixmapCache::recreateThumbnail( ImageId const& image_id, QImage const& image) { m_ptrImpl->recreateThumbnail(image_id, image); } /*======================= ThumbnailPixmapCache::Impl ========================*/ ThumbnailPixmapCache::Impl::Impl( QString const& thumb_dir, QSize const& max_thumb_size, int const max_cached_pixmaps, int const 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); } ThumbnailPixmapCache::Impl::~Impl() { { QMutexLocker const locker(&m_mutex); if (!m_threadStarted) { return; } m_shuttingDown = true; } quit(); wait(); } void ThumbnailPixmapCache::Impl::setThumbDir(QString const& thumb_dir) { QMutexLocker locker(&m_mutex); if (thumb_dir == m_thumbDir) { return; } m_thumbDir = thumb_dir; BOOST_FOREACH(Item const& 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( ImageId const& image_id, QPixmap& pixmap, bool const load_now, boost::weak_ptr const* completion_handler) { assert(QCoreApplication::instance()->thread() == QThread::currentThread()); QMutexLocker locker(&m_mutex); if (m_shuttingDown) { return LOAD_FAILED; } ItemsByKey::iterator const 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. RemoveQueue::iterator const 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 LOAD_FAILED; } } if (load_now) { QString const thumb_dir(m_thumbDir); QSize const 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); return LOADED; } if (!completion_handler) { 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. LoadQueue::iterator const lq_it( m_items.project(k_it) ); m_loadQueue.relocate(m_loadQueue.begin(), lq_it); } return QUEUED; } // Create a new item. LoadQueue::iterator const 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. assert(lq_it->status == Item::QUEUED); assert(lq_it->completionHandlers.empty()); 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; } } return QUEUED; } void ThumbnailPixmapCache::Impl::ensureThumbnailExists( ImageId const& image_id, QImage const& image) { if (m_shuttingDown) { return; } if (image.isNull()) { return; } QMutexLocker locker(&m_mutex); QString const thumb_dir(m_thumbDir); QSize const max_thumb_size(m_maxThumbSize); locker.unlock(); QString const thumb_file_path(getThumbFilePath(image_id, thumb_dir)); if (QFile::exists(thumb_file_path)) { return; } QImage const 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( ImageId const& image_id, QImage const& image) { if (m_shuttingDown) { return; } if (image.isNull()) { return; } QMutexLocker locker(&m_mutex); QString const thumb_dir(m_thumbDir); QSize const max_thumb_size(m_maxThumbSize); locker.unlock(); QString const thumb_file_path(getThumbFilePath(image_id, thumb_dir)); QImage const 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; } QMutexLocker const locker2(&m_mutex); ItemsByKey::iterator const 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; } } void ThumbnailPixmapCache::Impl::run() { backgroundProcessing(); exec(); // Wait for further processing requests (via custom events). } void ThumbnailPixmapCache::Impl::customEvent(QEvent* e) { processLoadResult(dynamic_cast(e)); } 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; { QMutexLocker const 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 QImage const image( loadSaveThumbnail(image_id, thumb_dir, max_thumb_size) ); ThumbnailLoadResult::Status const status = image.isNull() ? ThumbnailLoadResult::LOAD_FAILED : ThumbnailLoadResult::LOADED; postLoadResult(lq_it, image, status); } catch (std::bad_alloc const&) { OutOfMemoryHandler::instance().handleOutOfMemorySituation(); } } } QImage ThumbnailPixmapCache::Impl::loadSaveThumbnail( ImageId const& image_id, QString const& thumb_dir, QSize const& max_thumb_size) { QString const thumb_file_path(getThumbFilePath(image_id, thumb_dir)); QImage image(ImageLoader::load(thumb_file_path, 0)); if (!image.isNull()) { return image; } image = ImageLoader::load(image_id); if (image.isNull()) { return QImage(); } QImage const thumbnail(makeThumbnail(image, max_thumb_size)); thumbnail.save(thumb_file_path, "PNG"); return thumbnail; } QString ThumbnailPixmapCache::Impl::getThumbFilePath( ImageId const& image_id, QString const& 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. QByteArray const orig_path_hash( QCryptographicHash::hash( image_id.filePath().toUtf8(), QCryptographicHash::Md5 ).toHex() ); QString const orig_path_hash_str( QString::fromAscii(orig_path_hash.data(), orig_path_hash.size()) ); QFileInfo const 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::fromAscii(".png"); return thumb_file_path; } QImage ThumbnailPixmapCache::Impl::makeThumbnail( QImage const& image, QSize const& max_thumb_size) { 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); 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 ); } void ThumbnailPixmapCache::Impl::queuedToInProgress(LoadQueue::iterator const& lq_it) { assert(lq_it->status == Item::QUEUED); lq_it->status = Item::IN_PROGRESS; 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); // 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( LoadQueue::iterator const& lq_it, QImage const& image, ThumbnailLoadResult::Status const status) { LoadResultEvent* e = new LoadResultEvent(lq_it, image, status); QCoreApplication::postEvent(this, e); } void ThumbnailPixmapCache::Impl::processLoadResult(LoadResultEvent* result) { assert(QCoreApplication::instance()->thread() == QThread::currentThread()); QPixmap pixmap(QPixmap::fromImage(result->image())); result->releaseImage(); std::vector > completion_handlers; { QMutexLocker const locker(&m_mutex); if (m_shuttingDown) { return; } LoadQueue::iterator const lq_it(result->lqIter()); RemoveQueue::iterator const rq_it( m_items.project(lq_it) ); Item const& 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) { // Maybe remove an older item. removeExcessLocked(); 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 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; // 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. ThumbnailLoadResult const load_result(result->status(), pixmap); typedef boost::weak_ptr WeakHandler; BOOST_FOREACH (WeakHandler const& wh, completion_handlers) { boost::shared_ptr const sh(wh.lock()); if (sh.get()) { (*sh)(load_result); } } } 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()); } } void ThumbnailPixmapCache::Impl::removeItemLocked( RemoveQueue::iterator const& 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( ImageId const& image_id, QPixmap const& pixmap) { QMutexLocker const locker(&m_mutex); cachePixmapLocked(image_id, pixmap); } void ThumbnailPixmapCache::Impl::cachePixmapLocked( ImageId const& image_id, QPixmap const& pixmap) { if (m_shuttingDown) { return; } Item::Status const new_status = pixmap.isNull() ? Item::LOAD_FAILED : Item::LOADED; // Check if such item already exists. ItemsByKey::iterator const k_it(m_itemsByKey.find(image_id)); if (k_it == m_itemsByKey.end()) { // Existing item not found. // Maybe remove an older item. removeExcessLocked(); // Insert our new item. RemoveQueue::iterator const 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; assert(rq_it->completionHandlers.empty()); 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; } 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()); LoadQueue::iterator const lq_it( m_items.project(k_it) ); lq_it->pixmap = pixmap; queuedToInProgress(lq_it); postLoadResult(lq_it, QImage(), ThumbnailLoadResult::LOADED); return; } assert(k_it->status == Item::LOAD_FAILED); k_it->status = new_status; k_it->pixmap = pixmap; if (new_status == Item::LOADED) { RemoveQueue::iterator const rq_it( m_items.project(k_it) ); m_removeQueue.relocate(m_endOfLoadedItems, rq_it); ++m_numLoadedItems; } } /*====================== ThumbnailPixmapCache::Item =========================*/ ThumbnailPixmapCache::Item::Item(ImageId const& image_id, int const preceding_load_attempts, Status const st) : imageId(image_id), precedingLoadAttempts(preceding_load_attempts), status(st) { } ThumbnailPixmapCache::Item::Item(Item const& other) : imageId(other.imageId), pixmap(other.pixmap), completionHandlers(other.completionHandlers), precedingLoadAttempts(other.precedingLoadAttempts), status(other.status) { } /*=============== ThumbnailPixmapCache::Impl::LoadResultEvent ===============*/ ThumbnailPixmapCache::Impl::LoadResultEvent::LoadResultEvent( Impl::LoadQueue::iterator const& lq_it, QImage const& image, ThumbnailLoadResult::Status const status) : QEvent(QEvent::User), m_lqIter(lq_it), m_image(image), m_status(status) { } ThumbnailPixmapCache::Impl::LoadResultEvent::~LoadResultEvent() { } /*================== ThumbnailPixmapCache::BackgroundLoader =================*/ ThumbnailPixmapCache::Impl::BackgroundLoader::BackgroundLoader(Impl& owner) : m_rOwner(owner) { } void ThumbnailPixmapCache::Impl::BackgroundLoader::customEvent(QEvent*) { m_rOwner.backgroundProcessing(); } scantailor-RELEASE_0_9_12_2/ThumbnailPixmapCache.h000066400000000000000000000125001271170121200216540ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef THUMBNAILPIXMAPCACHE_H_ #define THUMBNAILPIXMAPCACHE_H_ #include "NonCopyable.h" #include "RefCountable.h" #include "ThumbnailLoadResult.h" #include "AbstractCommand.h" #ifndef Q_MOC_RUN #include #endif #include class ImageId; class QImage; class QPixmap; class QString; class QSize; class ThumbnailPixmapCache : public RefCountable { DECLARE_NON_COPYABLE(ThumbnailPixmapCache) public: enum Status { LOADED, LOAD_FAILED, QUEUED }; typedef AbstractCommand1< void, ThumbnailLoadResult const& > 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(QString const& thumb_dir, QSize const& max_size, int max_cached_pixmaps, int expiration_threshold); /** * \brief Destructor. To be called from the GUI thread only. */ virtual ~ThumbnailPixmapCache(); void setThumbDir(QString const& 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(ImageId const& 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(ImageId const& 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(ThumbnailLoadResult const& 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( ImageId const& image_id, QPixmap& pixmap, boost::weak_ptr const& 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(ImageId const& image_id, QImage const& 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(ImageId const& image_id, QImage const& image); private: class Item; class Impl; std::auto_ptr m_ptrImpl; }; #endif scantailor-RELEASE_0_9_12_2/ThumbnailSequence.cpp000066400000000000000000001231141271170121200216010ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ThumbnailSequence.h" #include "ThumbnailSequence.h.moc" #include "ThumbnailFactory.h" #include "IncompleteThumbnail.h" #include "PageSequence.h" #include "PageOrderProvider.h" #include "PageInfo.h" #include "PageId.h" #include "ImageId.h" #include "RefCountable.h" #include "IntrusivePtr.h" #include "ScopedIncDec.h" #ifndef Q_MOC_RUN #include #include #include #include #include #include #include #include #endif #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 #include #include #include #include #include #include #include using namespace ::boost::multi_index; using namespace ::boost::lambda; class ThumbnailSequence::Item { public: Item(PageInfo const& page_info, CompositeItem* comp_item); PageId const& pageId() const { return pageInfo.id(); } bool isSelected() const { return m_isSelected; } bool isSelectionLeader() const { return m_isSelectionLeader; } void setSelected(bool selected) const; void setSelectionLeader(bool selection_leader) const; PageInfo pageInfo; mutable CompositeItem* composite; mutable bool incompleteThumbnail; private: mutable bool m_isSelected; mutable bool m_isSelectionLeader; }; class ThumbnailSequence::GraphicsScene : public QGraphicsScene { public: typedef boost::function ContextMenuEventCallback; void setContextMenuEventCallback(ContextMenuEventCallback callback) { m_contextMenuEventCallback = callback; } protected: virtual void contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { QGraphicsScene::contextMenuEvent(event); if (!event->isAccepted() && m_contextMenuEventCallback) { m_contextMenuEventCallback(event); } } private: ContextMenuEventCallback m_contextMenuEventCallback; }; class ThumbnailSequence::Impl { public: Impl(ThumbnailSequence& owner, QSizeF const& max_logical_thumb_size); ~Impl(); void setThumbnailFactory(IntrusivePtr const& factory); void attachView(QGraphicsView* view); void reset(PageSequence const& pages, SelectionAction const selection_action, IntrusivePtr const& provider); IntrusivePtr pageOrderProvider() const; PageSequence toPageSequence() const; void invalidateThumbnail(PageId const& page_id); void invalidateThumbnail(PageInfo const& page_info); void invalidateAllThumbnails(); bool setSelection(PageId const& page_id); PageInfo selectionLeader() const; PageInfo prevPage(PageId const& page_id) const; PageInfo nextPage(PageId const& page_id) const; PageInfo firstPage() const; PageInfo lastPage() const; void insert(PageInfo const& new_page, BeforeOrAfter before_or_after, ImageId const& image); void removePages(std::set const& pages); QRectF selectionLeaderSceneRect() const; std::set selectedItems() const; std::vector selectedRanges() const; void contextMenuRequested( PageInfo const& page_info, QPoint const& screen_pos, bool selected); void itemSelectedByUser(CompositeItem* item, Qt::KeyboardModifiers modifiers); private: class ItemsByIdTag; class ItemsInOrderTag; class SelectedThenUnselectedTag; typedef multi_index_container< Item, indexed_by< ordered_unique< tag, const_mem_fun >, sequenced >, sequenced > > > Container; typedef Container::index::type ItemsById; typedef Container::index::type ItemsInOrder; typedef Container::index::type SelectedThenUnselected; void invalidateThumbnailImpl(ItemsById::iterator id_it); void sceneContextMenuEvent(QGraphicsSceneContextMenuEvent* evt); void selectItemNoModifiers(ItemsById::iterator const& it); void selectItemWithControl(ItemsById::iterator const& it); void selectItemWithShift(ItemsById::iterator const& it); bool multipleItemsSelected() const; void moveToSelected(Item const* item); void moveToUnselected(Item const* item); void clear(); void clearSelection(); /** * 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, PageId const& page_id, bool page_incomplete, ItemsInOrder::iterator hint, int* dist_from_hint = 0); std::auto_ptr getThumbnail(PageInfo const& page_info); std::auto_ptr getLabelGroup(PageInfo const& page_info); std::auto_ptr getCompositeItem( Item const* item, PageInfo const& info); void commitSceneRect(); static int const SPACING = 10; ThumbnailSequence& m_rOwner; 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; Item const* m_pSelectionLeader; IntrusivePtr m_ptrFactory; IntrusivePtr m_ptrOrderProvider; GraphicsScene m_graphicsScene; QRectF m_sceneRect; }; class ThumbnailSequence::PlaceholderThumb : public QGraphicsItem { public: PlaceholderThumb(QSizeF const& max_size); virtual QRectF boundingRect() const; virtual void paint(QPainter* painter, QStyleOptionGraphicsItem const* option, QWidget *widget); private: static QPainterPath m_sCachedPath; QSizeF m_maxSize; }; class ThumbnailSequence::LabelGroup : public QGraphicsItemGroup { public: LabelGroup(std::auto_ptr label); LabelGroup( std::auto_ptr normal_label, std::auto_ptr bold_label, std::auto_ptr pixmap = std::auto_ptr()); void updateAppearence(bool selected, bool selection_leader); private: QGraphicsSimpleTextItem* m_pNormalLabel; QGraphicsSimpleTextItem* m_pBoldLabel; }; class ThumbnailSequence::CompositeItem : public QGraphicsItemGroup { public: CompositeItem( ThumbnailSequence::Impl& owner, std::auto_ptr thumbnail, std::auto_ptr label_group); void setItem(Item const* item) { m_pItem = item; } Item const* item() { return m_pItem; } bool incompleteThumbnail() const; void updateSceneRect(QRectF& scene_rect); void updateAppearence(bool selected, bool selection_leader); virtual QRectF boundingRect() const; virtual void paint(QPainter* painter, QStyleOptionGraphicsItem const* option, QWidget *widget); protected: virtual void contextMenuEvent(QGraphicsSceneContextMenuEvent* 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; void setSelected(bool selected); ThumbnailSequence::Impl& m_rOwner; ThumbnailSequence::Item const* m_pItem; QGraphicsItem* m_pThumb; LabelGroup* m_pLabelGroup; }; /*============================= ThumbnailSequence ===========================*/ ThumbnailSequence::ThumbnailSequence(QSizeF const& max_logical_thumb_size) : m_ptrImpl(new Impl(*this, max_logical_thumb_size)) { } ThumbnailSequence::~ThumbnailSequence() { } void ThumbnailSequence::setThumbnailFactory(IntrusivePtr const& factory) { m_ptrImpl->setThumbnailFactory(factory); } void ThumbnailSequence::attachView(QGraphicsView* const view) { m_ptrImpl->attachView(view); } void ThumbnailSequence::reset( PageSequence const& pages, SelectionAction const selection_action, IntrusivePtr const& order_provider) { m_ptrImpl->reset(pages, selection_action, order_provider); } IntrusivePtr ThumbnailSequence::pageOrderProvider() const { return m_ptrImpl->pageOrderProvider(); } PageSequence ThumbnailSequence::toPageSequence() const { return m_ptrImpl->toPageSequence(); } void ThumbnailSequence::invalidateThumbnail(PageId const& page_id) { m_ptrImpl->invalidateThumbnail(page_id); } void ThumbnailSequence::invalidateThumbnail(PageInfo const& page_info) { m_ptrImpl->invalidateThumbnail(page_info); } void ThumbnailSequence::invalidateAllThumbnails() { m_ptrImpl->invalidateAllThumbnails(); } bool ThumbnailSequence::setSelection(PageId const& page_id) { return m_ptrImpl->setSelection(page_id); } PageInfo ThumbnailSequence::selectionLeader() const { return m_ptrImpl->selectionLeader(); } PageInfo ThumbnailSequence::prevPage(PageId const& reference_page) const { return m_ptrImpl->prevPage(reference_page); } PageInfo ThumbnailSequence::nextPage(PageId const& reference_page) const { return m_ptrImpl->nextPage(reference_page); } PageInfo ThumbnailSequence::firstPage() const { return m_ptrImpl->firstPage(); } PageInfo ThumbnailSequence::lastPage() const { return m_ptrImpl->lastPage(); } void ThumbnailSequence::insert( PageInfo const& new_page, BeforeOrAfter before_or_after, ImageId const& image) { m_ptrImpl->insert(new_page, before_or_after, image); } void ThumbnailSequence::removePages(std::set const& pages) { m_ptrImpl->removePages(pages); } QRectF ThumbnailSequence::selectionLeaderSceneRect() const { return m_ptrImpl->selectionLeaderSceneRect(); } std::set ThumbnailSequence::selectedItems() const { return m_ptrImpl->selectedItems(); } std::vector ThumbnailSequence::selectedRanges() const { return m_ptrImpl->selectedRanges(); } void ThumbnailSequence::emitNewSelectionLeader( PageInfo const& page_info, CompositeItem const* composite, SelectionFlags const flags) { QRectF const thumb_rect( composite->mapToScene(composite->boundingRect()).boundingRect() ); emit newSelectionLeader(page_info, thumb_rect, flags); } /*======================== ThumbnailSequence::Impl ==========================*/ ThumbnailSequence::Impl::Impl( ThumbnailSequence& owner, QSizeF const& 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( boost::lambda::bind(&Impl::sceneContextMenuEvent, this, boost::lambda::_1) ); } ThumbnailSequence::Impl::~Impl() { } void ThumbnailSequence::Impl::setThumbnailFactory( IntrusivePtr const& factory) { m_ptrFactory = factory; } void ThumbnailSequence::Impl::attachView(QGraphicsView* const view) { view->setScene(&m_graphicsScene); } void ThumbnailSequence::Impl::reset( PageSequence const& pages, SelectionAction const selection_action, IntrusivePtr const& order_provider) { m_ptrOrderProvider = order_provider; std::set selected; PageInfo selection_leader; if (selection_action == KEEP_SELECTION) { selectedItems().swap(selected); if (m_pSelectionLeader) { selection_leader = m_pSelectionLeader->pageInfo; } } clear(); // Also clears the selection. size_t const num_pages = pages.numPages(); if (num_pages == 0) { return; } Item const* some_selected_item = 0; for (size_t i = 0; i < num_pages; ++i) { PageInfo const& page_info(pages.pageAt(i)); std::auto_ptr composite(getCompositeItem(0, page_info)); m_itemsInOrder.push_back(Item(page_info, composite.release())); Item const* item = &m_itemsInOrder.back(); item->composite->setItem(item); 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; } } invalidateAllThumbnails(); if (!m_pSelectionLeader) { if (some_selected_item) { m_pSelectionLeader = some_selected_item; } } if (m_pSelectionLeader) { m_pSelectionLeader->setSelectionLeader(true); m_rOwner.emitNewSelectionLeader( selection_leader, m_pSelectionLeader->composite, DEFAULT_SELECTION_FLAGS ); } } IntrusivePtr ThumbnailSequence::Impl::pageOrderProvider() const { return m_ptrOrderProvider; } PageSequence ThumbnailSequence::Impl::toPageSequence() const { PageSequence pages; BOOST_FOREACH(Item const& item, m_itemsInOrder) { pages.append(item.pageInfo); } return pages; } void ThumbnailSequence::Impl::invalidateThumbnail(PageId const& page_id) { ItemsById::iterator const id_it(m_itemsById.find(page_id)); if (id_it != m_itemsById.end()) { invalidateThumbnailImpl(id_it); } } void ThumbnailSequence::Impl::invalidateThumbnail(PageInfo const& page_info) { ItemsById::iterator const id_it(m_itemsById.find(page_info.id())); if (id_it != m_itemsById.end()) { m_itemsById.modify(id_it, boost::lambda::bind(&Item::pageInfo, boost::lambda::_1) = page_info); invalidateThumbnailImpl(id_it); } } void ThumbnailSequence::Impl::invalidateThumbnailImpl(ItemsById::iterator const id_it) { std::auto_ptr composite( getCompositeItem(&*id_it, id_it->pageInfo) ); CompositeItem* const new_composite = composite.get(); CompositeItem* const old_composite = id_it->composite; QSizeF const old_size(old_composite->boundingRect().size()); QSizeF const new_size(new_composite->boundingRect().size()); QPointF const 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; ItemsInOrder::iterator const 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; } double offset = 0; if (ord_it != m_itemsInOrder.begin()) { ItemsInOrder::iterator prev(ord_it); --prev; offset = prev->composite->pos().y() + prev->composite->boundingRect().height() + SPACING; } // Reposition items between the old and the new position of our item, // including the item itself. for (; ord_it != ord_end; ++ord_it) { ord_it->composite->setPos(0.0, offset); offset += ord_it->composite->boundingRect().height() + SPACING; } // Reposition the items following both the new and the old position // of the item, if the item size has changed. if (old_size != new_size) { for (; ord_it != m_itemsInOrder.end(); ++ord_it) { ord_it->composite->setPos(0.0, offset); offset += ord_it->composite->boundingRect().height() + SPACING; } } // 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::invalidateAllThumbnails() { // Recreate thumbnails now, whether a thumbnail is incomplete // is taken into account when sorting. ItemsInOrder::iterator ord_it(m_itemsInOrder.begin()); ItemsInOrder::iterator const 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.get()) { m_itemsInOrder.sort( boost::lambda::bind( &PageOrderProvider::precedes, m_ptrOrderProvider.get(), boost::lambda::bind(&Item::pageId, boost::lambda::_1), bind(&Item::incompleteThumbnail, boost::lambda::_1), boost::lambda::bind(&Item::pageId, boost::lambda::_2), bind(&Item::incompleteThumbnail, boost::lambda::_2) ) ); } m_sceneRect = QRectF(0.0, 0.0, 0.0, 0.0); double offset = 0; for (ord_it = m_itemsInOrder.begin(); ord_it != ord_end; ++ord_it) { CompositeItem* composite = ord_it->composite; composite->setPos(0.0, offset); composite->updateSceneRect(m_sceneRect); composite->updateAppearence(ord_it->isSelected(), ord_it->isSelectionLeader()); offset += composite->boundingRect().height() + SPACING; m_graphicsScene.addItem(composite); } commitSceneRect(); } bool ThumbnailSequence::Impl::setSelection(PageId const& page_id) { ItemsById::iterator const id_it(m_itemsById.find(page_id)); if (id_it == m_itemsById.end()) { return false; } bool const was_selection_leader = (&*id_it == m_pSelectionLeader); // Clear selection from all items except the one for which // selection is requested. SelectedThenUnselected::iterator it(m_selectedThenUnselected.begin()); while (it != m_selectedThenUnselected.end()) { Item const& item = *it; if (!item.isSelected()) { break; } ++it; if (&*id_it != &item) { item.setSelected(false); moveToUnselected(&item); if (m_pSelectionLeader == &item) { m_pSelectionLeader = 0; } } } if (!was_selection_leader) { m_pSelectionLeader = &*id_it; m_pSelectionLeader->setSelectionLeader(true); moveToSelected(m_pSelectionLeader); } SelectionFlags flags = DEFAULT_SELECTION_FLAGS; if (was_selection_leader) { flags |= REDUNDANT_SELECTION; } m_rOwner.emitNewSelectionLeader(id_it->pageInfo, id_it->composite, flags); return true; } PageInfo ThumbnailSequence::Impl::selectionLeader() const { if (m_pSelectionLeader) { return m_pSelectionLeader->pageInfo; } else { return PageInfo(); } } PageInfo ThumbnailSequence::Impl::prevPage(PageId const& reference_page) const { 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 (ord_it != m_itemsInOrder.end()) { if (ord_it != m_itemsInOrder.begin()) { --ord_it; return ord_it->pageInfo; } } return PageInfo(); } PageInfo ThumbnailSequence::Impl::nextPage(PageId const& reference_page) const { 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 (ord_it != m_itemsInOrder.end()) { ++ord_it; if (ord_it != m_itemsInOrder.end()) { return ord_it->pageInfo; } } return PageInfo(); } PageInfo ThumbnailSequence::Impl::firstPage() const { if (m_items.empty()) { return PageInfo(); } return m_itemsInOrder.front().pageInfo; } PageInfo ThumbnailSequence::Impl::lastPage() const { if (m_items.empty()) { return PageInfo(); } return m_itemsInOrder.back().pageInfo; } void ThumbnailSequence::Impl::insert( PageInfo const& page_info, BeforeOrAfter before_or_after, ImageId const& 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_ptrOrderProvider.get()) { // 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(), /*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; } } std::auto_ptr composite( getCompositeItem(0, page_info) ); composite->setPos(0.0, offset); composite->updateSceneRect(m_sceneRect); QPointF const pos_delta(0.0, composite->boundingRect().height() + SPACING); Item const item(page_info, composite.get()); std::pair const ins( m_itemsInOrder.insert(ord_it, item) ); composite->setItem(&*ins.first); m_graphicsScene.addItem(composite.release()); ItemsInOrder::iterator const 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(); } void ThumbnailSequence::Impl::removePages(std::set const& to_remove) { m_sceneRect = QRectF(0, 0, 0, 0); std::set::const_iterator const to_remove_end(to_remove.end()); QPointF pos_delta(0, 0); ItemsInOrder::iterator ord_it(m_itemsInOrder.begin()); ItemsInOrder::iterator const 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++); } } commitSceneRect(); } bool ThumbnailSequence::Impl::multipleItemsSelected() const { SelectedThenUnselected::iterator it(m_selectedThenUnselected.begin()); SelectedThenUnselected::iterator const end(m_selectedThenUnselected.end()); for (int i = 0; i < 2; ++i, ++it) { if (it == end || !it->isSelected()) { return false; } } return true; } void ThumbnailSequence::Impl::moveToSelected(Item const* item) { m_selectedThenUnselected.relocate( m_selectedThenUnselected.begin(), m_selectedThenUnselected.iterator_to(*item) ); } void ThumbnailSequence::Impl::moveToUnselected(Item const* item) { m_selectedThenUnselected.relocate( m_selectedThenUnselected.end(), m_selectedThenUnselected.iterator_to(*item) ); } QRectF ThumbnailSequence::Impl::selectionLeaderSceneRect() const { if (!m_pSelectionLeader) { return QRectF(); } return m_pSelectionLeader->composite->mapToScene( m_pSelectionLeader->composite->boundingRect() ).boundingRect(); } std::set ThumbnailSequence::Impl::selectedItems() const { std::set selection; BOOST_FOREACH(Item const& item, m_selectedThenUnselected) { if (!item.isSelected()) { break; } selection.insert(item.pageInfo.id()); } return selection; } std::vector ThumbnailSequence::Impl::selectedRanges() const { std::vector ranges; ItemsInOrder::iterator it(m_itemsInOrder.begin()); ItemsInOrder::iterator const end(m_itemsInOrder.end()); for (;;) { 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()); } } return ranges; } void ThumbnailSequence::Impl::contextMenuRequested( PageInfo const& page_info, QPoint const& screen_pos, bool selected) { emit m_rOwner.pageContextMenuRequested(page_info, screen_pos, selected); } void ThumbnailSequence::Impl::sceneContextMenuEvent(QGraphicsSceneContextMenuEvent* evt) { if (!m_itemsInOrder.empty()) { CompositeItem* composite = m_itemsInOrder.back().composite; QRectF const last_thumb_rect( composite->mapToScene(composite->boundingRect()).boundingRect() ); if (evt->scenePos().y() <= last_thumb_rect.bottom()) { return; } } emit m_rOwner.pastLastPageContextMenuRequested(evt->screenPos()); } void ThumbnailSequence::Impl::itemSelectedByUser( CompositeItem* composite, Qt::KeyboardModifiers const modifiers) { ItemsById::iterator const 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); } } void ThumbnailSequence::Impl::selectItemWithControl(ItemsById::iterator const& 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; } } } } assert(m_pSelectionLeader); // We had multiple selected items. m_pSelectionLeader->setSelectionLeader(true); // No need to moveToSelected() as it was and remains selected. m_rOwner.emitNewSelectionLeader( m_pSelectionLeader->pageInfo, m_pSelectionLeader->composite, flags ); } void ThumbnailSequence::Impl::selectItemWithShift(ItemsById::iterator const& 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 (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); } void ThumbnailSequence::Impl::selectItemNoModifiers(ItemsById::iterator const& id_it) { SelectionFlags flags = SELECTED_BY_USER; if (m_pSelectionLeader == &*id_it) { flags |= REDUNDANT_SELECTION; } clearSelection(); m_pSelectionLeader = &*id_it; m_pSelectionLeader->setSelectionLeader(true); moveToSelected(m_pSelectionLeader); m_rOwner.emitNewSelectionLeader(id_it->pageInfo, id_it->composite, flags); } void ThumbnailSequence::Impl::clear() { m_pSelectionLeader = 0; ItemsInOrder::iterator it(m_itemsInOrder.begin()); ItemsInOrder::iterator const end(m_itemsInOrder.end()); while (it != end) { delete it->composite; m_itemsInOrder.erase(it++); } assert(m_graphicsScene.items().empty()); m_sceneRect = QRectF(0.0, 0.0, 0.0, 0.0); commitSceneRect(); } void ThumbnailSequence::Impl::clearSelection() { m_pSelectionLeader = 0; BOOST_FOREACH(Item const& item, m_selectedThenUnselected) { if (!item.isSelected()) { break; } item.setSelected(false); } } ThumbnailSequence::Impl::ItemsInOrder::iterator ThumbnailSequence::Impl::itemInsertPosition( ItemsInOrder::iterator const begin, ItemsInOrder::iterator const end, PageId const& page_id, bool const page_incomplete, ItemsInOrder::iterator const 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.get()) { 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; bool const 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) { bool const precedes = m_ptrOrderProvider->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; } std::auto_ptr ThumbnailSequence::Impl::getThumbnail(PageInfo const& page_info) { std::auto_ptr thumb; if (m_ptrFactory.get()) { thumb = m_ptrFactory->get(page_info); } if (!thumb.get()) { thumb.reset(new PlaceholderThumb(m_maxLogicalThumbSize)); } return thumb; } std::auto_ptr ThumbnailSequence::Impl::getLabelGroup(PageInfo const& page_info) { PageId const& page_id = page_info.id(); QFileInfo const file_info(page_id.imageId().filePath()); QString const file_name(file_info.fileName()); QString text(file_name); if (page_info.imageId().isMultiPageFile()) { text = ThumbnailSequence::tr( "%1 (page %2)" ).arg(file_name).arg(page_id.imageId().page()); } std::auto_ptr normal_text_item(new QGraphicsSimpleTextItem); normal_text_item->setText(text); std::auto_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(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()); char const* 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::auto_ptr(new LabelGroup(normal_text_item, bold_text_item)); } QPixmap const pixmap(pixmap_resource); std::auto_ptr pixmap_item(new QGraphicsPixmapItem); pixmap_item->setPixmap(pixmap); int const 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::auto_ptr(new LabelGroup(normal_text_item, bold_text_item, pixmap_item)); } std::auto_ptr ThumbnailSequence::Impl::getCompositeItem( Item const* item, PageInfo const& page_info) { std::auto_ptr thumb(getThumbnail(page_info)); std::auto_ptr label_group(getLabelGroup(page_info)); std::auto_ptr composite( new CompositeItem(*this, thumb, label_group) ); composite->setItem(item); 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); } } /*==================== ThumbnailSequence::Item ======================*/ ThumbnailSequence::Item::Item(PageInfo const& page_info, CompositeItem* comp_item) : pageInfo(page_info), composite(comp_item), incompleteThumbnail(comp_item->incompleteThumbnail()), m_isSelected(false), m_isSelectionLeader(false) { } void ThumbnailSequence::Item::setSelected(bool selected) const { bool const was_selected = m_isSelected; bool const 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(); } } void ThumbnailSequence::Item::setSelectionLeader(bool selection_leader) const { bool const was_selected = m_isSelected; bool const 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(); } } /*================== ThumbnailSequence::PlaceholderThumb ====================*/ QPainterPath ThumbnailSequence::PlaceholderThumb::m_sCachedPath; ThumbnailSequence::PlaceholderThumb::PlaceholderThumb(QSizeF const& max_size) : m_maxSize(max_size) { } QRectF ThumbnailSequence::PlaceholderThumb::boundingRect() const { return QRectF(QPointF(0.0, 0.0), m_maxSize); } void ThumbnailSequence::PlaceholderThumb::paint( QPainter* painter, QStyleOptionGraphicsItem const*, QWidget*) { IncompleteThumbnail::drawQuestionMark(*painter, boundingRect()); } /*====================== ThumbnailSequence::LabelGroup ======================*/ ThumbnailSequence::LabelGroup::LabelGroup( std::auto_ptr normal_label, std::auto_ptr bold_label, std::auto_ptr pixmap) : m_pNormalLabel(normal_label.get()), m_pBoldLabel(bold_label.get()) { m_pNormalLabel->setVisible(true); m_pBoldLabel->setVisible(false); addToGroup(normal_label.release()); addToGroup(bold_label.release()); if (pixmap.get()) { 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(QApplication::palette().highlightedText()); } else { m_pNormalLabel->setBrush(QApplication::palette().text()); } } /*==================== ThumbnailSequence::CompositeItem =====================*/ ThumbnailSequence::CompositeItem::CompositeItem( ThumbnailSequence::Impl& owner, std::auto_ptr thumbnail, std::auto_ptr label_group) : m_rOwner(owner), m_pItem(0), m_pThumb(thumbnail.get()), m_pLabelGroup(label_group.get()) { QSizeF const thumb_size(thumbnail->boundingRect().size()); QSizeF const label_size(label_group->boundingRect().size()); int const thumb_label_spacing = 1; thumbnail->setPos(-0.5 * thumb_size.width(), 0.0); label_group->setPos( thumbnail->pos().x() + thumb_size.width() - label_size.width(), thumb_size.height() + thumb_label_spacing ); addToGroup(thumbnail.release()); addToGroup(label_group.release()); setCursor(Qt::PointingHandCursor); setZValue(-1); } bool ThumbnailSequence::CompositeItem::incompleteThumbnail() const { return dynamic_cast(m_pThumb) != 0; } void ThumbnailSequence::CompositeItem::updateSceneRect(QRectF& scene_rect) { QRectF rect(m_pThumb->boundingRect()); rect.translate(m_pThumb->pos()); rect.translate(pos()); QRectF bounding_rect(boundingRect()); bounding_rect.translate(pos()); rect.setTop(bounding_rect.top()); rect.setBottom(bounding_rect.bottom()); scene_rect |= rect; } void ThumbnailSequence::CompositeItem::updateAppearence(bool selected, bool selection_leader) { m_pLabelGroup->updateAppearence(selected, selection_leader); } QRectF ThumbnailSequence::CompositeItem::boundingRect() const { QRectF rect(QGraphicsItemGroup::boundingRect()); rect.adjust(-100, -5, 100, 3); return rect; } void ThumbnailSequence::CompositeItem::paint( QPainter* painter, QStyleOptionGraphicsItem const* option, QWidget *widget) { if (m_pItem->isSelected()) { painter->fillRect( boundingRect(), QApplication::palette().color(QPalette::Highlight) ); } } void ThumbnailSequence::CompositeItem::mousePressEvent( QGraphicsSceneMouseEvent* const event) { QGraphicsItemGroup::mousePressEvent(event); event->accept(); if (event->button() == Qt::LeftButton) { m_rOwner.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() ); } scantailor-RELEASE_0_9_12_2/ThumbnailSequence.h000066400000000000000000000170171271170121200212520ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef THUMBNAILSEQUENCE_H_ #define THUMBNAILSEQUENCE_H_ #include "NonCopyable.h" #include "FlagOps.h" #include "IntrusivePtr.h" #include "PageRange.h" #include "PageOrderProvider.h" #include "BeforeOrAfter.h" #include #include #include #include class QGraphicsItem; class QGraphicsView; class PageId; class ImageId; class PageInfo; class PageSequence; class ThumbnailFactory; class QSizeF; 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, /** * Indicates that the request to make this item a selection leader was redundant, * as it's already a selection leader. */ REDUNDANT_SELECTION = 1 << 1, /** * 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 }; ThumbnailSequence(QSizeF const& max_logical_thumb_size); ~ThumbnailSequence(); void setThumbnailFactory(IntrusivePtr const& 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(PageSequence const& pages, SelectionAction const selection_action, IntrusivePtr const& order_provider = IntrusivePtr()); /** Returns the current page order provider, which may be null. */ IntrusivePtr 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(PageId const& page_id); /** * This signature differs from invalidateThumbnail(PageId) in that * it will cause PageInfo stored by ThumbnailSequence to be updated. */ void invalidateThumbnail(PageInfo const& 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. * \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(PageId const& page_id); /** * \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 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(PageId const& 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(PageId const& 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(PageInfo const& new_page, BeforeOrAfter before_or_after, ImageId const& image); void removePages(std::set const& 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( PageInfo const& page_info, QRectF const& thumb_rect, ThumbnailSequence::SelectionFlags flags); /** * Emitted when a user right-clicks on a page thumbnail. */ void pageContextMenuRequested( PageInfo const& page_info, QPoint const& 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(QPoint const& screen_pos); private: class Item; class Impl; class GraphicsScene; class PlaceholderThumb; class LabelGroup; class CompositeItem; void emitNewSelectionLeader( PageInfo const& page_info, CompositeItem const* composite, SelectionFlags flags); std::auto_ptr m_ptrImpl; }; DEFINE_FLAG_OPS(ThumbnailSequence::SelectionFlags) #endif scantailor-RELEASE_0_9_12_2/TiffMetadataLoader.cpp000066400000000000000000000023721271170121200216470ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "TiffMetadataLoader.h" #include "TiffReader.h" void TiffMetadataLoader::registerMyself() { static bool registered = false; if (!registered) { ImageMetadataLoader::registerLoader( IntrusivePtr(new TiffMetadataLoader) ); registered = true; } } ImageMetadataLoader::Status TiffMetadataLoader::loadMetadata( QIODevice& io_device, VirtualFunction1& out) { return TiffReader::readMetadata(io_device, out); } scantailor-RELEASE_0_9_12_2/TiffMetadataLoader.h000066400000000000000000000025171271170121200213150ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef TIFFMETADATALOADER_H_ #define TIFFMETADATALOADER_H_ #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: virtual Status loadMetadata( QIODevice& io_device, VirtualFunction1& out); }; #endif scantailor-RELEASE_0_9_12_2/TiffReader.cpp000066400000000000000000000316161271170121200202050ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "TiffReader.h" #include "ImageMetadata.h" #include "NonCopyable.h" #include "Dpi.h" #include "Dpm.h" #include #include #include #include #include #include #include #include #include #include #include #include class TiffReader::TiffHeader { public: enum Signature { INVALID_SIGNATURE, TIFF_BIG_ENDIAN, TIFF_LITTLE_ENDIAN }; TiffHeader() : m_signature(INVALID_SIGNATURE), m_version(0) {} TiffHeader(Signature signature, int version) : m_signature(signature), m_version(version) {} Signature signature() const { return m_signature; } int version() const { return m_version; } private: Signature m_signature; int m_version; }; class TiffReader::TiffHandle { public: TiffHandle(TIFF* handle) : m_pHandle(handle) {} ~TiffHandle() { if (m_pHandle) TIFFClose(m_pHandle); } TIFF* handle() const { return m_pHandle; } private: TIFF* m_pHandle; }; template class TiffReader::TiffBuffer { DECLARE_NON_COPYABLE(TiffBuffer) public: TiffBuffer() : m_pData(0) {} TiffBuffer(tsize_t num_items) { m_pData = (T*)_TIFFmalloc(num_items * sizeof(T)); if (!m_pData) { throw std::bad_alloc(); } } ~TiffBuffer() { if (m_pData) { _TIFFfree(m_pData); } } T* data() { return m_pData; } void swap(TiffBuffer& other) { std::swap(m_pData, other.m_pData); } private: T* m_pData; }; 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(TiffHandle const& tif, TiffHeader const& header); bool mapsToBinaryOrIndexed8() const; }; TiffReader::TiffInfo::TiffInfo(TiffHandle const& tif, TiffHeader const& 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; } 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; } return false; } static tsize_t deviceRead(thandle_t context, tdata_t data, tsize_t size) { QIODevice* dev = (QIODevice*)context; 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; } static toff_t deviceSeek(thandle_t context, toff_t offset, int whence) { QIODevice* 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; } return dev->pos(); } static int deviceClose(thandle_t context) { QIODevice* dev = (QIODevice*)context; dev->close(); return 0; } static toff_t deviceSize(thandle_t context) { QIODevice* dev = (QIODevice*)context; return dev->size(); } static int deviceMap(thandle_t, tdata_t*, toff_t*) { // Not implemented. return 0; } static void deviceUnmap(thandle_t, tdata_t, toff_t) { // Not implemented. } bool TiffReader::canRead(QIODevice& device) { if (!device.isReadable()) { return false; } if (device.isSequential()) { // libtiff needs to be able to seek. return false; } TiffHeader header(readHeader(device)); return checkHeader(header); } ImageMetadataLoader::Status TiffReader::readMetadata( QIODevice& device, VirtualFunction1& 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; } static void convertAbgrToArgb(uint32 const* src, uint32* dst, int count) { for (int i = 0; i < count; ++i) { uint32 const 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, int const page_num) { 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(), page_num)) { return QImage(); } TiffInfo const info(tif, header); ImageMetadata const 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; uint32 const* src_line = 0; 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 = (uint32 const*)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(); } uint32* dst_line = (uint32*)image.bits(); assert(image.bytesPerLine() % 4 == 0); int const 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()) { Dpm const dpm(metadata.dpi()); image.setDotsPerMeterX(dpm.horizontal()); image.setDotsPerMeterY(dpm.vertical()); } return image; } TiffReader::TiffHeader TiffReader::readHeader(QIODevice& device) { unsigned char data[4]; if (device.peek((char*)data, sizeof(data)) != sizeof(data)) { return TiffHeader(); } uint16 const version_byte0 = data[2]; uint16 const version_byte1 = data[3]; if (data[0] == 0x4d && data[1] == 0x4d) { uint16 const version = (version_byte0 << 8) + version_byte1; return TiffHeader(TiffHeader::TIFF_BIG_ENDIAN, version); } else if (data[0] == 0x49 && data[1] == 0x49) { uint16 const version = (version_byte1 << 8) + version_byte0; return TiffHeader(TiffHeader::TIFF_LITTLE_ENDIAN, version); } else { return TiffHeader(); } } bool TiffReader::checkHeader(TiffHeader const& header) { if (header.signature() == TiffHeader::INVALID_SIGNATURE) { return false; } if (header.version() != 42 && header.version() != 43) { return false; } return true; } ImageMetadata TiffReader::currentPageMetadata(TiffHandle const& 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)); } 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)); } return Dpi(); } QImage TiffReader::extractBinaryOrIndexed8Image( TiffHandle const& tif, TiffInfo const& 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(); } int const num_colors = 1 << info.bits_per_sample; image.setNumColors(num_colors); if (info.photometric == PHOTOMETRIC_PALETTE) { uint16* pr = 0; uint16* pg = 0; uint16* pb = 0; 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); } double const f = 255.0/65535.0; for (int i = 0; i < num_colors; ++i) { uint32 const r = (uint32)(pr[i] * f + 0.5); uint32 const g = (uint32)(pg[i] * f + 0.5); uint32 const b = (uint32)(pb[i] * f + 0.5); uint32 const a = 0xFF000000; image.setColor(i, a | (r << 16) | (g << 8) | b); } } else if (info.photometric == PHOTOMETRIC_MINISBLACK) { double const f = 255.0 / (num_colors - 1); for (int i = 0; i < num_colors; ++i) { int const gray = (int)(i * f + 0.5); image.setColor(i, qRgb(gray, gray, gray)); } } else if (info.photometric == PHOTOMETRIC_MINISWHITE) { double const f = 255.0 / (num_colors - 1); int c = num_colors - 1; for (int i = 0; i < num_colors; ++i, --c) { int const gray = (int)(c * f + 0.5); 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; } void TiffReader::readLines(TiffHandle const& tif, QImage& image) { int const height = image.height(); for (int y = 0; y < height; ++y) { TIFFReadScanline(tif.handle(), image.scanLine(y), y); } } void TiffReader::readAndUnpackLines( TiffHandle const& tif, TiffInfo const& info, QImage& image) { TiffBuffer buf(TIFFScanlineSize(tif.handle())); int const width = image.width(); int const height = image.height(); int const bits_per_sample = info.bits_per_sample; unsigned const 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; uint8 const* src = buf.data(); uint8* dst = (uint8*)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); } } } scantailor-RELEASE_0_9_12_2/TiffReader.h000066400000000000000000000042041271170121200176430ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef TIFFREADER_H_ #define TIFFREADER_H_ #include "ImageMetadataLoader.h" #include "VirtualFunction.h" class QIODevice; class QImage; class ImageMetadata; class Dpi; class TiffReader { public: static bool canRead(QIODevice& device); static ImageMetadataLoader::Status readMetadata( QIODevice& device, VirtualFunction1& 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); private: class TiffHeader; class TiffHandle; struct TiffInfo; template class TiffBuffer; static TiffHeader readHeader(QIODevice& device); static bool checkHeader(TiffHeader const& header); static ImageMetadata currentPageMetadata(TiffHandle const& tif); static Dpi getDpi(float xres, float yres, unsigned res_unit); static QImage extractBinaryOrIndexed8Image( TiffHandle const& tif, TiffInfo const& info); static void readLines(TiffHandle const& tif, QImage& image); static void readAndUnpackLines( TiffHandle const& tif, TiffInfo const& info, QImage& image); }; #endif scantailor-RELEASE_0_9_12_2/TiffWriter.cpp000066400000000000000000000305401271170121200202520ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "TiffWriter.h" #include "Dpm.h" #include "imageproc/Constants.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** * m_reverseBitsLUT[byte] gives the same byte, but with bit order reversed. */ uint8_t const 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 }; class TiffWriter::TiffHandle { public: TiffHandle(TIFF* handle) : m_pHandle(handle) {} ~TiffHandle() { if (m_pHandle) TIFFClose(m_pHandle); } TIFF* handle() const { return m_pHandle; } private: TIFF* m_pHandle; }; static tsize_t deviceRead(thandle_t context, tdata_t data, tsize_t size) { // Not implemented. return 0; } static tsize_t deviceWrite(thandle_t context, tdata_t data, tsize_t size) { QIODevice* dev = (QIODevice*)context; return (tsize_t)dev->write(static_cast(data), size); } static toff_t deviceSeek(thandle_t context, toff_t offset, int whence) { QIODevice* 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; } return dev->pos(); } static int deviceClose(thandle_t context) { QIODevice* dev = (QIODevice*)context; dev->close(); return 0; } static toff_t deviceSize(thandle_t context) { QIODevice* dev = (QIODevice*)context; return dev->size(); } static int deviceMap(thandle_t, tdata_t*, toff_t*) { // Not implemented. return 0; } static void deviceUnmap(thandle_t, tdata_t, toff_t) { // Not implemented. } bool TiffWriter::writeImage(QString const& file_path, QImage const& image) { if (image.isNull()) { return false; } QFile file(file_path); if (!file.open(QFile::WriteOnly)) { return false; } if (!writeImage(file, image)) { file.remove(); return false; } return true; } bool TiffWriter::writeImage(QIODevice& device, QImage const& 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) ); } } /** * Set the physical resolution, if it's defined. */ void TiffWriter::setDpm(TiffHandle const& tif, Dpm const& dpm) { using namespace imageproc::constants; if (dpm.isNull()) { return; } float xres = 0.01 * dpm.horizontal(); // cm float yres = 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. double const xdpi = dpm.horizontal() * DPM2DPI; double const ydpi = dpm.vertical() * DPM2DPI; double const rounded_xdpi = floor(xdpi + 0.5); double const rounded_ydpi = floor(ydpi + 0.5); if (fabs(xdpi - rounded_xdpi) < 0.02 && fabs(ydpi - rounded_ydpi) < 0.02) { xres = rounded_xdpi; yres = 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( TiffHandle const& tif, QImage const& image) { TIFFSetField(tif.handle(), TIFFTAG_SAMPLESPERPIXEL, uint16(1)); uint16 compression = COMPRESSION_LZW; 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: // Don't use CCITTFAX4 compression, as Photoshop // has problems with it. //compression = COMPRESSION_CCITTFAX4; bits_per_sample = 1; if (image.numColors() < 2) { photometric = PHOTOMETRIC_MINISWHITE; } else { // Some programs don't understand // palettized binary images, so don't // use a palette for black and white images. uint32_t const c0 = image.color(0); uint32_t const 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_COMPRESSION, compression); TIFFSetField(tif.handle(), TIFFTAG_BITSPERSAMPLE, bits_per_sample); TIFFSetField(tif.handle(), TIFFTAG_PHOTOMETRIC, photometric); if (photometric == PHOTOMETRIC_PALETTE) { int const 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) { QRgb const rgb = color_table[i]; pr[i] = (0xFFFF * qRed(rgb) + 128) / 255; pg[i] = (0xFFFF * qGreen(rgb) + 128) / 255; pb[i] = (0xFFFF * qBlue(rgb) + 128) / 255; } 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 { return writeBinaryLinesAsIs(tif, image); } } } bool TiffWriter::writeRGB32Image( TiffHandle const& tif, QImage const& image) { assert(image.format() == QImage::Format_RGB32); TIFFSetField(tif.handle(), TIFFTAG_SAMPLESPERPIXEL, uint16(3)); TIFFSetField(tif.handle(), TIFFTAG_COMPRESSION, COMPRESSION_LZW); TIFFSetField(tif.handle(), TIFFTAG_BITSPERSAMPLE, uint16(8)); TIFFSetField(tif.handle(), TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); int const width = image.width(); int const 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) { uint32_t const* p_src = (uint32_t const*)image.scanLine(y); uint8_t* p_dst = &tmp_line[0]; for (int x = 0; x < width; ++x) { uint32_t const 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; } bool TiffWriter::writeARGB32Image( TiffHandle const& tif, QImage const& image) { assert(image.format() == QImage::Format_ARGB32); TIFFSetField(tif.handle(), TIFFTAG_SAMPLESPERPIXEL, uint16(4)); TIFFSetField(tif.handle(), TIFFTAG_COMPRESSION, COMPRESSION_LZW); TIFFSetField(tif.handle(), TIFFTAG_BITSPERSAMPLE, uint16(8)); TIFFSetField(tif.handle(), TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); int const width = image.width(); int const 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) { uint32_t const* p_src = (uint32_t const*)image.scanLine(y); uint8_t* p_dst = &tmp_line[0]; for (int x = 0; x < width; ++x) { uint32_t const 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; } bool TiffWriter::write8bitLines( TiffHandle const& tif, QImage const& image) { int const width = image.width(); int const 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) { uint8_t const* 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; } bool TiffWriter::writeBinaryLinesAsIs( TiffHandle const& tif, QImage const& image) { int const width = image.width(); int const 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. int const bpl = (width + 7) / 8; std::vector tmp_line(bpl, 0); for (int y = 0; y < height; ++y) { uint8_t const* 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; } bool TiffWriter::writeBinaryLinesReversed( TiffHandle const& tif, QImage const& image) { int const width = image.width(); int const height = image.height(); int const bpl = (width + 7) / 8; std::vector tmp_line(bpl, 0); for (int y = 0; y < height; ++y) { uint8_t const* 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; } scantailor-RELEASE_0_9_12_2/TiffWriter.h000066400000000000000000000043541271170121200177230ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef TIFFWRITER_H_ #define TIFFWRITER_H_ #include #include class QIODevice; class QString; 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(QString const& file_path, QImage const& 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, QImage const& image); private: class TiffHandle; static void setDpm(TiffHandle const& tif, Dpm const& dpm); static bool writeBitonalOrIndexed8Image( TiffHandle const& tif, QImage const& image); static bool writeRGB32Image( TiffHandle const& tif, QImage const& image); static bool writeARGB32Image( TiffHandle const& tif, QImage const& image); static bool write8bitLines( TiffHandle const& tif, QImage const& image); static bool writeBinaryLinesAsIs( TiffHandle const& tif, QImage const& image); static bool writeBinaryLinesReversed( TiffHandle const& tif, QImage const& image); static uint8_t const m_reverseBitsLUT[256]; }; #endif scantailor-RELEASE_0_9_12_2/Utils.cpp000066400000000000000000000047411271170121200172710ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Utils.h" #include #include #include #include #include #include // Qt::escape() is actually declare there. #ifdef Q_WS_WIN #include #else #include #endif bool Utils::overwritingRename(QString const& from, QString const& to) { #ifdef Q_WS_WIN return MoveFileExW( (WCHAR*)from.utf16(), (WCHAR*)to.utf16(), MOVEFILE_REPLACE_EXISTING ) != 0; #else return rename( QFile::encodeName(from).data(), QFile::encodeName(to).data() ) == 0; #endif } QString Utils::richTextForLink( QString const& label, QString const& target) { return QString::fromAscii( "" "" "

%2

" ).arg(Qt::escape(target), Qt::escape(label)); } void Utils::maybeCreateCacheDir(QString const& output_dir) { QDir(output_dir).mkdir(QString::fromAscii("cache")); // QDir::mkdir() returns false if the directory already exists, // so to prevent confusion this function return void. } QString Utils::outputDirToThumbDir(QString const& output_dir) { return output_dir+QLatin1String("/cache/thumbs"); } IntrusivePtr Utils::createThumbnailCache(QString const& output_dir) { QSize const max_pixmap_size(200, 200); QString const thumbs_cache_path(outputDirToThumbDir(output_dir)); return IntrusivePtr( new ThumbnailPixmapCache(thumbs_cache_path, max_pixmap_size, 40, 5) ); } scantailor-RELEASE_0_9_12_2/Utils.h000066400000000000000000000052661271170121200167410ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef UTILS_H_ #define UTILS_H_ #include #include "ThumbnailPixmapCache.h" class Utils { public: template static typename M::iterator mapSetValue( M& map, K const& key, V const& val); /** * \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(QString const& output_dir); static QString outputDirToThumbDir(QString const& output_dir); static IntrusivePtr createThumbnailCache(QString const& output_dir); /** * Unlike QFile::rename(), this one overwrites existing files. */ static bool overwritingRename(QString const& from, QString const& 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(QString const&). * \return The resulting reach text. */ static QString richTextForLink( QString const& label, QString const& target = QString(QChar('#'))); }; template typename M::iterator Utils::mapSetValue(M& map, K const& key, V const& val) { typename M::iterator const it(map.lower_bound(key)); if (it == map.end() || map.key_comp()(key, it->first)) { return map.insert(it, typename M::value_type(key, val)); } else { it->second = val; return it; } } #endif scantailor-RELEASE_0_9_12_2/WorkerThread.cpp000066400000000000000000000164231271170121200205720ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "WorkerThread.h" #include "WorkerThread.h.moc" #include "ThreadPriority.h" #include "OutOfMemoryHandler.h" #include #include #include #include #include #include #if defined(Q_OS_LINUX) // For Linux updatePriority() #include #include #include #endif class WorkerThread::Dispatcher : public QObject { public: enum UpdatePriorityResult { PriorityUpdated, PriorityUpdateFailed, ThreadRestartRequired }; Dispatcher(Impl& owner); UpdatePriorityResult updateThreadPriority(BackgroundTask const& task); void maybeProcessQueuedTask(); protected: virtual void customEvent(QEvent* event); private: void processTask(BackgroundTaskPtr const& task); Impl& m_rOwner; /** * This one will be set if we decide we need to restart * the background thread before processing a given task. */ BackgroundTaskPtr m_ptrQueuedTask; }; class WorkerThread::Impl : public QThread { public: enum { NormalExit = 0, ExitForRestart }; Impl(WorkerThread& owner); ~Impl(); void performTask(BackgroundTaskPtr const& task); protected: virtual void run(); virtual void customEvent(QEvent* event); protected: void resetPriority(); private: static QEvent::Type const ThreadRestartEvent = (QEvent::Type)(QEvent::User + 1); WorkerThread& m_rOwner; Dispatcher m_dispatcher; bool m_threadStarted; }; class WorkerThread::PerformTaskEvent : public QEvent { public: PerformTaskEvent(BackgroundTaskPtr const& task); BackgroundTaskPtr const& task() const { return m_ptrTask; } private: BackgroundTaskPtr m_ptrTask; }; class WorkerThread::TaskResultEvent : public QEvent { public: TaskResultEvent(BackgroundTaskPtr const& task, FilterResultPtr const& result); BackgroundTaskPtr const& task() const { return m_ptrTask; } FilterResultPtr const& result() const { return m_ptrResult; } private: BackgroundTaskPtr m_ptrTask; FilterResultPtr m_ptrResult; }; /*=============================== WorkerThread ==============================*/ WorkerThread::WorkerThread(QObject* parent) : QObject(parent), m_ptrImpl(new Impl(*this)) { } WorkerThread::~WorkerThread() { } void WorkerThread::shutdown() { m_ptrImpl.reset(); } void WorkerThread::performTask(BackgroundTaskPtr const& task) { if (m_ptrImpl.get()) { m_ptrImpl->performTask(task); } } void WorkerThread::emitTaskResult( BackgroundTaskPtr const& task, FilterResultPtr const& result) { emit taskResult(task, result); } /*======================== WorkerThread::Dispatcher ========================*/ WorkerThread::Dispatcher::Dispatcher(Impl& owner) : m_rOwner(owner) { } WorkerThread::Dispatcher::UpdatePriorityResult WorkerThread::Dispatcher::updateThreadPriority(BackgroundTask const& task) { assert(QCoreApplication::instance()->thread() != QThread::currentThread()); ThreadPriority prio( ThreadPriority::load( "settings/batch_processing_priority", ThreadPriority::Normal ) ); if (task.type() == task.INTERACTIVE) { prio.setValue(ThreadPriority::Normal); } #if defined(Q_OS_LINUX) /* On Linux, there is no good way of adjusting priority of an individual thread of a process. In particular, pthread_setschedparam() only works for realtime threads. Fortunately, the following quote from pthread(7) provides a workaround that's ugly but better than nothing: ---------------------------------------------------- NPTL still has a few non-conformances with POSIX.1: ... Threads do not share a common nice value. ---------------------------------------------------- */ if (setpriority(PRIO_PROCESS, 0, prio.toPosixNiceLevel()) != 0) { if (errno == EACCES) { // Unless the executable has special permissions, lowering // the process nice value (even if it doesn't go below zero) // will fail with this error code. In this case, we restart // the thread. return ThreadRestartRequired; } else { return PriorityUpdateFailed; } } #else // QThread::setPriority() doesn't do anything on Linux BTW. m_rOwner.setPriority(prio.toQThreadPriority()); #endif return PriorityUpdated; } void WorkerThread::Dispatcher::maybeProcessQueuedTask() { if (m_ptrQueuedTask.get()) { BackgroundTaskPtr task; m_ptrQueuedTask.swap(task); // In this case we must not restart the thread if updateThreadPriority() // tells us we should. After all, we've just did that, and doing it again // would probably lead to infinite loop. updateThreadPriority(*task); processTask(task); } } void WorkerThread::Dispatcher::customEvent(QEvent* event) { PerformTaskEvent* evt = dynamic_cast(event); assert(evt); BackgroundTaskPtr const& task = evt->task(); if (updateThreadPriority(*task) == ThreadRestartRequired) { m_ptrQueuedTask = task; m_rOwner.exit(Impl::ExitForRestart); return; } processTask(task); } void WorkerThread::Dispatcher::processTask(BackgroundTaskPtr const& task) { if (task->isCancelled()) { return; } try { FilterResultPtr const result((*task)()); if (result) { QCoreApplication::postEvent( &m_rOwner, new TaskResultEvent(task, result) ); } } catch (std::bad_alloc const&) { OutOfMemoryHandler::instance().handleOutOfMemorySituation(); } } /*========================== WorkerThread::Impl ============================*/ WorkerThread::Impl::Impl(WorkerThread& owner) : m_rOwner(owner), m_dispatcher(*this), m_threadStarted(false) { m_dispatcher.moveToThread(this); } WorkerThread::Impl::~Impl() { exit(NormalExit); wait(); } void WorkerThread::Impl::performTask(BackgroundTaskPtr const& task) { QCoreApplication::postEvent(&m_dispatcher, new PerformTaskEvent(task)); if (!m_threadStarted) { start(); m_threadStarted = true; } } void WorkerThread::Impl::run() { m_dispatcher.maybeProcessQueuedTask(); if (exec() == ExitForRestart) { QCoreApplication::postEvent(this, new QEvent(ThreadRestartEvent)); } } void WorkerThread::Impl::customEvent(QEvent* event) { if (event->type() == ThreadRestartEvent) { start(); return; } if (TaskResultEvent* evt = dynamic_cast(event)) { m_rOwner.emitTaskResult(evt->task(), evt->result()); } } /*====================== WorkerThread::PerformTaskEvent ====================*/ WorkerThread::PerformTaskEvent::PerformTaskEvent( BackgroundTaskPtr const& task) : QEvent(User), m_ptrTask(task) { } /*====================== WorkerThread::TaskResultEvent =====================*/ WorkerThread::TaskResultEvent::TaskResultEvent( BackgroundTaskPtr const& task, FilterResultPtr const& result) : QEvent(User), m_ptrTask(task), m_ptrResult(result) { } scantailor-RELEASE_0_9_12_2/WorkerThread.h000066400000000000000000000032501271170121200202310ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef WORKERTHREAD_H_ #define WORKERTHREAD_H_ #include "NonCopyable.h" #include "BackgroundTask.h" #include "FilterResult.h" #include #include class WorkerThread : public QObject { Q_OBJECT DECLARE_NON_COPYABLE(WorkerThread) public: WorkerThread(QObject* parent = 0); ~WorkerThread(); /** * \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(); public slots: void performTask(BackgroundTaskPtr const& task); signals: void taskResult(BackgroundTaskPtr const& task, FilterResultPtr const& result); private: void emitTaskResult(BackgroundTaskPtr const& task, FilterResultPtr const& result); class Impl; class Dispatcher; class PerformTaskEvent; class TaskResultEvent; std::auto_ptr m_ptrImpl; }; #endif scantailor-RELEASE_0_9_12_2/XmlMarshaller.cpp000066400000000000000000000102261271170121200207370ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "XmlMarshaller.h" #include "OrthogonalRotation.h" #include "Margins.h" #include "Dpi.h" #include "Utils.h" #ifndef Q_MOC_RUN #include #endif #include #include #include #include #include #include #include QDomElement XmlMarshaller::string(QString const& str, QString const& name) { QDomElement el(m_doc.createElement(name)); el.appendChild(m_doc.createTextNode(str)); return el; } QDomElement XmlMarshaller::size(QSize const& size, QString const& name) { if (size.isNull()) { return QDomElement(); } QDomElement el(m_doc.createElement(name)); el.setAttribute("width", size.width()); el.setAttribute("height", size.height()); return el; } QDomElement XmlMarshaller::sizeF(QSizeF const& size, QString const& name) { 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())); return el; } QDomElement XmlMarshaller::dpi(Dpi const& dpi, QString const& name) { if (dpi.isNull()) { return QDomElement(); } QDomElement el(m_doc.createElement(name)); el.setAttribute("horizontal", dpi.horizontal()); el.setAttribute("vertical", dpi.vertical()); return el; } QDomElement XmlMarshaller::rotation(OrthogonalRotation const& rotation, QString const& name) { QDomElement el(m_doc.createElement(name)); el.setAttribute("degrees", rotation.toDegrees()); return el; } QDomElement XmlMarshaller::pointF(QPointF const& p, QString const& name) { QDomElement el(m_doc.createElement(name)); el.setAttribute("x", Utils::doubleToString(p.x())); el.setAttribute("y", Utils::doubleToString(p.y())); return el; } QDomElement XmlMarshaller::lineF(QLineF const& line, QString const& name) { QDomElement el(m_doc.createElement(name)); el.appendChild(pointF(line.p1(), "p1")); el.appendChild(pointF(line.p2(), "p2")); return el; } QDomElement XmlMarshaller::rect(QRect const& rect, QString const& 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())); return el; } QDomElement XmlMarshaller::rectF(QRectF const& rect, QString const& 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())); return el; } QDomElement XmlMarshaller::polygonF(QPolygonF const& poly, QString const& name) { QDomElement el(m_doc.createElement(name)); QPolygonF::const_iterator it(poly.begin()); QPolygonF::const_iterator const end(poly.end()); for (; it != end; ++it) { el.appendChild(pointF(*it, "point")); } return el; } QDomElement XmlMarshaller::margins(Margins const& margins, QString const& 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())); return el; } scantailor-RELEASE_0_9_12_2/XmlMarshaller.h000066400000000000000000000035671271170121200204160ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef XMLMARSHALLER_H_ #define XMLMARSHALLER_H_ #include #include #include class QSize; class QSizeF; class Dpi; class OrthogonalRotation; class Margins; class QPointF; class QLineF; class QPolygonF; class QRect; class QRectF; class XmlMarshaller { public: XmlMarshaller(QDomDocument const& doc) : m_doc(doc) {} QDomElement string(QString const& str, QString const& name); QDomElement size(QSize const& size, QString const& name); QDomElement sizeF(QSizeF const& size, QString const& name); QDomElement dpi(Dpi const& dpi, QString const& name); QDomElement rotation(OrthogonalRotation const& rotation, QString const& name); QDomElement pointF(QPointF const& p, QString const& name); QDomElement lineF(QLineF const& line, QString const& name); QDomElement rect(QRect const& rect, QString const& name); QDomElement rectF(QRectF const& rect, QString const& name); QDomElement polygonF(QPolygonF const& poly, QString const& name); QDomElement margins(Margins const& margins, QString const& name); private: QDomDocument m_doc; }; #endif scantailor-RELEASE_0_9_12_2/XmlUnmarshaller.cpp000066400000000000000000000072301271170121200213030ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "XmlUnmarshaller.h" #include "Dpi.h" #include "OrthogonalRotation.h" #include "Margins.h" #include #include #include #include #include #include #include #include #include QString XmlUnmarshaller::string(QDomElement const& el) { return el.text(); // FIXME: this needs unescaping, but Qt doesn't provide such functionality } QSize XmlUnmarshaller::size(QDomElement const& el) { int const width = el.attribute("width").toInt(); int const height = el.attribute("height").toInt(); return QSize(width, height); } QSizeF XmlUnmarshaller::sizeF(QDomElement const& el) { double const width = el.attribute("width").toDouble(); double const height = el.attribute("height").toDouble(); return QSizeF(width, height); } Dpi XmlUnmarshaller::dpi(QDomElement const& el) { int const hor = el.attribute("horizontal").toInt(); int const ver = el.attribute("vertical").toInt(); return Dpi(hor, ver); } OrthogonalRotation XmlUnmarshaller::rotation(QDomElement const& el) { int const degrees = el.attribute("degrees").toInt(); OrthogonalRotation rotation; for (int i = 0; i < 4; ++i) { if (rotation.toDegrees() == degrees) { break; } rotation.nextClockwiseDirection(); } return rotation; } Margins XmlUnmarshaller::margins(QDomElement const& 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()); return margins; } QPointF XmlUnmarshaller::pointF(QDomElement const& el) { double const x = el.attribute("x").toDouble(); double const y = el.attribute("y").toDouble(); return QPointF(x, y); } QLineF XmlUnmarshaller::lineF(QDomElement const& el) { QPointF const p1(pointF(el.namedItem("p1").toElement())); QPointF const p2(pointF(el.namedItem("p2").toElement())); return QLineF(p1, p2); } QRect XmlUnmarshaller::rect(QDomElement const& el) { int const x = el.attribute("x").toInt(); int const y = el.attribute("y").toInt(); int const width = el.attribute("width").toInt(); int const height = el.attribute("height").toInt(); return QRect(x, y, width, height); } QRectF XmlUnmarshaller::rectF(QDomElement const& el) { double const x = el.attribute("x").toDouble(); double const y = el.attribute("y").toDouble(); double const width = el.attribute("width").toDouble(); double const height = el.attribute("height").toDouble(); return QRectF(x, y, width, height); } QPolygonF XmlUnmarshaller::polygonF(QDomElement const& el) { QPolygonF poly; QString const 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; } scantailor-RELEASE_0_9_12_2/XmlUnmarshaller.h000066400000000000000000000031221271170121200207440ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef XMLUNMARSHALLER_H_ #define XMLUNMARSHALLER_H_ class QString; class QDomElement; class QSize; class QSizeF; class Dpi; class OrthogonalRotation; class Margins; class QPointF; class QLineF; class QRect; class QRectF; class QPolygonF; class XmlUnmarshaller { public: static QString string(QDomElement const& el); static QSize size(QDomElement const& el); static QSizeF sizeF(QDomElement const& el); static Dpi dpi(QDomElement const& el); static OrthogonalRotation rotation(QDomElement const& el); static Margins margins(QDomElement const& el); static QPointF pointF(QDomElement const& el); static QLineF lineF(QDomElement const& el); static QRect rect(QDomElement const& el); static QRectF rectF(QDomElement const& el); static QPolygonF polygonF(QDomElement const& el); }; #endif scantailor-RELEASE_0_9_12_2/cmake/000077500000000000000000000000001271170121200165375ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/cmake/CopyToBuildDir.cmake000066400000000000000000000040371271170121200224010ustar00rootroot00000000000000# 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() # 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() 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("${subdir_}" STREQUAL "") LIST(APPEND "COPY_TO_BUILD_DIR_${conf_}" "${file_}") ELSE() LIST(APPEND "COPY_TO_BUILD_DIR_${conf_}" "${file_}=>${subdir_}") 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() 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() # 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()scantailor-RELEASE_0_9_12_2/cmake/FindPthreads.cmake000066400000000000000000000066311271170121200221220ustar00rootroot00000000000000MACRO(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" "}\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) scantailor-RELEASE_0_9_12_2/cmake/LibToDLL.cmake000066400000000000000000000014671271170121200211160ustar00rootroot00000000000000# 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) 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()scantailor-RELEASE_0_9_12_2/cmake/PatchFile.cmake000066400000000000000000000027711271170121200214070ustar00rootroot00000000000000# Applies a list of search / replace expressions to a file. # If the file is modified, saves a backup with .orig name # appended to the original file name. If a backup already # exists, it's not overwritten. # Example usage: # PATCH_FILE("C:/test.txt" "/apple/orange/" "/@([A-Z_]+)@/@\\1_OLD@") FUNCTION(PATCH_FILE file_) MESSAGE(STATUS "Patching file ${file_}") FILE(READ "${file_}" contents_) SET(orig_contents "${contents_}") FOREACH(search_replace_ ${ARGN}) IF(search_replace_ STREQUAL "") MESSAGE(FATAL_ERROR "Empty string passed as a search/replace expression to PATCH_FILE()") ENDIF() # Parse the string of type /search/replace/ into search_ and _replace variables. # Note that any symbol missing from both search and replace can be used instead of /. STRING(SUBSTRING "${search_replace_}" 0 1 sep_) STRING(REGEX REPLACE "^${sep_}([^${sep_}]+)${sep_}.*" "\\1" search_ "${search_replace_}") STRING(REGEX REPLACE "^${sep_}[^${sep_}]+${sep_}([^${sep_}]*)${sep_}\$" "\\1" replace_ "${search_replace_}") IF(search_replace_ STREQUAL "${replace_}") MESSAGE(FATAL_ERROR "Invalid search/replace expression passed to PATCH_FILE()") ENDIF() STRING(REGEX REPLACE "${search_}" "${replace_}" contents_ "${contents_}") ENDFOREACH() IF(NOT "${contents_}" STREQUAL "${orig_contents_}") # Make a backup copy, if not already there. IF(NOT EXISTS "${file_}.orig") CONFIGURE_FILE("${file_}" "${file_}.orig" COPYONLY) ENDIF() FILE(WRITE "${file_}" "${contents_}") ENDIF() ENDFUNCTION() scantailor-RELEASE_0_9_12_2/cmake/SetDefaultBuildType.cmake000066400000000000000000000005621271170121200234260ustar00rootroot00000000000000MACRO(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) scantailor-RELEASE_0_9_12_2/cmake/SetDefaultGccFlags.cmake000066400000000000000000000103501271170121200231720ustar00rootroot00000000000000INCLUDE(TestCXXAcceptsFlag) MACRO(ST_SET_DEFAULT_GCC_FLAGS) IF(CMAKE_COMPILER_IS_GNUCC AND CMAKE_COMPILER_IS_GNUCXX AND CMAKE_C_FLAGS MATCHES "\\s*" AND CMAKE_CXX_FLAGS MATCHES "\\s*") SET(dead_strip_ldflags_ "") SET(gc_sections_cflags_ "") SET(gc_sections_ldflags_ "") SET(no_inline_dllexport_cflags_ "") CHECK_CXX_ACCEPTS_FLAG( "-ffunction-sections -fdata-sections -Wl,--gc-sections" gc_sections_supported_ ) IF(gc_sections_supported_) SET(gc_sections_cflags_ "-ffunction-sections -fdata-sections") SET(gc_sections_ldflags_ "-Wl,--gc-sections") ENDIF(gc_sections_supported_) CHECK_CXX_ACCEPTS_FLAG("-fno-keep-inline-dllexport" no_inline_dllexport_supported_) IF(no_inline_dllexport_supported_) SET(no_inline_dllexport_cflags_ "-fno-keep-inline-dllexport") ENDIF() IF(MINGW) CHECK_CXX_ACCEPTS_FLAG("-shared-libgcc -static-libstdc++" supported_) IF(supported_) # This is the configuration we want for 32-bit MinGW. # Note that the default for libstdc++ recently changed # from static to shared. We don't want to bundle # another DLL, so we force it back. # For 64-bit MinGW, such configuration is invalid and # fortunately gets rejected. SET(stdlibs_shared_static_ "-shared-libgcc -static-libstdc++") ELSE() # This configuration is used for 64-bit MinGW. SET(stdlibs_shared_static_ "") ENDIF() ENDIF() # GCC on Windows doesn't support -fvisibility, but doesn't reject it either, # printing warnings instead. IF(NOT WIN32) CHECK_CXX_ACCEPTS_FLAG("-fvisibility=hidden" visibility_supported_) IF(visibility_supported_) SET(visibility_cflags_ "-fvisibility=hidden") ELSE(visibility_supported_) SET(visibility_cflags_ "") ENDIF(visibility_supported_) ENDIF() IF(NOT COMPILER_FLAGS_OVERRIDDEN) SET(default_flags_ "-Wall -Wno-unused -ffast-math ${no_inline_dllexport_cflags_}") # Flags common for all build configurations. SET( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${default_flags_}" CACHE STRING "Common C flags for all build configurations." FORCE ) SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${default_flags_} ${stdlibs_shared_static_}" CACHE STRING "Common C++ flags for all build configurations." FORCE ) # Release SET( CMAKE_C_FLAGS_RELEASE "-DNDEBUG -O2 ${visibility_cflags_} ${gc_sections_cflags_}" CACHE STRING "C flags for Release builds." FORCE ) SET( CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG -O2 ${visibility_cflags_} ${gc_sections_cflags_}" CACHE STRING "C++ flags for Release builds." FORCE ) SET( CMAKE_EXE_LINKER_FLAGS_RELEASE "${gc_sections_ldflags_} ${dead_strip_ldflags_}" CACHE STRING "Link flags for Release builds." FORCE ) # MinSizeRel SET( CMAKE_C_FLAGS_MINSIZEREL "-DNDEBUG -Os ${visibility_cflags_} ${gc_sections_cflags_}" CACHE STRING "C flags for MinSizeRel builds." FORCE ) SET( CMAKE_CXX_FLAGS_MINSIZEREL "-DNDEBUG -Os ${visibility_cflags_} ${gc_sections_cflags_}" CACHE STRING "C++ flags for MinSizeRel builds." FORCE ) SET( CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "${gc_sections_ldflags_} ${dead_strip_ldflags_}" CACHE STRING "Link flags for MinSizeRel builds." FORCE ) # RelWithDebInfo SET( CMAKE_C_FLAGS_RELWITHDEBINFO "-DNDEBUG -g -O2 ${visibility_cflags_}" CACHE STRING "C flags for RelWithDebInfo builds." FORCE ) SET( CMAKE_CXX_FLAGS_RELWITHDEBINFO "-DNDEBUG -g -O2 ${visibility_cflags_}" CACHE STRING "C++ flags for RelWithDebInfo builds." FORCE ) SET( CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "" CACHE STRING "Link flags for RelWithDebInfo builds." FORCE ) # Debug SET( CMAKE_C_FLAGS_DEBUG "-DDEBUG -g" CACHE STRING "C flags for Debug builds." FORCE ) SET( CMAKE_CXX_FLAGS_DEBUG "-DDEBUG -g" CACHE STRING "C++ flags for Debug builds." FORCE ) SET( CMAKE_EXE_LINKER_FLAGS_DEBUG "" CACHE STRING "Link flags for Debug builds." FORCE ) SET(COMPILER_FLAGS_OVERRIDDEN YES CACHE INTERNAL "" FORCE) ENDIF(NOT COMPILER_FLAGS_OVERRIDDEN) ENDIF(CMAKE_COMPILER_IS_GNUCC AND CMAKE_COMPILER_IS_GNUCXX AND CMAKE_C_FLAGS MATCHES "\\s*" AND CMAKE_CXX_FLAGS MATCHES "\\s*") ENDMACRO(ST_SET_DEFAULT_GCC_FLAGS) scantailor-RELEASE_0_9_12_2/cmake/ToNativePath.cmake000066400000000000000000000003631271170121200221110ustar00rootroot00000000000000# This macro exists because FILE(TO_NATIVE_PATH ...) is broken on MinGW. MACRO(TO_NATIVE_PATH PATH OUT) FILE(TO_NATIVE_PATH "${PATH}" "${OUT}") IF(MINGW) STRING(REPLACE "/" "\\" "${OUT}" "${${OUT}}") ENDIF(MINGW) ENDMACRO(TO_NATIVE_PATH) scantailor-RELEASE_0_9_12_2/cmake/UpdateTranslations.cmake000066400000000000000000000071671271170121200234000ustar00rootroot00000000000000# 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 # translation. Usually translation sets also correspond to build targets, # though they don't have to. You may use the target name as a translation set # name if you want to. Translation set names can only contain characters # allowed in CMake variable names. # 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) # 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}) # 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>) # # Associates *.ts files with a translation set. # May be called multiple times for different translation sets. # 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() 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(_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) 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() # UPDATE_TRANSLATIONS_TARGET( ) # # Creates a target that updates *.ts files assiciated with the specified # translation sets by FINALIZE_TRANSLATION_SET() # MACRO(UPDATE_TRANSLATIONS_TARGET _target) #, _sets SET(_commands "") FOREACH(_set ${ARGN}) LIST( APPEND _commands COMMAND "${QT_LUPDATE_EXECUTABLE}" -locations absolute -pro "${CMAKE_BINARY_DIR}/update_translations_${_set}.pro" ) ENDFOREACH() ADD_CUSTOM_TARGET(${_target} ${_commands} VERBATIM) ENDMACRO()scantailor-RELEASE_0_9_12_2/cmake/copy_to_build_dir.cmake.in000066400000000000000000000014361271170121200236430ustar00rootroot00000000000000SET("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) 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()scantailor-RELEASE_0_9_12_2/cmake/default_cflags.cmake000066400000000000000000000003651271170121200225100ustar00rootroot00000000000000IF(MSVC) # If we want reliable stack traces, we need /Oy- # We do it only for RelWithDebInfo, because configuration is used with # the built-in crash reporter. SET(CMAKE_C_FLAGS_RELWITHDEBINFO_INIT "/MD /Zi /O2 /Ob1 /D NDEBUG /Oy-") ENDIF() scantailor-RELEASE_0_9_12_2/cmake/default_cxxflags.cmake000066400000000000000000000003671271170121200230720ustar00rootroot00000000000000IF(MSVC) # If we want reliable stack traces, we need /Oy- # We do it only for RelWithDebInfo, because configuration is used with # the built-in crash reporter. SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT "/MD /Zi /O2 /Ob1 /D NDEBUG /Oy-") ENDIF() scantailor-RELEASE_0_9_12_2/cmake/generate_nsi_file.cmake.in000066400000000000000000000041031271170121200236060ustar00rootroot00000000000000INCLUDE("@CMAKE_SOURCE_DIR@/cmake/ToNativePath.cmake") # Translations SET(QM_FILES "@QM_FILES@") SET(INSTALL_TRANSLATIONS "") FOREACH(file ${QM_FILES}) TO_NATIVE_PATH("${file}" file) SET(INSTALL_TRANSLATIONS "${INSTALL_TRANSLATIONS}\n File \"${file}\"") ENDFOREACH() # Imageformat plugins for Qt SET(IMAGEFORMAT_PLUGINS_Debug "@QT_QJPEG_PLUGIN_DEBUG@") SET(IMAGEFORMAT_PLUGINS_Release "@QT_QJPEG_PLUGIN_RELEASE@") SET(IMAGEFORMAT_PLUGINS_MinSizeRel "@QT_QJPEG_PLUGIN_RELEASE@") SET(IMAGEFORMAT_PLUGINS_RelWithDebInfo "@QT_QJPEG_PLUGIN_RELEASE@") SET(INSTALL_IMAGEFORMAT_PLUGINS "") FOREACH(file ${IMAGEFORMAT_PLUGINS_${CFG}}) TO_NATIVE_PATH("${file}" file) LIST( APPEND INSTALL_IMAGEFORMAT_PLUGINS "${INSTALL_IMAGEFORMAT_PLUGINS}\nFile \"${file}\"" ) ENDFOREACH() # Extra binaries (DLLs) 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(INSTALL_EXTRA_BINARIES "") FOREACH(file ${COPY_TO_BUILD_DIR_${CFG}}) TO_NATIVE_PATH("${file}" file) SET(INSTALL_EXTRA_BINARIES "${INSTALL_EXTRA_BINARIES}\n File \"${file}\"") ENDFOREACH() SET(REGISTER_EXTENSION_NSH "@CMAKE_SOURCE_DIR@/packaging/windows/registerExtension.nsh") TO_NATIVE_PATH("${REGISTER_EXTENSION_NSH}" REGISTER_EXTENSION_NSH) SET(LICENSE_FILE "@LICENSE_FILE@") TO_NATIVE_PATH("${LICENSE_FILE}" LICENSE_FILE) SET(INSTALLER_FILENAME "@INSTALLER_FILENAME@") # These are passed at runtime, not at configure time. IF(SCANTAILOR_EXE) TO_NATIVE_PATH("${SCANTAILOR_EXE}" SCANTAILOR_EXE) ENDIF() IF(SCANTAILOR_CLI_EXE) TO_NATIVE_PATH("${SCANTAILOR_CLI_EXE}" SCANTAILOR_CLI_EXE) ENDIF() IF(CRASHREPORTER_EXE) TO_NATIVE_PATH("${CRASHREPORTER_EXE}" CRASHREPORTER_EXE) ENDIF() SET(STAGING_DIR "@CMAKE_BINARY_DIR@/staging") TO_NATIVE_PATH("${STAGING_DIR}" STAGING_DIR) SET(SIZEOF_VOID_PTR "@CMAKE_SIZEOF_VOID_P@") CONFIGURE_FILE( "@CMAKE_SOURCE_DIR@/packaging/windows/scantailor.nsi.in" "@CMAKE_BINARY_DIR@/scantailor.nsi" @ONLY )scantailor-RELEASE_0_9_12_2/cmake/move_sym_file.cmake000066400000000000000000000006451271170121200224030ustar00rootroot00000000000000FILE(STRINGS "${SYMBOLS_PATH}/temp.sym" first_line LIMIT_COUNT 1) SEPARATE_ARGUMENTS(first_line) LIST(GET first_line 3 module_id) LIST(GET first_line 4 module_name_pdb) STRING( REGEX REPLACE "(.*)\\.pdb" "\\1.sym" module_name_sym "${module_name_pdb}" ) CONFIGURE_FILE( "${SYMBOLS_PATH}/temp.sym" "${SYMBOLS_PATH}/${module_name_pdb}/${module_id}/${module_name_sym}" COPYONLY ) FILE(REMOVE "${SYMBOLS_PATH}/temp.sym") scantailor-RELEASE_0_9_12_2/cmake/prepare_staging_dir.cmake.in000066400000000000000000000041111271170121200241530ustar00rootroot00000000000000INCLUDE("@CMAKE_SOURCE_DIR@/cmake/ToNativePath.cmake") SET(MINGW "@MINGW@") SET(STAGING_DIR "@CMAKE_BINARY_DIR@/staging") FILE(REMOVE_RECURSE "${STAGING_DIR}") FILE(MAKE_DIRECTORY "${STAGING_DIR}") # Translations SET(QM_FILES "@QM_FILES@") FOREACH(file ${QM_FILES}) GET_FILENAME_COMPONENT(fname "${file}" NAME) CONFIGURE_FILE("${file}" "${STAGING_DIR}/translations/${fname}" COPYONLY) ENDFOREACH() # Imageformat plugins for Qt FILE(GLOB imageformat_plugins "${CONF_BUILD_DIR}/imageformats/*.dll") FOREACH(file ${imageformat_plugins}) GET_FILENAME_COMPONENT(fname "${file}" NAME) CONFIGURE_FILE("${file}" "${STAGING_DIR}/imageformats/${fname}" COPYONLY) IF(MINGW) EXECUTE_PROCESS(COMMAND "@CMAKE_STRIP@" "${STAGING_DIR}/imageformats/${fname}") ENDIF() ENDFOREACH() # Extra binaries (DLLs) SET(COPY_TO_STAGING_DIR_Debug "@COPY_TO_BUILD_DIR_Debug@") SET(COPY_TO_STAGING_DIR_Release "@COPY_TO_BUILD_DIR_Release@") SET(COPY_TO_STAGING_DIR_MinSizeRel "@COPY_TO_BUILD_DIR_MinSizeRel@") SET(COPY_TO_STAGING_DIR_RelWithDebInfo "@COPY_TO_BUILD_DIR_RelWithDebInfo@") SET(EXTRA_BINARIES ${COPY_TO_STAGING_DIR_${CFG}}) # These are passed at runtime, not at configure time. SET(EXECUTABLES "${SCANTAILOR_EXE}" "${SCANTAILOR_CLI_EXE}" "${CRASHREPORTER_EXE}") # Strip executables and DLLs. FOREACH(src_file ${EXECUTABLES} ${EXTRA_BINARIES}) SET(subdir "") IF(src_file MATCHES ".*=>.*") STRING(REGEX REPLACE ".*=>(.*)" "/\\1" subdir "${src_file}") STRING(REGEX REPLACE "(.*)=>.*" "\\1" src_file "${src_file}") ENDIF() GET_FILENAME_COMPONENT(fname "${src_file}" NAME) SET(dst_file "${STAGING_DIR}${subdir}/${fname}") CONFIGURE_FILE("${src_file}" "${dst_file}" COPYONLY) IF(MINGW AND fname MATCHES ".*\\.(([Dd][Ll][Ll])|([Ee][Xx][Ee]))$") MESSAGE(STATUS "Stripping ${dst_file}") EXECUTE_PROCESS(COMMAND "@CMAKE_STRIP@" "${dst_file}") ENDIF() ENDFOREACH() # Microsoft CRT redistributable IF(CRT_REDIST_PATH) GET_FILENAME_COMPONENT(CRT_REDIST_DIR "${CRT_REDIST_PATH}" NAME) EXECUTE_PROCESS( COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CRT_REDIST_PATH}" "${STAGING_DIR}/${CRT_REDIST_DIR}" ) ENDIF() scantailor-RELEASE_0_9_12_2/compat/000077500000000000000000000000001271170121200167425ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/compat/QAtomicInt000066400000000000000000000024121271170121200206740ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef QATOMICINT_H_ #define QATOMICINT_H_ #include class QAtomicInt { public: QAtomicInt() : m_val(0) {} QAtomicInt(int val) : m_val(val) {} int fetchAndStoreRelaxed(int new_val) { return q_atomic_set_int(&m_val, new_val); } int fetchAndAddRelaxed(int delta) { return q_atomic_fetch_and_add_int(&m_val, delta); } bool ref() { return q_atomic_increment(&m_val) != 0; } bool deref() { return q_atomic_decrement(&m_val) != 0; } private: int m_val; }; #endif scantailor-RELEASE_0_9_12_2/compat/boost_multi_index_foreach_fix.h000066400000000000000000000031071271170121200252000ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef BOOST_MULTI_INDEX_FOREACH_FIX_H_ #define BOOST_MULTI_INDEX_FOREACH_FIX_H_ #include #include #include // BOOST_FOREACH() in boost >= 1.47 has problems with gcc >= 4.6 // These problems aren't specific to boost::multi_index, // but the code below only deals with it. // In future versions of boost, they might include equivalent // code in boost::multi_index itself, which will lead to build problems. // If / when this happens, conditional compilation will be necessary. namespace boost { namespace foreach { template struct is_noncopyable > : mpl::true_ { }; } // namespace foreach } // namespace boost #endif scantailor-RELEASE_0_9_12_2/compat/pstdint.h000066400000000000000000000556201271170121200206100ustar00rootroot00000000000000/* A portable stdint.h * * Copyright (c) 2005-2007 Paul Hsieh * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must not misrepresent the orignal * source in the documentation and/or other materials provided * with the distribution. * * The names of the authors nor its contributors may be used to * endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * **************************************************************************** * * Version 0.1.8 * * The ANSI C standard committee, for the C99 standard, specified the * inclusion of a new standard include file called stdint.h. This is * a very useful and long desired include file which contains several * very precise definitions for integer scalar types that is * critically important for making portable several classes of * applications including cryptography, hashing, variable length * integer libraries and so on. But for most developers its likely * useful just for programming sanity. * * The problem is that most compiler vendors have decided not to * implement the C99 standard, and the next C++ language standard * (which has a lot more mindshare these days) will be a long time in * coming and its unknown whether or not it will include stdint.h or * how much adoption it will have. Either way, it will be a long time * before all compilers come with a stdint.h and it also does nothing * for the extremely large number of compilers available today which * do not include this file, or anything comparable to it. * * So that's what this file is all about. Its an attempt to build a * single universal include file that works on as many platforms as * possible to deliver what stdint.h is supposed to. A few things * that should be noted about this file: * * 1) It is not guaranteed to be portable and/or present an identical * interface on all platforms. The extreme variability of the * ANSI C standard makes this an impossibility right from the * very get go. Its really only meant to be useful for the vast * majority of platforms that possess the capability of * implementing usefully and precisely defined, standard sized * integer scalars. Systems which are not intrinsically 2s * complement may produce invalid constants. * * 2) There is an unavoidable use of non-reserved symbols. * * 3) Other standard include files are invoked. * * 4) This file may come in conflict with future platforms that do * include stdint.h. The hope is that one or the other can be * used with no real difference. * * 5) In the current verison, if your platform can't represent * int32_t, int16_t and int8_t, it just dumps out with a compiler * error. * * 6) 64 bit integers may or may not be defined. Test for their * presence with the test: #ifdef INT64_MAX or #ifdef UINT64_MAX. * Note that this is different from the C99 specification which * requires the existence of 64 bit support in the compiler. If * this is not defined for your platform, yet it is capable of * dealing with 64 bits then it is because this file has not yet * been extended to cover all of your system's capabilities. * * 7) (u)intptr_t may or may not be defined. Test for its presence * with the test: #ifdef PTRDIFF_MAX. If this is not defined * for your platform, then it is because this file has not yet * been extended to cover all of your system's capabilities, not * because its optional. * * 8) The following might not been defined even if your platform is * capable of defining it: * * WCHAR_MIN * WCHAR_MAX * (u)int64_t * PTRDIFF_MIN * PTRDIFF_MAX * (u)intptr_t * * 9) The following have not been defined: * * WINT_MIN * WINT_MAX * * 10) The criteria for defining (u)int_least(*)_t isn't clear, * except for systems which don't have a type that precisely * defined 8, 16, or 32 bit types (which this include file does * not support anyways). Default definitions have been given. * * 11) The criteria for defining (u)int_fast(*)_t isn't something I * would trust to any particular compiler vendor or the ANSI C * committee. It is well known that "compatible systems" are * commonly created that have very different performance * characteristics from the systems they are compatible with, * especially those whose vendors make both the compiler and the * system. Default definitions have been given, but its strongly * recommended that users never use these definitions for any * reason (they do *NOT* deliver any serious guarantee of * improved performance -- not in this file, nor any vendor's * stdint.h). * * 12) The following macros: * * PRINTF_INTMAX_MODIFIER * PRINTF_INT64_MODIFIER * PRINTF_INT32_MODIFIER * PRINTF_INT16_MODIFIER * PRINTF_LEAST64_MODIFIER * PRINTF_LEAST32_MODIFIER * PRINTF_LEAST16_MODIFIER * PRINTF_INTPTR_MODIFIER * * are strings which have been defined as the modifiers required * for the "d", "u" and "x" printf formats to correctly output * (u)intmax_t, (u)int64_t, (u)int32_t, (u)int16_t, (u)least64_t, * (u)least32_t, (u)least16_t and (u)intptr_t types respectively. * PRINTF_INTPTR_MODIFIER is not defined for some systems which * provide their own stdint.h. PRINTF_INT64_MODIFIER is not * defined if INT64_MAX is not defined. These are an extension * beyond what C99 specifies must be in stdint.h. * * In addition, the following macros are defined: * * PRINTF_INTMAX_HEX_WIDTH * PRINTF_INT64_HEX_WIDTH * PRINTF_INT32_HEX_WIDTH * PRINTF_INT16_HEX_WIDTH * PRINTF_INT8_HEX_WIDTH * PRINTF_INTMAX_DEC_WIDTH * PRINTF_INT64_DEC_WIDTH * PRINTF_INT32_DEC_WIDTH * PRINTF_INT16_DEC_WIDTH * PRINTF_INT8_DEC_WIDTH * * Which specifies the maximum number of characters required to * print the number of that type in either hexadecimal or decimal. * These are an extension beyond what C99 specifies must be in * stdint.h. * * Compilers tested (all with 0 warnings at their highest respective * settings): Borland Turbo C 2.0, WATCOM C/C++ 11.0 (16 bits and 32 * bits), Microsoft Visual C++ 6.0 (32 bit), Microsoft Visual Studio * .net (VC7), Intel C++ 4.0, GNU gcc v3.3.3 * * This file should be considered a work in progress. Suggestions for * improvements, especially those which increase coverage are strongly * encouraged. * * Acknowledgements * * The following people have made significant contributions to the * development and testing of this file: * * Chris Howie * John Steele Scott * Dave Thorup * */ #include #include #include /* * For gcc with _STDINT_H, fill in the PRINTF_INT*_MODIFIER macros, and * do nothing else. On the Mac OS X version of gcc this is _STDINT_H_. */ #if ((defined(__STDC__) && __STDC__ && __STDC_VERSION__ >= 199901L) || (defined (__WATCOMC__) && (defined (_STDINT_H_INCLUDED) || __WATCOMC__ >= 1250)) || (defined(__GNUC__) && (defined(_STDINT_H) || defined(_STDINT_H_)) )) && !defined (_PSTDINT_H_INCLUDED) #include #define _PSTDINT_H_INCLUDED # ifndef PRINTF_INT64_MODIFIER # define PRINTF_INT64_MODIFIER "ll" # endif # ifndef PRINTF_INT32_MODIFIER # define PRINTF_INT32_MODIFIER "l" # endif # ifndef PRINTF_INT16_MODIFIER # define PRINTF_INT16_MODIFIER "h" # endif # ifndef PRINTF_INTMAX_MODIFIER # define PRINTF_INTMAX_MODIFIER PRINTF_INT64_MODIFIER # endif # ifndef PRINTF_INT64_HEX_WIDTH # define PRINTF_INT64_HEX_WIDTH "16" # endif # ifndef PRINTF_INT32_HEX_WIDTH # define PRINTF_INT32_HEX_WIDTH "8" # endif # ifndef PRINTF_INT16_HEX_WIDTH # define PRINTF_INT16_HEX_WIDTH "4" # endif # ifndef PRINTF_INT8_HEX_WIDTH # define PRINTF_INT8_HEX_WIDTH "2" # endif # ifndef PRINTF_INT64_DEC_WIDTH # define PRINTF_INT64_DEC_WIDTH "20" # endif # ifndef PRINTF_INT32_DEC_WIDTH # define PRINTF_INT32_DEC_WIDTH "10" # endif # ifndef PRINTF_INT16_DEC_WIDTH # define PRINTF_INT16_DEC_WIDTH "5" # endif # ifndef PRINTF_INT8_DEC_WIDTH # define PRINTF_INT8_DEC_WIDTH "3" # endif # ifndef PRINTF_INTMAX_HEX_WIDTH # define PRINTF_INTMAX_HEX_WIDTH PRINTF_INT64_HEX_WIDTH # endif # ifndef PRINTF_INTMAX_DEC_WIDTH # define PRINTF_INTMAX_DEC_WIDTH PRINTF_INT64_DEC_WIDTH # endif #endif #ifndef _PSTDINT_H_INCLUDED #define _PSTDINT_H_INCLUDED #ifndef SIZE_MAX # define SIZE_MAX (~(size_t)0) #endif /* * Deduce the type assignments from limits.h under the assumption that * integer sizes in bits are powers of 2, and follow the ANSI * definitions. */ #ifndef UINT8_MAX # define UINT8_MAX 0xff #endif #ifndef uint8_t # if (UCHAR_MAX == UINT8_MAX) || defined (S_SPLINT_S) typedef unsigned char uint8_t; # ifndef UINT8_C # define UINT8_C(v) ((uint8_t) v) # endif # else # error "Platform not supported" # endif #endif #ifndef INT8_MAX # define INT8_MAX 0x7f #endif #ifndef INT8_MIN # define INT8_MIN INT8_C(0x80) #endif #ifndef int8_t # if (SCHAR_MAX == INT8_MAX) || defined (S_SPLINT_S) typedef signed char int8_t; # ifndef INT8_C # define INT8_C(v) ((int8_t) v) # endif # else # error "Platform not supported" # endif #endif #ifndef UINT16_MAX # define UINT16_MAX 0xffff #endif #ifndef uint16_t #if (UINT_MAX == UINT16_MAX) || defined (S_SPLINT_S) typedef unsigned int uint16_t; # ifndef PRINTF_INT16_MODIFIER # define PRINTF_INT16_MODIFIER "" # endif # ifndef UINT16_C # define UINT16_C(v) ((uint16_t) (v)) # endif #elif (USHRT_MAX == UINT16_MAX) typedef unsigned short uint16_t; # ifndef UINT16_C # define UINT16_C(v) ((uint16_t) (v)) # endif # ifndef PRINTF_INT16_MODIFIER # define PRINTF_INT16_MODIFIER "h" # endif #else #error "Platform not supported" #endif #endif #ifndef INT16_MAX # define INT16_MAX 0x7fff #endif #ifndef INT16_MIN # define INT16_MIN INT16_C(0x8000) #endif #ifndef int16_t #if (INT_MAX == INT16_MAX) || defined (S_SPLINT_S) typedef signed int int16_t; # ifndef INT16_C # define INT16_C(v) ((int16_t) (v)) # endif # ifndef PRINTF_INT16_MODIFIER # define PRINTF_INT16_MODIFIER "" # endif #elif (SHRT_MAX == INT16_MAX) typedef signed short int16_t; # ifndef INT16_C # define INT16_C(v) ((int16_t) (v)) # endif # ifndef PRINTF_INT16_MODIFIER # define PRINTF_INT16_MODIFIER "h" # endif #else #error "Platform not supported" #endif #endif #ifndef UINT32_MAX # define UINT32_MAX (0xffffffffUL) #endif #ifndef uint32_t #if (ULONG_MAX == UINT32_MAX) || defined (S_SPLINT_S) typedef unsigned long uint32_t; # ifndef UINT32_C # define UINT32_C(v) v ## UL # endif # ifndef PRINTF_INT32_MODIFIER # define PRINTF_INT32_MODIFIER "l" # endif #elif (UINT_MAX == UINT32_MAX) typedef unsigned int uint32_t; # ifndef PRINTF_INT32_MODIFIER # define PRINTF_INT32_MODIFIER "" # endif # ifndef UINT32_C # define UINT32_C(v) v ## U # endif #elif (USHRT_MAX == UINT32_MAX) typedef unsigned short uint32_t; # ifndef UINT32_C # define UINT32_C(v) ((unsigned short) (v)) # endif # ifndef PRINTF_INT32_MODIFIER # define PRINTF_INT32_MODIFIER "" # endif #else #error "Platform not supported" #endif #endif #ifndef INT32_MAX # define INT32_MAX (0x7fffffffL) #endif #ifndef INT32_MIN # define INT32_MIN INT32_C(0x80000000) #endif #ifndef int32_t #if (LONG_MAX == INT32_MAX) || defined (S_SPLINT_S) typedef signed long int32_t; # ifndef INT32_C # define INT32_C(v) v ## L # endif # ifndef PRINTF_INT32_MODIFIER # define PRINTF_INT32_MODIFIER "l" # endif #elif (INT_MAX == INT32_MAX) typedef signed int int32_t; # ifndef INT32_C # define INT32_C(v) v # endif # ifndef PRINTF_INT32_MODIFIER # define PRINTF_INT32_MODIFIER "" # endif #elif (SHRT_MAX == INT32_MAX) typedef signed short int32_t; # ifndef INT32_C # define INT32_C(v) ((short) (v)) # endif # ifndef PRINTF_INT32_MODIFIER # define PRINTF_INT32_MODIFIER "" # endif #else #error "Platform not supported" #endif #endif /* * The macro stdint_int64_defined is temporarily used to record * whether or not 64 integer support is available. It must be * defined for any 64 integer extensions for new platforms that are * added. */ #undef stdint_int64_defined #if (defined(__STDC__) && defined(__STDC_VERSION__)) || defined (S_SPLINT_S) # if (__STDC__ && __STDC_VERSION >= 199901L) || defined (S_SPLINT_S) # define stdint_int64_defined typedef long long int64_t; typedef unsigned long long uint64_t; # ifndef UINT64_C # define UINT64_C(v) v ## ULL # endif # ifndef INT64_C # define INT64_C(v) v ## LL # endif # ifndef PRINTF_INT64_MODIFIER # define PRINTF_INT64_MODIFIER "ll" # endif # endif #endif #if !defined (stdint_int64_defined) # if defined(__GNUC__) # define stdint_int64_defined __extension__ typedef long long int64_t; __extension__ typedef unsigned long long uint64_t; # ifndef UINT64_C # define UINT64_C(v) v ## ULL # endif # ifndef INT64_C # define INT64_C(v) v ## LL # endif # ifndef PRINTF_INT64_MODIFIER # define PRINTF_INT64_MODIFIER "ll" # endif # elif defined(__MWERKS__) || defined (__SUNPRO_C) || defined (__SUNPRO_CC) || defined (__APPLE_CC__) || defined (_LONG_LONG) || defined (_CRAYC) || defined (S_SPLINT_S) # define stdint_int64_defined typedef long long int64_t; typedef unsigned long long uint64_t; # ifndef UINT64_C # define UINT64_C(v) v ## ULL # endif # ifndef INT64_C # define INT64_C(v) v ## LL # endif # ifndef PRINTF_INT64_MODIFIER # define PRINTF_INT64_MODIFIER "ll" # endif # elif (defined(__WATCOMC__) && defined(__WATCOM_INT64__)) || (defined(_MSC_VER) && _INTEGRAL_MAX_BITS >= 64) || (defined (__BORLANDC__) && __BORLANDC__ > 0x460) || defined (__alpha) || defined (__DECC) # define stdint_int64_defined typedef __int64 int64_t; typedef unsigned __int64 uint64_t; # ifndef UINT64_C # define UINT64_C(v) v ## UI64 # endif # ifndef INT64_C # define INT64_C(v) v ## I64 # endif # ifndef PRINTF_INT64_MODIFIER # define PRINTF_INT64_MODIFIER "I64" # endif # endif #endif #if !defined (LONG_LONG_MAX) && defined (INT64_C) # define LONG_LONG_MAX INT64_C (9223372036854775807) #endif #ifndef ULONG_LONG_MAX # define ULONG_LONG_MAX UINT64_C (18446744073709551615) #endif #if !defined (INT64_MAX) && defined (INT64_C) # define INT64_MAX INT64_C (9223372036854775807) #endif #if !defined (INT64_MIN) && defined (INT64_C) # define INT64_MIN INT64_C (-9223372036854775808) #endif #if !defined (UINT64_MAX) && defined (INT64_C) # define UINT64_MAX UINT64_C (18446744073709551615) #endif /* * Width of hexadecimal for number field. */ #ifndef PRINTF_INT64_HEX_WIDTH # define PRINTF_INT64_HEX_WIDTH "16" #endif #ifndef PRINTF_INT32_HEX_WIDTH # define PRINTF_INT32_HEX_WIDTH "8" #endif #ifndef PRINTF_INT16_HEX_WIDTH # define PRINTF_INT16_HEX_WIDTH "4" #endif #ifndef PRINTF_INT8_HEX_WIDTH # define PRINTF_INT8_HEX_WIDTH "2" #endif #ifndef PRINTF_INT64_DEC_WIDTH # define PRINTF_INT64_DEC_WIDTH "20" #endif #ifndef PRINTF_INT32_DEC_WIDTH # define PRINTF_INT32_DEC_WIDTH "10" #endif #ifndef PRINTF_INT16_DEC_WIDTH # define PRINTF_INT16_DEC_WIDTH "5" #endif #ifndef PRINTF_INT8_DEC_WIDTH # define PRINTF_INT8_DEC_WIDTH "3" #endif /* * Ok, lets not worry about 128 bit integers for now. Moore's law says * we don't need to worry about that until about 2040 at which point * we'll have bigger things to worry about. */ #ifdef stdint_int64_defined typedef int64_t intmax_t; typedef uint64_t uintmax_t; # ifndef INTMAX_MAX # define INTMAX_MAX INT64_MAX # endif # ifndef INTMAX_MIN # define INTMAX_MIN INT64_MIN # endif # ifndef UINTMAX_MAX # define UINTMAX_MAX UINT64_MAX # endif # ifndef UINTMAX_C # define UINTMAX_C(v) UINT64_C(v) # endif # ifndef INTMAX_C # define INTMAX_C(v) INT64_C(v) # endif # ifndef PRINTF_INTMAX_MODIFIER # define PRINTF_INTMAX_MODIFIER PRINTF_INT64_MODIFIER # endif # ifndef PRINTF_INTMAX_HEX_WIDTH # define PRINTF_INTMAX_HEX_WIDTH PRINTF_INT64_HEX_WIDTH # endif # ifndef PRINTF_INTMAX_DEC_WIDTH # define PRINTF_INTMAX_DEC_WIDTH PRINTF_INT64_DEC_WIDTH # endif #else typedef int32_t intmax_t; typedef uint32_t uintmax_t; # ifndef INTMAX_MAX # define INTMAX_MAX INT32_MAX # endif # ifndef UINTMAX_MAX # define UINTMAX_MAX UINT32_MAX # endif # ifndef UINTMAX_C # define UINTMAX_C(v) UINT32_C(v) # endif # ifndef INTMAX_C # define INTMAX_C(v) INT32_C(v) # endif # ifndef PRINTF_INTMAX_MODIFIER # define PRINTF_INTMAX_MODIFIER PRINTF_INT32_MODIFIER # endif # ifndef PRINTF_INTMAX_HEX_WIDTH # define PRINTF_INTMAX_HEX_WIDTH PRINTF_INT32_HEX_WIDTH # endif # ifndef PRINTF_INTMAX_DEC_WIDTH # define PRINTF_INTMAX_DEC_WIDTH PRINTF_INT32_DEC_WIDTH # endif #endif /* * Because this file currently only supports platforms which have * precise powers of 2 as bit sizes for the default integers, the * least definitions are all trivial. Its possible that a future * version of this file could have different definitions. */ #ifndef stdint_least_defined typedef int8_t int_least8_t; typedef uint8_t uint_least8_t; typedef int16_t int_least16_t; typedef uint16_t uint_least16_t; typedef int32_t int_least32_t; typedef uint32_t uint_least32_t; # define PRINTF_LEAST32_MODIFIER PRINTF_INT32_MODIFIER # define PRINTF_LEAST16_MODIFIER PRINTF_INT16_MODIFIER # define UINT_LEAST8_MAX UINT8_MAX # define INT_LEAST8_MAX INT8_MAX # define UINT_LEAST16_MAX UINT16_MAX # define INT_LEAST16_MAX INT16_MAX # define UINT_LEAST32_MAX UINT32_MAX # define INT_LEAST32_MAX INT32_MAX # define INT_LEAST8_MIN INT8_MIN # define INT_LEAST16_MIN INT16_MIN # define INT_LEAST32_MIN INT32_MIN # ifdef stdint_int64_defined typedef int64_t int_least64_t; typedef uint64_t uint_least64_t; # define PRINTF_LEAST64_MODIFIER PRINTF_INT64_MODIFIER # define UINT_LEAST64_MAX UINT64_MAX # define INT_LEAST64_MAX INT64_MAX # define INT_LEAST64_MIN INT64_MIN # endif #endif #undef stdint_least_defined /* * The ANSI C committee pretending to know or specify anything about * performance is the epitome of misguided arrogance. The mandate of * this file is to *ONLY* ever support that absolute minimum * definition of the fast integer types, for compatibility purposes. * No extensions, and no attempt to suggest what may or may not be a * faster integer type will ever be made in this file. Developers are * warned to stay away from these types when using this or any other * stdint.h. */ typedef int_least8_t int_fast8_t; typedef uint_least8_t uint_fast8_t; typedef int_least16_t int_fast16_t; typedef uint_least16_t uint_fast16_t; typedef int_least32_t int_fast32_t; typedef uint_least32_t uint_fast32_t; #define UINT_FAST8_MAX UINT_LEAST8_MAX #define INT_FAST8_MAX INT_LEAST8_MAX #define UINT_FAST16_MAX UINT_LEAST16_MAX #define INT_FAST16_MAX INT_LEAST16_MAX #define UINT_FAST32_MAX UINT_LEAST32_MAX #define INT_FAST32_MAX INT_LEAST32_MAX #define INT_FAST8_MIN IN_LEASTT8_MIN #define INT_FAST16_MIN INT_LEAST16_MIN #define INT_FAST32_MIN INT_LEAST32_MIN #ifdef stdint_int64_defined typedef int_least64_t int_fast64_t; typedef uint_least64_t uint_fast64_t; # define UINT_FAST64_MAX UINT_LEAST64_MAX # define INT_FAST64_MAX INT_LEAST64_MAX # define INT_FAST64_MIN INT_LEAST64_MIN #endif #undef stdint_int64_defined /* * Whatever piecemeal, per compiler thing we can do about the wchar_t * type limits. */ #if defined(__WATCOMC__) || defined(_MSC_VER) || defined (__GNUC__) # include # ifndef WCHAR_MIN # define WCHAR_MIN 0 # endif # ifndef WCHAR_MAX # define WCHAR_MAX ((wchar_t)-1) # endif #endif /* * Whatever piecemeal, per compiler/platform thing we can do about the * (u)intptr_t types and limits. */ #if defined (_MSC_VER) && defined (_UINTPTR_T_DEFINED) # define STDINT_H_UINTPTR_T_DEFINED #endif #ifndef STDINT_H_UINTPTR_T_DEFINED # if defined (__alpha__) || defined (__ia64__) || defined (__x86_64__) || defined (_WIN64) # define stdint_intptr_bits 64 # elif defined (__WATCOMC__) || defined (__TURBOC__) # if defined(__TINY__) || defined(__SMALL__) || defined(__MEDIUM__) # define stdint_intptr_bits 16 # else # define stdint_intptr_bits 32 # endif # elif defined (__i386__) || defined (_WIN32) || defined (WIN32) # define stdint_intptr_bits 32 # elif defined (__INTEL_COMPILER) /* TODO -- what will Intel do about x86-64? */ # endif # ifdef stdint_intptr_bits # define stdint_intptr_glue3_i(a,b,c) a##b##c # define stdint_intptr_glue3(a,b,c) stdint_intptr_glue3_i(a,b,c) # ifndef PRINTF_INTPTR_MODIFIER # define PRINTF_INTPTR_MODIFIER stdint_intptr_glue3(PRINTF_INT,stdint_intptr_bits,_MODIFIER) # endif # ifndef PTRDIFF_MAX # define PTRDIFF_MAX stdint_intptr_glue3(INT,stdint_intptr_bits,_MAX) # endif # ifndef PTRDIFF_MIN # define PTRDIFF_MIN stdint_intptr_glue3(INT,stdint_intptr_bits,_MIN) # endif # ifndef UINTPTR_MAX # define UINTPTR_MAX stdint_intptr_glue3(UINT,stdint_intptr_bits,_MAX) # endif # ifndef INTPTR_MAX # define INTPTR_MAX stdint_intptr_glue3(INT,stdint_intptr_bits,_MAX) # endif # ifndef INTPTR_MIN # define INTPTR_MIN stdint_intptr_glue3(INT,stdint_intptr_bits,_MIN) # endif # ifndef INTPTR_C # define INTPTR_C(x) stdint_intptr_glue3(INT,stdint_intptr_bits,_C)(x) # endif # ifndef UINTPTR_C # define UINTPTR_C(x) stdint_intptr_glue3(UINT,stdint_intptr_bits,_C)(x) # endif typedef stdint_intptr_glue3(uint,stdint_intptr_bits,_t) uintptr_t; typedef stdint_intptr_glue3( int,stdint_intptr_bits,_t) intptr_t; # else /* TODO -- This following is likely wrong for some platforms, and does nothing for the definition of uintptr_t. */ typedef ptrdiff_t intptr_t; # endif # define STDINT_H_UINTPTR_T_DEFINED #endif /* * Assumes sig_atomic_t is signed and we have a 2s complement machine. */ #ifndef SIG_ATOMIC_MAX # define SIG_ATOMIC_MAX ((((sig_atomic_t) 1) << (sizeof (sig_atomic_t)*CHAR_BIT-1)) - 1) #endif #endif scantailor-RELEASE_0_9_12_2/config.h.in000066400000000000000000000004571271170121200175100ustar00rootroot00000000000000/* This file is a template for config.h, for use with CMake. */ #ifndef SCANTAILOR_CONFIG_H_ #define SCANTAILOR_CONFIG_H_ #define TRANSLATIONS_DIR_REL "@TRANSLATIONS_DIR_REL@" #define TRANSLATIONS_DIR_ABS "@TRANSLATIONS_DIR_ABS@" #cmakedefine ENABLE_CRASH_REPORTER #cmakedefine ENABLE_OPENGL #endif scantailor-RELEASE_0_9_12_2/crash_reporter/000077500000000000000000000000001271170121200205015ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/crash_reporter/CMakeLists.txt000066400000000000000000000033041271170121200232410ustar00rootroot00000000000000PROJECT(CrashReporter) ADD_DEFINITIONS(-D_UNICODE) INCLUDE_DIRECTORIES(google-breakpad) MACRO(LIST_ITEMS_PREPEND LIST PREFIX) SET(tmp_list_) FOREACH(item ${${LIST}}) LIST(APPEND tmp_list_ "${PREFIX}${item}") ENDFOREACH(item) SET(${LIST} ${tmp_list_}) ENDMACRO(LIST_ITEMS_PREPEND) SET(eh_sources exception_handler.cc exception_handler.h) LIST_ITEMS_PREPEND(eh_sources google-breakpad/client/windows/handler/) SET(cg_sources crash_generation_client.cc crash_generation_client.h) LIST_ITEMS_PREPEND(cg_sources google-breakpad/client/windows/crash_generation/) SET(common_sources guid_string.cc guid_string.h) LIST_ITEMS_PREPEND(common_sources google-breakpad/common/windows/) IF(ENABLE_CRASH_REPORTER) ADD_LIBRARY(crash_handler ${eh_sources} ${cg_sources} ${common_sources}) ENDIF(ENABLE_CRASH_REPORTER) SET( crash_reporter_sources main.cpp CrashReportDialog.cpp CrashReportDialog.h MultipartFormData.cpp MultipartFormData.h ) SOURCE_GROUP("Sources" FILES ${crash_reporter_sources}) QT4_AUTOMOC(${crash_reporter_sources}) FILE(GLOB ui_files "ui/*.ui") SOURCE_GROUP("Ui Files" FILES ${ui_files}) QT4_WRAP_UI(ui_sources ${ui_files}) SET_SOURCE_FILES_PROPERTIES(${ui_sources} PROPERTIES GENERATED TRUE) ADD_SUBDIRECTORY(ui) INCLUDE_DIRECTORIES("${CMAKE_CURRENT_BINARY_DIR}") # for ui files IF(ENABLE_CRASH_REPORTER) ADD_EXECUTABLE(CrashReporter WIN32 ${crash_reporter_sources} ${ui_sources}) TARGET_LINK_LIBRARIES( CrashReporter ${QT_QTGUI_LIBRARY} ${QT_QTXML_LIBRARY} ${QT_QTNETWORK_LIBRARY} ${QT_QTXML_LIBRARY} ${QT_QTCORE_LIBRARY} ${QT_QTMAIN_LIBRARY} ${PNG_LIBRARY} ${JPEG_LIBRARY} winmm imm32 ws2_32 ) ENDIF(ENABLE_CRASH_REPORTER) TRANSLATION_SOURCES(crashreporter ${crash_reporter_sources} ${ui_files})scantailor-RELEASE_0_9_12_2/crash_reporter/CrashReportDialog.cpp000066400000000000000000000212011271170121200245550ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "CrashReportDialog.h" #include "CrashReportDialog.h.moc" #include "MultipartFormData.h" #include "version.h" #include #include #include // for Qt::escape() #include #include #include #include #include #include #include #include #include #include #include #include #include #include class CrashReportDialog::SystemProxyFactory : public QNetworkProxyFactory { public: virtual QList queryProxy( QNetworkProxyQuery const& query = QNetworkProxyQuery()) { return systemProxyForQuery(query); } }; CrashReportDialog::CrashReportDialog( QString const& dir, QString const& id, QWidget* parent) : QDialog(parent), m_dumpFileInfo(QDir(dir), id+".dmp"), m_normalPalette(QApplication::palette()), m_errorPalette(m_normalPalette), m_successPalette(m_normalPalette), m_pDispatcher(new QNetworkAccessManager(this)), m_pSubmitter(new QNetworkAccessManager(this)), m_disregardAdditionalInfo(true), m_disregardEmail(true) { m_pDispatcher->setProxyFactory(new SystemProxyFactory); m_pSubmitter->setProxyFactory(new SystemProxyFactory); ui.setupUi(this); ui.statusLabel->setText(" "); m_errorPalette.setColor(QPalette::WindowText, Qt::red); m_successPalette.setColor(QPalette::WindowText, Qt::green); ui.dumpFile->setToolTip(tr( "This file contains the internal state of the program when it crashed" )); ui.dumpFile->setText( QString("%2 (%3)").arg( Qt::escape(QUrl::fromLocalFile(m_dumpFileInfo.absoluteFilePath()).toString()), Qt::escape(tr("Dump file")), formatFileSize(m_dumpFileInfo.size()) ) ); ui.dumpFile->setOpenExternalLinks(true); QPalette graytext_palette(m_normalPalette); graytext_palette.setColor(QPalette::Text, QColor(118, 118, 118)); ui.additionalInfo->setPalette(graytext_palette); ui.email->setPalette(graytext_palette); ui.buttonBox->button(QDialogButtonBox::Ok)->setFocus(); // To catch FocusIn events and clear text / restore palette. ui.additionalInfo->installEventFilter(this); ui.email->installEventFilter(this); connect(ui.buttonBox, SIGNAL(accepted()), SLOT(onSubmit())); } CrashReportDialog::~CrashReportDialog() { QFile::remove(m_dumpFileInfo.absoluteFilePath()); } void CrashReportDialog::onSubmit() { ui.statusLabel->setText(tr("Sending ...")); ui.statusLabel->setPalette(m_normalPalette); ui.infoGroup->setEnabled(false); ui.buttonBox->setEnabled(false); QUrl url("http://scantailor.sourceforge.net/crash_dispatcher/"); url.addQueryItem("version", VERSION); url.addQueryItem("locale", QLocale::system().name()); m_pDispatcher->get(QNetworkRequest(url)); connect( m_pDispatcher, SIGNAL(finished(QNetworkReply*)), SLOT(dispatchDone(QNetworkReply*)) ); } void CrashReportDialog::dispatchDone(QNetworkReply* reply) { reply->deleteLater(); ui.infoGroup->setEnabled(true); ui.buttonBox->setEnabled(true); if (reply->error() != QNetworkReply::NoError) { ui.statusLabel->setText(reply->errorString()); ui.statusLabel->setPalette(m_errorPalette); return; } int const status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (status != 200) { ui.statusLabel->setText(tr("Unexpected response (code %1) from dispatcher").arg(status)); ui.statusLabel->setPalette(m_errorPalette); return; } QDomDocument doc; QString errmsg; if (!doc.setContent(reply->readAll(), true, &errmsg)) { ui.statusLabel->setText(errmsg); ui.statusLabel->setPalette(m_errorPalette); return; } QDomElement doc_el(doc.documentElement()); QDomElement reject_el(doc_el.namedItem("reject").toElement()); if (!reject_el.isNull()) { ui.statusLabel->setText(reject_el.text()); ui.statusLabel->setPalette(m_errorPalette); return; } QDomElement forward_el(doc_el.namedItem("forward").toElement()); if (forward_el.isNull()) { ui.statusLabel->setText(tr("Unexpected response from dispatcher")); ui.statusLabel->setPalette(m_errorPalette); return; } MultipartFormData form_data; QUrl const url(forward_el.attribute("url")); QDomNodeList const fields(forward_el.childNodes()); for (int i = 0; i < fields.count(); ++i) { QDomElement field(fields.item(i).toElement()); if (field.isNull()) { continue; } if (field.localName() == "fixed") { form_data.addParameter(field.attribute("name"), field.text()); } else if (field.localName() == "mapped") { QString const name(field.attribute("name")); QString const var(field.text()); if (var == "details") { form_data.addParameter(name, prepareDetails()); } else if (var == "file") { QFile file(m_dumpFileInfo.absoluteFilePath()); file.open(QIODevice::ReadOnly); form_data.addFile(name, m_dumpFileInfo.fileName(), file.readAll()); } } } QNetworkRequest request(url); request.setHeader(QNetworkRequest::ContentTypeHeader, form_data.contentType()); request.setRawHeader("User-Agent", "Scan Tailor crash reporter"); request.setRawHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); m_pSubmitter->post(request, form_data.finalize()); connect( m_pSubmitter, SIGNAL(finished(QNetworkReply*)), SLOT(submissionDone(QNetworkReply*)) ); ui.infoGroup->setEnabled(false); ui.buttonBox->setEnabled(false); } void CrashReportDialog::submissionDone(QNetworkReply* reply) { reply->deleteLater(); ui.infoGroup->setEnabled(true); ui.buttonBox->setEnabled(true); if (reply->error() != QNetworkReply::NoError) { ui.statusLabel->setText(reply->errorString()); ui.statusLabel->setPalette(m_errorPalette); return; } int const status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (status != 200) { ui.statusLabel->setText( tr("Unexpected response (code %1) from the receiving side").arg(status) ); ui.statusLabel->setPalette(m_errorPalette); return; } ui.statusLabel->setText(tr("Successfully sent")); ui.statusLabel->setPalette(m_successPalette); ui.infoGroup->setEnabled(false); ui.buttonBox->setStandardButtons(QDialogButtonBox::Close); } bool CrashReportDialog::eventFilter(QObject* watched, QEvent* event) { if (event->type() == QEvent::FocusIn) { if (watched == ui.additionalInfo) { if (m_disregardAdditionalInfo) { ui.additionalInfo->setPalette(m_normalPalette); ui.additionalInfo->clear(); m_disregardAdditionalInfo = false; } } else if (watched == ui.email) { if (m_disregardEmail) { ui.email->setPalette(m_normalPalette); ui.email->clear(); m_disregardEmail = false; } } } return false; // Don't filter out the event. } QString CrashReportDialog::formatFileSize(qint64 size) { qint64 const kb = (size + 1023) / 1024; if (kb < 1000) { return QString("%1 KB").arg((size + 1023) / 1024); } else { return QString("%1 MB").arg(size / (1024.0*1024), 0, 'g', 1); } } QString CrashReportDialog::prepareDetails() const { QString text; text += "This crash report was sent using Scan Tailor's built-in crash reporter.\n\n"; text += "Scan Tailor version: " VERSION "\n\n"; QString const additional_info(ui.additionalInfo->toPlainText().trimmed()); if (!m_disregardAdditionalInfo && !additional_info.isEmpty()) { text += "Additional information provided by the user:"; text += "\n--------------------------------------------------------------\n"; text += additional_info; text += "\n--------------------------------------------------------------\n"; } QString const email(ui.email->text().trimmed()); if (!m_disregardEmail && !email.isEmpty()) { text += "Contact email: "+email; } return text; } scantailor-RELEASE_0_9_12_2/crash_reporter/CrashReportDialog.h000066400000000000000000000034411271170121200242300ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef CRASH_REPORTER_DIALOG_H_ #define CRASH_REPORTER_DIALOG_H_ #include "ui_CrashReportDialog.h" #include #include #include #include #include #include class QNetworkAccessManager; class QNetworkReply; class CrashReportDialog : public QDialog { Q_OBJECT public: CrashReportDialog(QString const& dir, QString const& id, QWidget* parent = 0); ~CrashReportDialog(); protected: virtual bool eventFilter(QObject* watched, QEvent* event); private slots: void onSubmit(); void dispatchDone(QNetworkReply* reply); void submissionDone(QNetworkReply* reply); private: class SystemProxyFactory; static QString formatFileSize(qint64 size); QString prepareDetails() const; Ui::CrashReportDialog ui; QFileInfo m_dumpFileInfo; QPalette m_normalPalette; QPalette m_errorPalette; QPalette m_successPalette; QNetworkAccessManager* m_pDispatcher; QNetworkAccessManager* m_pSubmitter; bool m_disregardAdditionalInfo; bool m_disregardEmail; }; #endif scantailor-RELEASE_0_9_12_2/crash_reporter/MultipartFormData.cpp000066400000000000000000000035371271170121200246140ustar00rootroot00000000000000#include "MultipartFormData.h" #include MultipartFormData::MultipartFormData() { generateBoundary(m_boundary); } QString MultipartFormData::contentType() const { return "multipart/form-data; boundary="+QString::fromAscii(m_boundary); } void MultipartFormData::addParameter(QString const& name, QString const& value) { m_body.append("--"); m_body.append(m_boundary); m_body.append("\r\n"); m_body.append("Content-Disposition: form-data; name=\""); m_body.append(name.toUtf8()); m_body.append("\"\r\n\r\n"); m_body.append(value.toUtf8()); m_body.append("\r\n"); } void MultipartFormData::addFile(QString const& name, QString const& filename, QByteArray const& data) { m_body.append("--"); m_body.append(m_boundary); m_body.append("\r\n"); m_body.append("Content-Disposition: form-data; name=\""); m_body.append(name.toUtf8()); m_body.append("\"; filename=\""); m_body.append(filename.toUtf8()); m_body.append("\"\r\n"); m_body.append("Content-Type: application/octet-stream\r\n\r\n"); m_body.append(data); m_body.append("\r\n"); } QByteArray MultipartFormData::finalize() { m_body.append("--"); m_body.append(m_boundary); m_body.append("--\r\n"); return m_body; } void MultipartFormData::generateBoundary(QByteArray& boundary) { static char const safe_chars[64] = { 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2B, 0x41 }; for (int i = 0; i < 8; ++i) { int const rnd = rand(); for (int shift = 0; shift <= 24; shift += 8) { boundary.append(safe_chars[(rnd >> shift) & 0x3F]); } } } scantailor-RELEASE_0_9_12_2/crash_reporter/MultipartFormData.h000066400000000000000000000014061271170121200242520ustar00rootroot00000000000000#ifndef MULTIPART_FORM_DATA_H_ #define MULTIPART_FORM_DATA_H_ #include #include #include class MultipartFormData { public: MultipartFormData(); /** * \brief Returns the content type that needs to be set for HTTP requests. */ QString contentType() const; void addParameter(QString const& name, QString const& value); void addFile(QString const& name, QString const& filename, QByteArray const& data); /** * \brief Marks the end of parameters and returns the serialized * representation of the form. * * Once called, no other data may be added to the form. */ QByteArray finalize(); private: static void generateBoundary(QByteArray& boundary); QByteArray m_boundary; QByteArray m_body; }; #endif scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/000077500000000000000000000000001271170121200235245ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/client/000077500000000000000000000000001271170121200250025ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/client/windows/000077500000000000000000000000001271170121200264745ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/client/windows/common/000077500000000000000000000000001271170121200277645ustar00rootroot00000000000000auto_critical_section.h000066400000000000000000000050121271170121200344220ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/client/windows/common// Copyright (c) 2008, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef CLIENT_WINDOWS_COMMON_AUTO_CRITICAL_SECTION_H__ #define CLIENT_WINDOWS_COMMON_AUTO_CRITICAL_SECTION_H__ #include namespace google_breakpad { // Automatically enters the critical section in the constructor and leaves // the critical section in the destructor. class AutoCriticalSection { public: // Creates a new instance with the given critical section object // and enters the critical section immediately. explicit AutoCriticalSection(CRITICAL_SECTION* cs) : cs_(cs) { assert(cs_); EnterCriticalSection(cs_); } // Destructor: leaves the critical section. ~AutoCriticalSection() { LeaveCriticalSection(cs_); } private: // Disable copy ctor and operator=. AutoCriticalSection(const AutoCriticalSection&); AutoCriticalSection& operator=(const AutoCriticalSection&); CRITICAL_SECTION* cs_; }; } // namespace google_breakpad #endif // CLIENT_WINDOWS_COMMON_AUTO_CRITICAL_SECTION_H__ scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/client/windows/common/ipc_protocol.h000066400000000000000000000132201271170121200326270ustar00rootroot00000000000000// Copyright (c) 2008, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef CLIENT_WINDOWS_COMMON_IPC_PROTOCOL_H__ #define CLIENT_WINDOWS_COMMON_IPC_PROTOCOL_H__ #include #include #include #include #include "common/windows/string_utils-inl.h" #include "google_breakpad/common/minidump_format.h" namespace google_breakpad { // Name/value pair for custom client information. struct CustomInfoEntry { // Maximum length for name and value for client custom info. static const int kNameMaxLength = 64; static const int kValueMaxLength = 64; CustomInfoEntry() { // Putting name and value in initializer list makes VC++ show warning 4351. set_name(NULL); set_value(NULL); } CustomInfoEntry(const wchar_t* name_arg, const wchar_t* value_arg) { set_name(name_arg); set_value(value_arg); } void set_name(const wchar_t* name_arg) { if (!name_arg) { name[0] = L'\0'; return; } WindowsStringUtils::safe_wcscpy(name, kNameMaxLength, name_arg); } void set_value(const wchar_t* value_arg) { if (!value_arg) { value[0] = L'\0'; return; } WindowsStringUtils::safe_wcscpy(value, kValueMaxLength, value_arg); } void set(const wchar_t* name_arg, const wchar_t* value_arg) { set_name(name_arg); set_value(value_arg); } wchar_t name[kNameMaxLength]; wchar_t value[kValueMaxLength]; }; // Constants for the protocol between client and the server. // Tags sent with each message indicating the purpose of // the message. enum MessageTag { MESSAGE_TAG_NONE = 0, MESSAGE_TAG_REGISTRATION_REQUEST = 1, MESSAGE_TAG_REGISTRATION_RESPONSE = 2, MESSAGE_TAG_REGISTRATION_ACK = 3 }; struct CustomClientInfo { const CustomInfoEntry* entries; int count; }; // Message structure for IPC between crash client and crash server. struct ProtocolMessage { ProtocolMessage() : tag(MESSAGE_TAG_NONE), pid(0), dump_type(MiniDumpNormal), thread_id(0), exception_pointers(NULL), assert_info(NULL), custom_client_info(), dump_request_handle(NULL), dump_generated_handle(NULL), server_alive_handle(NULL) { } // Creates an instance with the given parameters. ProtocolMessage(MessageTag arg_tag, DWORD arg_pid, MINIDUMP_TYPE arg_dump_type, DWORD* arg_thread_id, EXCEPTION_POINTERS** arg_exception_pointers, MDRawAssertionInfo* arg_assert_info, const CustomClientInfo& custom_info, HANDLE arg_dump_request_handle, HANDLE arg_dump_generated_handle, HANDLE arg_server_alive) : tag(arg_tag), pid(arg_pid), dump_type(arg_dump_type), thread_id(arg_thread_id), exception_pointers(arg_exception_pointers), assert_info(arg_assert_info), custom_client_info(custom_info), dump_request_handle(arg_dump_request_handle), dump_generated_handle(arg_dump_generated_handle), server_alive_handle(arg_server_alive) { } // Tag in the message. MessageTag tag; // Process id. DWORD pid; // Dump type requested. MINIDUMP_TYPE dump_type; // Client thread id pointer. DWORD* thread_id; // Exception information. EXCEPTION_POINTERS** exception_pointers; // Assert information in case of an invalid parameter or // pure call failure. MDRawAssertionInfo* assert_info; // Custom client information. CustomClientInfo custom_client_info; // Handle to signal the crash event. HANDLE dump_request_handle; // Handle to check if server is done generating crash. HANDLE dump_generated_handle; // Handle to a mutex that becomes signaled (WAIT_ABANDONED) // if server process goes down. HANDLE server_alive_handle; private: // Disable copy ctor and operator=. ProtocolMessage(const ProtocolMessage& msg); ProtocolMessage& operator=(const ProtocolMessage& msg); }; } // namespace google_breakpad #endif // CLIENT_WINDOWS_COMMON_IPC_PROTOCOL_H__ scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/client/windows/crash_generation/000077500000000000000000000000001271170121200320075ustar00rootroot00000000000000client_info.cc000066400000000000000000000163341271170121200345370ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/client/windows/crash_generation// Copyright (c) 2008, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "client/windows/crash_generation/client_info.h" #include "client/windows/common/ipc_protocol.h" static const wchar_t kCustomInfoProcessUptimeName[] = L"ptime"; namespace google_breakpad { ClientInfo::ClientInfo(CrashGenerationServer* crash_server, DWORD pid, MINIDUMP_TYPE dump_type, DWORD* thread_id, EXCEPTION_POINTERS** ex_info, MDRawAssertionInfo* assert_info, const CustomClientInfo& custom_client_info) : crash_server_(crash_server), pid_(pid), dump_type_(dump_type), ex_info_(ex_info), assert_info_(assert_info), custom_client_info_(custom_client_info), thread_id_(thread_id), process_handle_(NULL), dump_requested_handle_(NULL), dump_generated_handle_(NULL), dump_request_wait_handle_(NULL), process_exit_wait_handle_(NULL) { GetSystemTimeAsFileTime(&start_time_); } bool ClientInfo::Initialize() { process_handle_ = OpenProcess(GENERIC_ALL, FALSE, pid_); if (!process_handle_) { return false; } dump_requested_handle_ = CreateEvent(NULL, // Security attributes. TRUE, // Manual reset. FALSE, // Initial state. NULL); // Name. if (!dump_requested_handle_) { return false; } dump_generated_handle_ = CreateEvent(NULL, // Security attributes. TRUE, // Manual reset. FALSE, // Initial state. NULL); // Name. return dump_generated_handle_ != NULL; } ClientInfo::~ClientInfo() { if (dump_request_wait_handle_) { // Wait for callbacks that might already be running to finish. UnregisterWaitEx(dump_request_wait_handle_, INVALID_HANDLE_VALUE); } if (process_exit_wait_handle_) { // Wait for the callback that might already be running to finish. UnregisterWaitEx(process_exit_wait_handle_, INVALID_HANDLE_VALUE); } if (process_handle_) { CloseHandle(process_handle_); } if (dump_requested_handle_) { CloseHandle(dump_requested_handle_); } if (dump_generated_handle_) { CloseHandle(dump_generated_handle_); } } bool ClientInfo::UnregisterWaits() { bool success = true; if (dump_request_wait_handle_) { if (!UnregisterWait(dump_request_wait_handle_)) { success = false; } else { dump_request_wait_handle_ = NULL; } } if (process_exit_wait_handle_) { if (!UnregisterWait(process_exit_wait_handle_)) { success = false; } else { process_exit_wait_handle_ = NULL; } } return success; } bool ClientInfo::GetClientExceptionInfo(EXCEPTION_POINTERS** ex_info) const { SIZE_T bytes_count = 0; if (!ReadProcessMemory(process_handle_, ex_info_, ex_info, sizeof(*ex_info), &bytes_count)) { return false; } return bytes_count == sizeof(*ex_info); } bool ClientInfo::GetClientThreadId(DWORD* thread_id) const { SIZE_T bytes_count = 0; if (!ReadProcessMemory(process_handle_, thread_id_, thread_id, sizeof(*thread_id), &bytes_count)) { return false; } return bytes_count == sizeof(*thread_id); } void ClientInfo::SetProcessUptime() { FILETIME now = {0}; GetSystemTimeAsFileTime(&now); ULARGE_INTEGER time_start; time_start.HighPart = start_time_.dwHighDateTime; time_start.LowPart = start_time_.dwLowDateTime; ULARGE_INTEGER time_now; time_now.HighPart = now.dwHighDateTime; time_now.LowPart = now.dwLowDateTime; // Calculate the delay and convert it from 100-nanoseconds to milliseconds. __int64 delay = (time_now.QuadPart - time_start.QuadPart) / 10 / 1000; // Convert it to a string. wchar_t* value = custom_info_entries_.get()[custom_client_info_.count].value; _i64tow_s(delay, value, CustomInfoEntry::kValueMaxLength, 10); } bool ClientInfo::PopulateCustomInfo() { SIZE_T bytes_count = 0; SIZE_T read_count = sizeof(CustomInfoEntry) * custom_client_info_.count; // If the scoped array for custom info already has an array, it will be // the same size as what we need. This is because the number of custom info // entries is always the same. So allocate memory only if scoped array has // a NULL pointer. if (!custom_info_entries_.get()) { // Allocate an extra entry for reporting uptime for the client process. custom_info_entries_.reset( new CustomInfoEntry[custom_client_info_.count + 1]); // Use the last element in the array for uptime. custom_info_entries_.get()[custom_client_info_.count].set_name( kCustomInfoProcessUptimeName); } if (!ReadProcessMemory(process_handle_, custom_client_info_.entries, custom_info_entries_.get(), read_count, &bytes_count)) { return false; } SetProcessUptime(); return (bytes_count != read_count); } CustomClientInfo ClientInfo::GetCustomInfo() const { CustomClientInfo custom_info; custom_info.entries = custom_info_entries_.get(); // Add 1 to the count from the client process to account for extra entry for // process uptime. custom_info.count = custom_client_info_.count + 1; return custom_info; } } // namespace google_breakpad client_info.h000066400000000000000000000142401271170121200343730ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/client/windows/crash_generation// Copyright (c) 2008, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef CLIENT_WINDOWS_CRASH_GENERATION_CLIENT_INFO_H__ #define CLIENT_WINDOWS_CRASH_GENERATION_CLIENT_INFO_H__ #include #include #include "client/windows/common/ipc_protocol.h" #include "google_breakpad/common/minidump_format.h" #include "processor/scoped_ptr.h" namespace google_breakpad { class CrashGenerationServer; // Abstraction for a crash client process. class ClientInfo { public: // Creates an instance with the given values. Gets the process // handle for the given process id and creates necessary event // objects. ClientInfo(CrashGenerationServer* crash_server, DWORD pid, MINIDUMP_TYPE dump_type, DWORD* thread_id, EXCEPTION_POINTERS** ex_info, MDRawAssertionInfo* assert_info, const CustomClientInfo& custom_client_info); ~ClientInfo(); CrashGenerationServer* crash_server() const { return crash_server_; } DWORD pid() const { return pid_; } MINIDUMP_TYPE dump_type() const { return dump_type_; } EXCEPTION_POINTERS** ex_info() const { return ex_info_; } MDRawAssertionInfo* assert_info() const { return assert_info_; } DWORD* thread_id() const { return thread_id_; } HANDLE process_handle() const { return process_handle_; } HANDLE dump_requested_handle() const { return dump_requested_handle_; } HANDLE dump_generated_handle() const { return dump_generated_handle_; } HANDLE dump_request_wait_handle() const { return dump_request_wait_handle_; } void set_dump_request_wait_handle(HANDLE value) { dump_request_wait_handle_ = value; } HANDLE process_exit_wait_handle() const { return process_exit_wait_handle_; } void set_process_exit_wait_handle(HANDLE value) { process_exit_wait_handle_ = value; } // Unregister all waits for the client. bool UnregisterWaits(); bool Initialize(); bool GetClientExceptionInfo(EXCEPTION_POINTERS** ex_info) const; bool GetClientThreadId(DWORD* thread_id) const; // Reads the custom information from the client process address space. bool PopulateCustomInfo(); // Returns the client custom information. CustomClientInfo GetCustomInfo() const; private: // Calcualtes the uptime for the client process, converts it to a string and // stores it in the last entry of client custom info. void SetProcessUptime(); // Crash generation server. CrashGenerationServer* crash_server_; // Client process ID. DWORD pid_; // Dump type requested by the client. MINIDUMP_TYPE dump_type_; // Address of an EXCEPTION_POINTERS* variable in the client // process address space that will point to an instance of // EXCEPTION_POINTERS containing information about crash. // // WARNING: Do not dereference these pointers as they are pointers // in the address space of another process. EXCEPTION_POINTERS** ex_info_; // Address of an instance of MDRawAssertionInfo in the client // process address space that will contain information about // non-exception related crashes like invalid parameter assertion // failures and pure calls. // // WARNING: Do not dereference these pointers as they are pointers // in the address space of another process. MDRawAssertionInfo* assert_info_; // Custom information about the client. CustomClientInfo custom_client_info_; // Contains the custom client info entries read from the client process // memory. This will be populated only if the method GetClientCustomInfo // is called. scoped_array custom_info_entries_; // Address of a variable in the client process address space that // will contain the thread id of the crashing client thread. // // WARNING: Do not dereference these pointers as they are pointers // in the address space of another process. DWORD* thread_id_; // Client process handle. HANDLE process_handle_; // Dump request event handle. HANDLE dump_requested_handle_; // Dump generated event handle. HANDLE dump_generated_handle_; // Wait handle for dump request event. HANDLE dump_request_wait_handle_; // Wait handle for process exit event. HANDLE process_exit_wait_handle_; // Time when the client process started. It is used to determine the uptime // for the client process when it signals a crash. FILETIME start_time_; // Disallow copy ctor and operator=. ClientInfo(const ClientInfo& client_info); ClientInfo& operator=(const ClientInfo& client_info); }; } // namespace google_breakpad #endif // CLIENT_WINDOWS_CRASH_GENERATION_CLIENT_INFO_H__ crash_generation_client.cc000066400000000000000000000256001271170121200371130ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/client/windows/crash_generation// Copyright (c) 2008, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "client/windows/crash_generation/crash_generation_client.h" #include #include #include "client/windows/common/ipc_protocol.h" namespace google_breakpad { const int kPipeBusyWaitTimeoutMs = 2000; #ifdef _DEBUG const DWORD kWaitForServerTimeoutMs = INFINITE; #else const DWORD kWaitForServerTimeoutMs = 15000; #endif const int kPipeConnectMaxAttempts = 2; const DWORD kPipeDesiredAccess = FILE_READ_DATA | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES; const DWORD kPipeFlagsAndAttributes = SECURITY_IDENTIFICATION | SECURITY_SQOS_PRESENT; const DWORD kPipeMode = PIPE_READMODE_MESSAGE; const size_t kWaitEventCount = 2; // This function is orphan for production code. It can be used // for debugging to help repro some scenarios like the client // is slow in writing to the pipe after connecting, the client // is slow in reading from the pipe after writing, etc. The parameter // overlapped below is not used and it is present to match the signature // of this function to TransactNamedPipe Win32 API. Uncomment if needed // for debugging. /** static bool TransactNamedPipeDebugHelper(HANDLE pipe, const void* in_buffer, DWORD in_size, void* out_buffer, DWORD out_size, DWORD* bytes_count, LPOVERLAPPED) { // Uncomment the next sleep to create a gap before writing // to pipe. // Sleep(5000); if (!WriteFile(pipe, in_buffer, in_size, bytes_count, NULL)) { return false; } // Uncomment the next sleep to create a gap between write // and read. // Sleep(5000); return ReadFile(pipe, out_buffer, out_size, bytes_count, NULL) != FALSE; } **/ CrashGenerationClient::CrashGenerationClient( const wchar_t* pipe_name, MINIDUMP_TYPE dump_type, const CustomClientInfo* custom_info) : pipe_name_(pipe_name), dump_type_(dump_type), thread_id_(0), server_process_id_(0), crash_event_(NULL), crash_generated_(NULL), server_alive_(NULL), exception_pointers_(NULL), custom_info_() { memset(&assert_info_, 0, sizeof(assert_info_)); if (custom_info) { custom_info_ = *custom_info; } } CrashGenerationClient::~CrashGenerationClient() { if (crash_event_) { CloseHandle(crash_event_); } if (crash_generated_) { CloseHandle(crash_generated_); } if (server_alive_) { CloseHandle(server_alive_); } } // Performs the registration step with the server process. // The registration step involves communicating with the server // via a named pipe. The client sends the following pieces of // data to the server: // // * Message tag indicating the client is requesting registration. // * Process id of the client process. // * Address of a DWORD variable in the client address space // that will contain the thread id of the client thread that // caused the crash. // * Address of a EXCEPTION_POINTERS* variable in the client // address space that will point to an instance of EXCEPTION_POINTERS // when the crash happens. // * Address of an instance of MDRawAssertionInfo that will contain // relevant information in case of non-exception crashes like assertion // failures and pure calls. // // In return the client expects the following information from the server: // // * Message tag indicating successful registration. // * Server process id. // * Handle to an object that client can signal to request dump // generation from the server. // * Handle to an object that client can wait on after requesting // dump generation for the server to finish dump generation. // * Handle to a mutex object that client can wait on to make sure // server is still alive. // // If any step of the expected behavior mentioned above fails, the // registration step is not considered successful and hence out-of-process // dump generation service is not available. // // Returns true if the registration is successful; false otherwise. bool CrashGenerationClient::Register() { HANDLE pipe = ConnectToServer(); if (!pipe) { return false; } bool success = RegisterClient(pipe); CloseHandle(pipe); return success; } HANDLE CrashGenerationClient::ConnectToServer() { HANDLE pipe = ConnectToPipe(pipe_name_.c_str(), kPipeDesiredAccess, kPipeFlagsAndAttributes); if (!pipe) { return NULL; } DWORD mode = kPipeMode; if (!SetNamedPipeHandleState(pipe, &mode, NULL, NULL)) { CloseHandle(pipe); pipe = NULL; } return pipe; } bool CrashGenerationClient::RegisterClient(HANDLE pipe) { ProtocolMessage msg(MESSAGE_TAG_REGISTRATION_REQUEST, GetCurrentProcessId(), dump_type_, &thread_id_, &exception_pointers_, &assert_info_, custom_info_, NULL, NULL, NULL); ProtocolMessage reply; DWORD bytes_count = 0; // The call to TransactNamedPipe below can be changed to a call // to TransactNamedPipeDebugHelper to help repro some scenarios. // For details see comments for TransactNamedPipeDebugHelper. if (!TransactNamedPipe(pipe, &msg, sizeof(msg), &reply, sizeof(ProtocolMessage), &bytes_count, NULL)) { return false; } if (!ValidateResponse(reply)) { return false; } ProtocolMessage ack_msg; ack_msg.tag = MESSAGE_TAG_REGISTRATION_ACK; if (!WriteFile(pipe, &ack_msg, sizeof(ack_msg), &bytes_count, NULL)) { return false; } crash_event_ = reply.dump_request_handle; crash_generated_ = reply.dump_generated_handle; server_alive_ = reply.server_alive_handle; server_process_id_ = reply.pid; return true; } HANDLE CrashGenerationClient::ConnectToPipe(const wchar_t* pipe_name, DWORD pipe_access, DWORD flags_attrs) { for (int i = 0; i < kPipeConnectMaxAttempts; ++i) { HANDLE pipe = CreateFile(pipe_name, pipe_access, 0, NULL, OPEN_EXISTING, flags_attrs, NULL); if (pipe != INVALID_HANDLE_VALUE) { return pipe; } // Cannot continue retrying if error is something other than // ERROR_PIPE_BUSY. if (GetLastError() != ERROR_PIPE_BUSY) { break; } // Cannot continue retrying if wait on pipe fails. if (!WaitNamedPipe(pipe_name, kPipeBusyWaitTimeoutMs)) { break; } } return NULL; } bool CrashGenerationClient::ValidateResponse( const ProtocolMessage& msg) const { return (msg.tag == MESSAGE_TAG_REGISTRATION_RESPONSE) && (msg.pid != 0) && (msg.dump_request_handle != NULL) && (msg.dump_generated_handle != NULL) && (msg.server_alive_handle != NULL); } bool CrashGenerationClient::IsRegistered() const { return crash_event_ != NULL; } bool CrashGenerationClient::RequestDump(EXCEPTION_POINTERS* ex_info) { if (!IsRegistered()) { return false; } exception_pointers_ = ex_info; thread_id_ = GetCurrentThreadId(); assert_info_.line = 0; assert_info_.type = 0; assert_info_.expression[0] = 0; assert_info_.file[0] = 0; assert_info_.function[0] = 0; return SignalCrashEventAndWait(); } bool CrashGenerationClient::RequestDump(MDRawAssertionInfo* assert_info) { if (!IsRegistered()) { return false; } exception_pointers_ = NULL; if (assert_info) { memcpy(&assert_info_, assert_info, sizeof(assert_info_)); } else { memset(&assert_info_, 0, sizeof(assert_info_)); } thread_id_ = GetCurrentThreadId(); return SignalCrashEventAndWait(); } bool CrashGenerationClient::SignalCrashEventAndWait() { assert(crash_event_); assert(crash_generated_); assert(server_alive_); // Reset the dump generated event before signaling the crash // event so that the server can set the dump generated event // once it is done generating the event. if (!ResetEvent(crash_generated_)) { return false; } if (!SetEvent(crash_event_)) { return false; } HANDLE wait_handles[kWaitEventCount] = {crash_generated_, server_alive_}; DWORD result = WaitForMultipleObjects(kWaitEventCount, wait_handles, FALSE, kWaitForServerTimeoutMs); // Crash dump was successfully generated only if the server // signaled the crash generated event. return result == WAIT_OBJECT_0; } } // namespace google_breakpad crash_generation_client.h000066400000000000000000000137601271170121200367610ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/client/windows/crash_generation// Copyright (c) 2008, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef CLIENT_WINDOWS_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H__ #define CLIENT_WINDOWS_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H__ #include #include #include #include #include "client/windows/common/ipc_protocol.h" #include "processor/scoped_ptr.h" namespace google_breakpad { struct CustomClientInfo; // Abstraction of client-side implementation of out of process // crash generation. // // The process that desires to have out-of-process crash dump // generation service can use this class in the following way: // // * Create an instance. // * Call Register method so that the client tries to register // with the server process and check the return value. If // registration is not successful, out-of-process crash dump // generation will not be available // * Request dump generation by calling either of the two // overloaded RequestDump methods - one in case of exceptions // and the other in case of assertion failures // // Note that it is the responsibility of the client code of // this class to set the unhandled exception filter with the // system by calling the SetUnhandledExceptionFilter function // and the client code should explicitly request dump generation. class CrashGenerationClient { public: CrashGenerationClient(const wchar_t* pipe_name, MINIDUMP_TYPE dump_type, const CustomClientInfo* custom_info); ~CrashGenerationClient(); // Registers the client process with the crash server. // // Returns true if the registration is successful; false otherwise. bool Register(); // Requests the crash server to generate a dump with the given // exception information. // // Returns true if the dump was successful; false otherwise. Note that // if the registration step was not performed or it was not successful, // false will be returned. bool RequestDump(EXCEPTION_POINTERS* ex_info); // Requests the crash server to generate a dump with the given // assertion information. // // Returns true if the dump was successful; false otherwise. Note that // if the registration step was not performed or it was not successful, // false will be returned. bool RequestDump(MDRawAssertionInfo* assert_info); private: // Connects to the appropriate pipe and sets the pipe handle state. // // Returns the pipe handle if everything goes well; otherwise Returns NULL. HANDLE ConnectToServer(); // Performs a handshake with the server over the given pipe which should be // already connected to the server. // // Returns true if handshake with the server was successful; false otherwise. bool RegisterClient(HANDLE pipe); // Validates the given server response. bool ValidateResponse(const ProtocolMessage& msg) const; // Returns true if the registration step succeeded; false otherwise. bool IsRegistered() const; // Connects to the given named pipe with given parameters. // // Returns true if the connection is successful; false otherwise. HANDLE ConnectToPipe(const wchar_t* pipe_name, DWORD pipe_access, DWORD flags_attrs); // Signals the crash event and wait for the server to generate crash. bool SignalCrashEventAndWait(); // Pipe name to use to talk to server. std::wstring pipe_name_; // Custom client information CustomClientInfo custom_info_; // Type of dump to generate. MINIDUMP_TYPE dump_type_; // Event to signal in case of a crash. HANDLE crash_event_; // Handle to wait on after signaling a crash for the server // to finish generating crash dump. HANDLE crash_generated_; // Handle to a mutex that will become signaled with WAIT_ABANDONED // if the server process goes down. HANDLE server_alive_; // Server process id. DWORD server_process_id_; // Id of the thread that caused the crash. DWORD thread_id_; // Exception pointers for an exception crash. EXCEPTION_POINTERS* exception_pointers_; // Assertion info for an invalid parameter or pure call crash. MDRawAssertionInfo assert_info_; // Disable copy ctor and operator=. CrashGenerationClient(const CrashGenerationClient& crash_client); CrashGenerationClient& operator=(const CrashGenerationClient& crash_client); }; } // namespace google_breakpad #endif // CLIENT_WINDOWS_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H__ crash_generation_server.cc000066400000000000000000000700131271170121200371410ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/client/windows/crash_generation// Copyright (c) 2008, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "client/windows/crash_generation/crash_generation_server.h" #include #include #include #include "client/windows/common/auto_critical_section.h" #include "processor/scoped_ptr.h" namespace google_breakpad { // Output buffer size. static const size_t kOutBufferSize = 64; // Input buffer size. static const size_t kInBufferSize = 64; // Access flags for the client on the dump request event. static const DWORD kDumpRequestEventAccess = EVENT_MODIFY_STATE; // Access flags for the client on the dump generated event. static const DWORD kDumpGeneratedEventAccess = EVENT_MODIFY_STATE | SYNCHRONIZE; // Access flags for the client on the mutex. static const DWORD kMutexAccess = SYNCHRONIZE; // Attribute flags for the pipe. static const DWORD kPipeAttr = FILE_FLAG_FIRST_PIPE_INSTANCE | PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED; // Mode for the pipe. static const DWORD kPipeMode = PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT; // For pipe I/O, execute the callback in the wait thread itself, // since the callback does very little work. The callback executes // the code for one of the states of the server state machine and // the code for all of the states perform async I/O and hence // finish very quickly. static const ULONG kPipeIOThreadFlags = WT_EXECUTEINWAITTHREAD; // Dump request threads will, most likely, generate dumps. That may // take some time to finish, so specify WT_EXECUTELONGFUNCTION flag. static const ULONG kDumpRequestThreadFlags = WT_EXECUTEINWAITTHREAD | WT_EXECUTELONGFUNCTION; // Maximum delay during server shutdown if some work items // are still executing. static const int kShutdownDelayMs = 10000; // Interval for each sleep during server shutdown. static const int kShutdownSleepIntervalMs = 5; static bool IsClientRequestValid(const ProtocolMessage& msg) { return msg.tag == MESSAGE_TAG_REGISTRATION_REQUEST && msg.pid != 0 && msg.thread_id != NULL && msg.exception_pointers != NULL && msg.assert_info != NULL; } CrashGenerationServer::CrashGenerationServer( const std::wstring& pipe_name, SECURITY_ATTRIBUTES* pipe_sec_attrs, OnClientConnectedCallback connect_callback, void* connect_context, OnClientDumpRequestCallback dump_callback, void* dump_context, OnClientExitedCallback exit_callback, void* exit_context, bool generate_dumps, const std::wstring* dump_path) : pipe_name_(pipe_name), pipe_sec_attrs_(pipe_sec_attrs), pipe_(NULL), pipe_wait_handle_(NULL), server_alive_handle_(NULL), connect_callback_(connect_callback), connect_context_(connect_context), dump_callback_(dump_callback), dump_context_(dump_context), exit_callback_(exit_callback), exit_context_(exit_context), generate_dumps_(generate_dumps), dump_generator_(NULL), server_state_(IPC_SERVER_STATE_INITIAL), shutting_down_(false), overlapped_(), client_info_(NULL), cleanup_item_count_(0) { InitializeCriticalSection(&clients_sync_); if (dump_path) { dump_generator_.reset(new MinidumpGenerator(*dump_path)); } } CrashGenerationServer::~CrashGenerationServer() { // Indicate to existing threads that server is shutting down. shutting_down_ = true; // Even if there are no current worker threads running, it is possible that // an I/O request is pending on the pipe right now but not yet done. In fact, // it's very likely this is the case unless we are in an ERROR state. If we // don't wait for the pending I/O to be done, then when the I/O completes, // it may write to invalid memory. AppVerifier will flag this problem too. // So we disconnect from the pipe and then wait for the server to get into // error state so that the pending I/O will fail and get cleared. DisconnectNamedPipe(pipe_); int num_tries = 100; while (num_tries-- && server_state_ != IPC_SERVER_STATE_ERROR) { Sleep(10); } // Unregister wait on the pipe. if (pipe_wait_handle_) { // Wait for already executing callbacks to finish. UnregisterWaitEx(pipe_wait_handle_, INVALID_HANDLE_VALUE); } // Close the pipe to avoid further client connections. if (pipe_) { CloseHandle(pipe_); } // Request all ClientInfo objects to unregister all waits. // New scope to hold the lock for the shortest time. { AutoCriticalSection lock(&clients_sync_); std::list::iterator iter; for (iter = clients_.begin(); iter != clients_.end(); ++iter) { ClientInfo* client_info = *iter; client_info->UnregisterWaits(); } } // Now that all waits have been unregistered, wait for some time // for all pending work items to finish. int total_wait = 0; while (cleanup_item_count_ > 0) { Sleep(kShutdownSleepIntervalMs); total_wait += kShutdownSleepIntervalMs; if (total_wait >= kShutdownDelayMs) { break; } } // Clean up all the ClientInfo objects. // New scope to hold the lock for the shortest time. { AutoCriticalSection lock(&clients_sync_); std::list::iterator iter; for (iter = clients_.begin(); iter != clients_.end(); ++iter) { ClientInfo* client_info = *iter; delete client_info; } } if (server_alive_handle_) { // Release the mutex before closing the handle so that clients requesting // dumps wait for a long time for the server to generate a dump. ReleaseMutex(server_alive_handle_); CloseHandle(server_alive_handle_); } DeleteCriticalSection(&clients_sync_); } bool CrashGenerationServer::Start() { server_state_ = IPC_SERVER_STATE_INITIAL; server_alive_handle_ = CreateMutex(NULL, TRUE, NULL); if (!server_alive_handle_) { return false; } // Event to signal the client connection and pipe reads and writes. overlapped_.hEvent = CreateEvent(NULL, // Security descriptor. TRUE, // Manual reset. FALSE, // Initially signaled. NULL); // Name. if (!overlapped_.hEvent) { return false; } // Register a callback with the thread pool for the client connection. RegisterWaitForSingleObject(&pipe_wait_handle_, overlapped_.hEvent, OnPipeConnected, this, INFINITE, kPipeIOThreadFlags); pipe_ = CreateNamedPipe(pipe_name_.c_str(), kPipeAttr, kPipeMode, 1, kOutBufferSize, kInBufferSize, 0, pipe_sec_attrs_); if (pipe_ == INVALID_HANDLE_VALUE) { return false; } // Signal the event to start a separate thread to handle // client connections. return SetEvent(overlapped_.hEvent) != FALSE; } // If the server thread serving clients ever gets into the // ERROR state, reset the event, close the pipe and remain // in the error state forever. Error state means something // that we didn't account for has happened, and it's dangerous // to do anything unknowingly. void CrashGenerationServer::HandleErrorState() { assert(server_state_ == IPC_SERVER_STATE_ERROR); // If the server is shutting down anyway, don't clean up // here since shut down process will clean up. if (shutting_down_) { return; } if (pipe_wait_handle_) { UnregisterWait(pipe_wait_handle_); pipe_wait_handle_ = NULL; } if (pipe_) { CloseHandle(pipe_); pipe_ = NULL; } if (overlapped_.hEvent) { CloseHandle(overlapped_.hEvent); overlapped_.hEvent = NULL; } } // When the server thread serving clients is in the INITIAL state, // try to connect to the pipe asynchronously. If the connection // finishes synchronously, directly go into the CONNECTED state; // otherwise go into the CONNECTING state. For any problems, go // into the ERROR state. void CrashGenerationServer::HandleInitialState() { assert(server_state_ == IPC_SERVER_STATE_INITIAL); if (!ResetEvent(overlapped_.hEvent)) { server_state_ = IPC_SERVER_STATE_ERROR; return; } bool success = ConnectNamedPipe(pipe_, &overlapped_) != FALSE; // From MSDN, it is not clear that when ConnectNamedPipe is used // in an overlapped mode, will it ever return non-zero value, and // if so, in what cases. assert(!success); DWORD error_code = GetLastError(); switch (error_code) { case ERROR_IO_PENDING: server_state_ = IPC_SERVER_STATE_CONNECTING; break; case ERROR_PIPE_CONNECTED: if (SetEvent(overlapped_.hEvent)) { server_state_ = IPC_SERVER_STATE_CONNECTED; } else { server_state_ = IPC_SERVER_STATE_ERROR; } break; default: server_state_ = IPC_SERVER_STATE_ERROR; break; } } // When the server thread serving the clients is in the CONNECTING state, // try to get the result of the asynchronous connection request using // the OVERLAPPED object. If the result indicates the connection is done, // go into the CONNECTED state. If the result indicates I/O is still // INCOMPLETE, remain in the CONNECTING state. For any problems, // go into the DISCONNECTING state. void CrashGenerationServer::HandleConnectingState() { assert(server_state_ == IPC_SERVER_STATE_CONNECTING); DWORD bytes_count = 0; bool success = GetOverlappedResult(pipe_, &overlapped_, &bytes_count, FALSE) != FALSE; if (success) { server_state_ = IPC_SERVER_STATE_CONNECTED; return; } if (GetLastError() != ERROR_IO_INCOMPLETE) { server_state_ = IPC_SERVER_STATE_DISCONNECTING; } } // When the server thread serving the clients is in the CONNECTED state, // try to issue an asynchronous read from the pipe. If read completes // synchronously or if I/O is pending then go into the READING state. // For any problems, go into the DISCONNECTING state. void CrashGenerationServer::HandleConnectedState() { assert(server_state_ == IPC_SERVER_STATE_CONNECTED); DWORD bytes_count = 0; memset(&msg_, 0, sizeof(msg_)); bool success = ReadFile(pipe_, &msg_, sizeof(msg_), &bytes_count, &overlapped_) != FALSE; // Note that the asynchronous read issued above can finish before the // code below executes. But, it is okay to change state after issuing // the asynchronous read. This is because even if the asynchronous read // is done, the callback for it would not be executed until the current // thread finishes its execution. if (success || GetLastError() == ERROR_IO_PENDING) { server_state_ = IPC_SERVER_STATE_READING; } else { server_state_ = IPC_SERVER_STATE_DISCONNECTING; } } // When the server thread serving the clients is in the READING state, // try to get the result of the async read. If async read is done, // go into the READ_DONE state. For any problems, go into the // DISCONNECTING state. void CrashGenerationServer::HandleReadingState() { assert(server_state_ == IPC_SERVER_STATE_READING); DWORD bytes_count = 0; bool success = GetOverlappedResult(pipe_, &overlapped_, &bytes_count, FALSE) != FALSE; if (success && bytes_count == sizeof(ProtocolMessage)) { server_state_ = IPC_SERVER_STATE_READ_DONE; return; } DWORD error_code; error_code = GetLastError(); // We should never get an I/O incomplete since we should not execute this // unless the Read has finished and the overlapped event is signaled. If // we do get INCOMPLETE, we have a bug in our code. assert(error_code != ERROR_IO_INCOMPLETE); server_state_ = IPC_SERVER_STATE_DISCONNECTING; } // When the server thread serving the client is in the READ_DONE state, // validate the client's request message, register the client by // creating appropriate objects and prepare the response. Then try to // write the response to the pipe asynchronously. If that succeeds, // go into the WRITING state. For any problems, go into the DISCONNECTING // state. void CrashGenerationServer::HandleReadDoneState() { assert(server_state_ == IPC_SERVER_STATE_READ_DONE); if (!IsClientRequestValid(msg_)) { server_state_ = IPC_SERVER_STATE_DISCONNECTING; return; } scoped_ptr client_info( new ClientInfo(this, msg_.pid, msg_.dump_type, msg_.thread_id, msg_.exception_pointers, msg_.assert_info, msg_.custom_client_info)); if (!client_info->Initialize()) { server_state_ = IPC_SERVER_STATE_DISCONNECTING; return; } if (!RespondToClient(client_info.get())) { server_state_ = IPC_SERVER_STATE_DISCONNECTING; return; } // Note that the asynchronous write issued by RespondToClient function // can finish before the code below executes. But it is okay to change // state after issuing the asynchronous write. This is because even if // the asynchronous write is done, the callback for it would not be // executed until the current thread finishes its execution. server_state_ = IPC_SERVER_STATE_WRITING; client_info_ = client_info.release(); } // When the server thread serving the clients is in the WRITING state, // try to get the result of the async write. If the async write is done, // go into the WRITE_DONE state. For any problems, go into the // DISONNECTING state. void CrashGenerationServer::HandleWritingState() { assert(server_state_ == IPC_SERVER_STATE_WRITING); DWORD bytes_count = 0; bool success = GetOverlappedResult(pipe_, &overlapped_, &bytes_count, FALSE) != FALSE; if (success) { server_state_ = IPC_SERVER_STATE_WRITE_DONE; return; } DWORD error_code; error_code = GetLastError(); // We should never get an I/O incomplete since we should not execute this // unless the Write has finished and the overlapped event is signaled. If // we do get INCOMPLETE, we have a bug in our code. assert(error_code != ERROR_IO_INCOMPLETE); server_state_ = IPC_SERVER_STATE_DISCONNECTING; } // When the server thread serving the clients is in the WRITE_DONE state, // try to issue an async read on the pipe. If the read completes synchronously // or if I/O is still pending then go into the READING_ACK state. For any // issues, go into the DISCONNECTING state. void CrashGenerationServer::HandleWriteDoneState() { assert(server_state_ == IPC_SERVER_STATE_WRITE_DONE); server_state_ = IPC_SERVER_STATE_READING_ACK; DWORD bytes_count = 0; bool success = ReadFile(pipe_, &msg_, sizeof(msg_), &bytes_count, &overlapped_) != FALSE; if (success) { return; } DWORD error_code = GetLastError(); if (error_code != ERROR_IO_PENDING) { server_state_ = IPC_SERVER_STATE_DISCONNECTING; } } // When the server thread serving the clients is in the READING_ACK state, // try to get result of async read. Go into the DISCONNECTING state. void CrashGenerationServer::HandleReadingAckState() { assert(server_state_ == IPC_SERVER_STATE_READING_ACK); DWORD bytes_count = 0; bool success = GetOverlappedResult(pipe_, &overlapped_, &bytes_count, FALSE) != FALSE; if (success) { // The connection handshake with the client is now complete; perform // the callback. if (connect_callback_) { connect_callback_(connect_context_, client_info_); } } else { DWORD error_code = GetLastError(); // We should never get an I/O incomplete since we should not execute this // unless the Read has finished and the overlapped event is signaled. If // we do get INCOMPLETE, we have a bug in our code. assert(error_code != ERROR_IO_INCOMPLETE); } server_state_ = IPC_SERVER_STATE_DISCONNECTING; } // When the server thread serving the client is in the DISCONNECTING state, // disconnect from the pipe and reset the event. If anything fails, go into // the ERROR state. If it goes well, go into the INITIAL state and set the // event to start all over again. void CrashGenerationServer::HandleDisconnectingState() { assert(server_state_ == IPC_SERVER_STATE_DISCONNECTING); // Done serving the client. client_info_ = NULL; overlapped_.Internal = NULL; overlapped_.InternalHigh = NULL; overlapped_.Offset = 0; overlapped_.OffsetHigh = 0; overlapped_.Pointer = NULL; if (!ResetEvent(overlapped_.hEvent)) { server_state_ = IPC_SERVER_STATE_ERROR; return; } if (!DisconnectNamedPipe(pipe_)) { server_state_ = IPC_SERVER_STATE_ERROR; return; } // If the server is shutting down do not connect to the // next client. if (shutting_down_) { return; } server_state_ = IPC_SERVER_STATE_INITIAL; if (!SetEvent(overlapped_.hEvent)) { server_state_ = IPC_SERVER_STATE_ERROR; } } bool CrashGenerationServer::PrepareReply(const ClientInfo& client_info, ProtocolMessage* reply) const { reply->tag = MESSAGE_TAG_REGISTRATION_RESPONSE; reply->pid = GetCurrentProcessId(); if (CreateClientHandles(client_info, reply)) { return true; } if (reply->dump_request_handle) { CloseHandle(reply->dump_request_handle); } if (reply->dump_generated_handle) { CloseHandle(reply->dump_generated_handle); } if (reply->server_alive_handle) { CloseHandle(reply->server_alive_handle); } return false; } bool CrashGenerationServer::CreateClientHandles(const ClientInfo& client_info, ProtocolMessage* reply) const { HANDLE current_process = GetCurrentProcess(); if (!DuplicateHandle(current_process, client_info.dump_requested_handle(), client_info.process_handle(), &reply->dump_request_handle, kDumpRequestEventAccess, FALSE, 0)) { return false; } if (!DuplicateHandle(current_process, client_info.dump_generated_handle(), client_info.process_handle(), &reply->dump_generated_handle, kDumpGeneratedEventAccess, FALSE, 0)) { return false; } if (!DuplicateHandle(current_process, server_alive_handle_, client_info.process_handle(), &reply->server_alive_handle, kMutexAccess, FALSE, 0)) { return false; } return true; } bool CrashGenerationServer::RespondToClient(ClientInfo* client_info) { ProtocolMessage reply; if (!PrepareReply(*client_info, &reply)) { return false; } if (!AddClient(client_info)) { return false; } DWORD bytes_count = 0; bool success = WriteFile(pipe_, &reply, sizeof(reply), &bytes_count, &overlapped_) != FALSE; return success || GetLastError() == ERROR_IO_PENDING; } // The server thread servicing the clients runs this method. The method // implements the state machine described in ReadMe.txt along with the // helper methods HandleXXXState. void CrashGenerationServer::HandleConnectionRequest() { // If we are shutting doen then get into ERROR state, reset the event so more // workers don't run and return immediately. if (shutting_down_) { server_state_ = IPC_SERVER_STATE_ERROR; ResetEvent(overlapped_.hEvent); return; } switch (server_state_) { case IPC_SERVER_STATE_ERROR: HandleErrorState(); break; case IPC_SERVER_STATE_INITIAL: HandleInitialState(); break; case IPC_SERVER_STATE_CONNECTING: HandleConnectingState(); break; case IPC_SERVER_STATE_CONNECTED: HandleConnectedState(); break; case IPC_SERVER_STATE_READING: HandleReadingState(); break; case IPC_SERVER_STATE_READ_DONE: HandleReadDoneState(); break; case IPC_SERVER_STATE_WRITING: HandleWritingState(); break; case IPC_SERVER_STATE_WRITE_DONE: HandleWriteDoneState(); break; case IPC_SERVER_STATE_READING_ACK: HandleReadingAckState(); break; case IPC_SERVER_STATE_DISCONNECTING: HandleDisconnectingState(); break; default: assert(false); // This indicates that we added one more state without // adding handling code. server_state_ = IPC_SERVER_STATE_ERROR; break; } } bool CrashGenerationServer::AddClient(ClientInfo* client_info) { HANDLE request_wait_handle = NULL; if (!RegisterWaitForSingleObject(&request_wait_handle, client_info->dump_requested_handle(), OnDumpRequest, client_info, INFINITE, kDumpRequestThreadFlags)) { return false; } client_info->set_dump_request_wait_handle(request_wait_handle); // OnClientEnd will be called when the client process terminates. HANDLE process_wait_handle = NULL; if (!RegisterWaitForSingleObject(&process_wait_handle, client_info->process_handle(), OnClientEnd, client_info, INFINITE, WT_EXECUTEONLYONCE)) { return false; } client_info->set_process_exit_wait_handle(process_wait_handle); // New scope to hold the lock for the shortest time. { AutoCriticalSection lock(&clients_sync_); clients_.push_back(client_info); } return true; } // static void CALLBACK CrashGenerationServer::OnPipeConnected(void* context, BOOLEAN) { assert (context); CrashGenerationServer* obj = reinterpret_cast(context); obj->HandleConnectionRequest(); } // static void CALLBACK CrashGenerationServer::OnDumpRequest(void* context, BOOLEAN) { assert(context); ClientInfo* client_info = reinterpret_cast(context); client_info->PopulateCustomInfo(); CrashGenerationServer* crash_server = client_info->crash_server(); assert(crash_server); crash_server->HandleDumpRequest(*client_info); ResetEvent(client_info->dump_requested_handle()); } // static void CALLBACK CrashGenerationServer::OnClientEnd(void* context, BOOLEAN) { assert(context); ClientInfo* client_info = reinterpret_cast(context); CrashGenerationServer* crash_server = client_info->crash_server(); assert(crash_server); InterlockedIncrement(&crash_server->cleanup_item_count_); if (!QueueUserWorkItem(CleanupClient, context, WT_EXECUTEDEFAULT)) { InterlockedDecrement(&crash_server->cleanup_item_count_); } } // static DWORD WINAPI CrashGenerationServer::CleanupClient(void* context) { assert(context); ClientInfo* client_info = reinterpret_cast(context); CrashGenerationServer* crash_server = client_info->crash_server(); assert(crash_server); if (crash_server->exit_callback_) { crash_server->exit_callback_(crash_server->exit_context_, client_info); } crash_server->DoCleanup(client_info); InterlockedDecrement(&crash_server->cleanup_item_count_); return 0; } void CrashGenerationServer::DoCleanup(ClientInfo* client_info) { assert(client_info); // Start a new scope to release lock automatically. { AutoCriticalSection lock(&clients_sync_); clients_.remove(client_info); } delete client_info; } void CrashGenerationServer::HandleDumpRequest(const ClientInfo& client_info) { // Generate the dump only if it's explicitly requested by the // server application; otherwise the server might want to generate // dump in the callback. std::wstring dump_path; if (generate_dumps_) { if (!GenerateDump(client_info, &dump_path)) { return; } } if (dump_callback_) { std::wstring* ptr_dump_path = (dump_path == L"") ? NULL : &dump_path; dump_callback_(dump_context_, &client_info, ptr_dump_path); } SetEvent(client_info.dump_generated_handle()); } bool CrashGenerationServer::GenerateDump(const ClientInfo& client, std::wstring* dump_path) { assert(client.pid() != 0); assert(client.process_handle()); // We have to get the address of EXCEPTION_INFORMATION from // the client process address space. EXCEPTION_POINTERS* client_ex_info = NULL; if (!client.GetClientExceptionInfo(&client_ex_info)) { return false; } DWORD client_thread_id = 0; if (!client.GetClientThreadId(&client_thread_id)) { return false; } return dump_generator_->WriteMinidump(client.process_handle(), client.pid(), client_thread_id, GetCurrentThreadId(), client_ex_info, client.assert_info(), client.dump_type(), true, dump_path); } } // namespace google_breakpad crash_generation_server.h000066400000000000000000000246651271170121200370170ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/client/windows/crash_generation// Copyright (c) 2008, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef CLIENT_WINDOWS_CRASH_GENERATION_CRASH_GENERATION_SERVER_H__ #define CLIENT_WINDOWS_CRASH_GENERATION_CRASH_GENERATION_SERVER_H__ #include #include #include "client/windows/common/ipc_protocol.h" #include "client/windows/crash_generation/client_info.h" #include "client/windows/crash_generation/minidump_generator.h" #include "processor/scoped_ptr.h" namespace google_breakpad { // Abstraction for server side implementation of out-of-process crash // generation protocol for Windows platform only. It generates Windows // minidump files for client processes that request dump generation. When // the server is requested to start listening for clients (by calling the // Start method), it creates a named pipe and waits for the clients to // register. In response, it hands them event handles that the client can // signal to request dump generation. When the clients request dump // generation in this way, the server generates Windows minidump files. class CrashGenerationServer { public: typedef void (*OnClientConnectedCallback)(void* context, const ClientInfo* client_info); typedef void (*OnClientDumpRequestCallback)(void* context, const ClientInfo* client_info, const std::wstring* file_path); typedef void (*OnClientExitedCallback)(void* context, const ClientInfo* client_info); // Creates an instance with the given parameters. // // Parameter pipe_name: Name of the Windows named pipe // Parameter pipe_sec_attrs Security attributes to set on the pipe. Pass // NULL to use default security on the pipe. By default, the pipe created // allows Local System, Administrators and the Creator full control and // the Everyone group read access on the pipe. // Parameter connect_callback: Callback for a new client connection. // Parameter connect_context: Context for client connection callback. // Parameter crash_callback: Callback for a client crash dump request. // Parameter crash_context: Context for client crash dump request callback. // Parameter exit_callback: Callback for client process exit. // Parameter exit_context: Context for client exit callback. // Parameter generate_dumps: Whether to automatically generate dumps. // Client code of this class might want to generate dumps explicitly in the // crash dump request callback. In that case, false can be passed for this // parameter. // Parameter dump_path: Path for generating dumps; required only if true is // passed for generateDumps parameter; NULL can be passed otherwise. CrashGenerationServer(const std::wstring& pipe_name, SECURITY_ATTRIBUTES* pipe_sec_attrs, OnClientConnectedCallback connect_callback, void* connect_context, OnClientDumpRequestCallback dump_callback, void* dump_context, OnClientExitedCallback exit_callback, void* exit_context, bool generate_dumps, const std::wstring* dump_path); ~CrashGenerationServer(); // Performs initialization steps needed to start listening to clients. // // Returns true if initialization is successful; false otherwise. bool Start(); private: // Various states the client can be in during the handshake with // the server. enum IPCServerState { // Server is in error state and it cannot serve any clients. IPC_SERVER_STATE_ERROR, // Server starts in this state. IPC_SERVER_STATE_INITIAL, // Server has issued an async connect to the pipe and it is waiting // for the connection to be established. IPC_SERVER_STATE_CONNECTING, // Server is connected successfully. IPC_SERVER_STATE_CONNECTED, // Server has issued an async read from the pipe and it is waiting for // the read to finish. IPC_SERVER_STATE_READING, // Server is done reading from the pipe. IPC_SERVER_STATE_READ_DONE, // Server has issued an async write to the pipe and it is waiting for // the write to finish. IPC_SERVER_STATE_WRITING, // Server is done writing to the pipe. IPC_SERVER_STATE_WRITE_DONE, // Server has issued an async read from the pipe for an ack and it // is waiting for the read to finish. IPC_SERVER_STATE_READING_ACK, // Server is done writing to the pipe and it is now ready to disconnect // and reconnect. IPC_SERVER_STATE_DISCONNECTING }; // // Helper methods to handle various server IPC states. // void HandleErrorState(); void HandleInitialState(); void HandleConnectingState(); void HandleConnectedState(); void HandleReadingState(); void HandleReadDoneState(); void HandleWritingState(); void HandleWriteDoneState(); void HandleReadingAckState(); void HandleDisconnectingState(); // Prepares reply for a client from the given parameters. bool PrepareReply(const ClientInfo& client_info, ProtocolMessage* reply) const; // Duplicates various handles in the ClientInfo object for the client // process and stores them in the given ProtocolMessage instance. If // creating any handle fails, ProtocolMessage will contain the handles // already created successfully, which should be closed by the caller. bool CreateClientHandles(const ClientInfo& client_info, ProtocolMessage* reply) const; // Response to the given client. Return true if all steps of // responding to the client succeed, false otherwise. bool RespondToClient(ClientInfo* client_info); // Handles a connection request from the client. void HandleConnectionRequest(); // Handles a dump request from the client. void HandleDumpRequest(const ClientInfo& client_info); // Callback for pipe connected event. static void CALLBACK OnPipeConnected(void* context, BOOLEAN timer_or_wait); // Callback for a dump request. static void CALLBACK OnDumpRequest(void* context, BOOLEAN timer_or_wait); // Callback for client process exit event. static void CALLBACK OnClientEnd(void* context, BOOLEAN timer_or_wait); // Releases resources for a client. static DWORD WINAPI CleanupClient(void* context); // Cleans up for the given client. void DoCleanup(ClientInfo* client_info); // Adds the given client to the list of registered clients. bool AddClient(ClientInfo* client_info); // Generates dump for the given client. bool GenerateDump(const ClientInfo& client, std::wstring* dump_path); // Sync object for thread-safe access to the shared list of clients. CRITICAL_SECTION clients_sync_; // List of clients. std::list clients_; // Pipe name. std::wstring pipe_name_; // Pipe security attributes SECURITY_ATTRIBUTES* pipe_sec_attrs_; // Handle to the pipe used for handshake with clients. HANDLE pipe_; // Pipe wait handle. HANDLE pipe_wait_handle_; // Handle to server-alive mutex. HANDLE server_alive_handle_; // Callback for a successful client connection. OnClientConnectedCallback connect_callback_; // Context for client connected callback. void* connect_context_; // Callback for a client dump request. OnClientDumpRequestCallback dump_callback_; // Context for client dump request callback. void* dump_context_; // Callback for client process exit. OnClientExitedCallback exit_callback_; // Context for client process exit callback. void* exit_context_; // Whether to generate dumps. bool generate_dumps_; // Instance of a mini dump generator. scoped_ptr dump_generator_; // State of the server in performing the IPC with the client. // Note that since we restrict the pipe to one instance, we // only need to keep one state of the server. Otherwise, server // would have one state per client it is talking to. volatile IPCServerState server_state_; // Whether the server is shutting down. volatile bool shutting_down_; // Overlapped instance for async I/O on the pipe. OVERLAPPED overlapped_; // Message object used in IPC with the client. ProtocolMessage msg_; // Client Info for the client that's connecting to the server. ClientInfo* client_info_; // Count of clean-up work items that are currently running or are // already queued to run. volatile LONG cleanup_item_count_; // Disable copy ctor and operator=. CrashGenerationServer(const CrashGenerationServer& crash_server); CrashGenerationServer& operator=(const CrashGenerationServer& crash_server); }; } // namespace google_breakpad #endif // CLIENT_WINDOWS_CRASH_GENERATION_CRASH_GENERATION_SERVER_H__ minidump_generator.cc000066400000000000000000000240111271170121200361250ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/client/windows/crash_generation// Copyright (c) 2008, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "client/windows/crash_generation/minidump_generator.h" #include #include "client/windows/common/auto_critical_section.h" #include "common/windows/guid_string.h" using std::wstring; namespace google_breakpad { MinidumpGenerator::MinidumpGenerator(const wstring& dump_path) : dbghelp_module_(NULL), rpcrt4_module_(NULL), dump_path_(dump_path), write_dump_(NULL), create_uuid_(NULL) { InitializeCriticalSection(&module_load_sync_); InitializeCriticalSection(&get_proc_address_sync_); } MinidumpGenerator::~MinidumpGenerator() { if (dbghelp_module_) { FreeLibrary(dbghelp_module_); } if (rpcrt4_module_) { FreeLibrary(rpcrt4_module_); } DeleteCriticalSection(&get_proc_address_sync_); DeleteCriticalSection(&module_load_sync_); } bool MinidumpGenerator::WriteMinidump(HANDLE process_handle, DWORD process_id, DWORD thread_id, DWORD requesting_thread_id, EXCEPTION_POINTERS* exception_pointers, MDRawAssertionInfo* assert_info, MINIDUMP_TYPE dump_type, bool is_client_pointers, wstring* dump_path) { MiniDumpWriteDumpType write_dump = GetWriteDump(); if (!write_dump) { return false; } wstring dump_file_path; if (!GenerateDumpFilePath(&dump_file_path)) { return false; } // If the client requests a full memory dump, we will write a normal mini // dump and a full memory dump. Both dump files use the same uuid as file // name prefix. bool full_memory_dump = (dump_type & MiniDumpWithFullMemory) != 0; wstring full_dump_file_path; if (full_memory_dump) { full_dump_file_path.assign(dump_file_path); full_dump_file_path.resize(full_dump_file_path.size() - 4); // strip .dmp full_dump_file_path.append(TEXT("-full.dmp")); } HANDLE dump_file = CreateFile(dump_file_path.c_str(), GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); if (dump_file == INVALID_HANDLE_VALUE) { return false; } HANDLE full_dump_file = INVALID_HANDLE_VALUE; if (full_memory_dump) { full_dump_file = CreateFile(full_dump_file_path.c_str(), GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); if (full_dump_file == INVALID_HANDLE_VALUE) { CloseHandle(dump_file); return false; } } MINIDUMP_EXCEPTION_INFORMATION* dump_exception_pointers = NULL; MINIDUMP_EXCEPTION_INFORMATION dump_exception_info; // Setup the exception information object only if it's a dump // due to an exception. if (exception_pointers) { dump_exception_pointers = &dump_exception_info; dump_exception_info.ThreadId = thread_id; dump_exception_info.ExceptionPointers = exception_pointers; dump_exception_info.ClientPointers = is_client_pointers; } // Add an MDRawBreakpadInfo stream to the minidump, to provide additional // information about the exception handler to the Breakpad processor. // The information will help the processor determine which threads are // relevant. The Breakpad processor does not require this information but // can function better with Breakpad-generated dumps when it is present. // The native debugger is not harmed by the presence of this information. MDRawBreakpadInfo breakpad_info = {0}; if (!is_client_pointers) { // Set the dump thread id and requesting thread id only in case of // in-process dump generation. breakpad_info.validity = MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID | MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID; breakpad_info.dump_thread_id = thread_id; breakpad_info.requesting_thread_id = requesting_thread_id; } // Leave room in user_stream_array for a possible assertion info stream. MINIDUMP_USER_STREAM user_stream_array[2]; user_stream_array[0].Type = MD_BREAKPAD_INFO_STREAM; user_stream_array[0].BufferSize = sizeof(breakpad_info); user_stream_array[0].Buffer = &breakpad_info; MINIDUMP_USER_STREAM_INFORMATION user_streams; user_streams.UserStreamCount = 1; user_streams.UserStreamArray = user_stream_array; MDRawAssertionInfo* actual_assert_info = assert_info; MDRawAssertionInfo client_assert_info = {0}; if (assert_info) { // If the assertion info object lives in the client process, // read the memory of the client process. if (is_client_pointers) { SIZE_T bytes_read = 0; if (!ReadProcessMemory(process_handle, assert_info, &client_assert_info, sizeof(client_assert_info), &bytes_read)) { CloseHandle(dump_file); if (full_dump_file != INVALID_HANDLE_VALUE) CloseHandle(full_dump_file); return false; } if (bytes_read != sizeof(client_assert_info)) { CloseHandle(dump_file); if (full_dump_file != INVALID_HANDLE_VALUE) CloseHandle(full_dump_file); return false; } actual_assert_info = &client_assert_info; } user_stream_array[1].Type = MD_ASSERTION_INFO_STREAM; user_stream_array[1].BufferSize = sizeof(MDRawAssertionInfo); user_stream_array[1].Buffer = actual_assert_info; ++user_streams.UserStreamCount; } bool result_minidump = write_dump( process_handle, process_id, dump_file, static_cast((dump_type & (~MiniDumpWithFullMemory)) | MiniDumpNormal), exception_pointers ? &dump_exception_info : NULL, &user_streams, NULL) != FALSE; bool result_full_memory = true; if (full_memory_dump) { result_full_memory = write_dump( process_handle, process_id, full_dump_file, static_cast(dump_type & (~MiniDumpNormal)), exception_pointers ? &dump_exception_info : NULL, &user_streams, NULL) != FALSE; } bool result = result_minidump && result_full_memory; CloseHandle(dump_file); if (full_dump_file != INVALID_HANDLE_VALUE) CloseHandle(full_dump_file); // Store the path of the dump file in the out parameter if dump generation // succeeded. if (result && dump_path) { *dump_path = dump_file_path; } return result; } HMODULE MinidumpGenerator::GetDbghelpModule() { AutoCriticalSection lock(&module_load_sync_); if (!dbghelp_module_) { dbghelp_module_ = LoadLibrary(TEXT("dbghelp.dll")); } return dbghelp_module_; } MinidumpGenerator::MiniDumpWriteDumpType MinidumpGenerator::GetWriteDump() { AutoCriticalSection lock(&get_proc_address_sync_); if (!write_dump_) { HMODULE module = GetDbghelpModule(); if (module) { FARPROC proc = GetProcAddress(module, "MiniDumpWriteDump"); write_dump_ = reinterpret_cast(proc); } } return write_dump_; } HMODULE MinidumpGenerator::GetRpcrt4Module() { AutoCriticalSection lock(&module_load_sync_); if (!rpcrt4_module_) { rpcrt4_module_ = LoadLibrary(TEXT("rpcrt4.dll")); } return rpcrt4_module_; } MinidumpGenerator::UuidCreateType MinidumpGenerator::GetCreateUuid() { AutoCriticalSection lock(&module_load_sync_); if (!create_uuid_) { HMODULE module = GetRpcrt4Module(); if (module) { FARPROC proc = GetProcAddress(module, "UuidCreate"); create_uuid_ = reinterpret_cast(proc); } } return create_uuid_; } bool MinidumpGenerator::GenerateDumpFilePath(wstring* file_path) { UUID id = {0}; UuidCreateType create_uuid = GetCreateUuid(); if(!create_uuid) { return false; } create_uuid(&id); wstring id_str = GUIDString::GUIDToWString(&id); *file_path = dump_path_ + TEXT("\\") + id_str + TEXT(".dmp"); return true; } } // namespace google_breakpad minidump_generator.h000066400000000000000000000113611271170121200357730ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/client/windows/crash_generation// Copyright (c) 2008, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef CLIENT_WINDOWS_CRASH_GENERATION_MINIDUMP_GENERATION_H__ #define CLIENT_WINDOWS_CRASH_GENERATION_MINIDUMP_GENERATION_H__ #include #include #include #include "google_breakpad/common/minidump_format.h" namespace google_breakpad { // Abstraction for various objects and operations needed to generate // minidump on Windows. This abstraction is useful to hide all the gory // details for minidump generation and provide a clean interface to // the clients to generate minidumps. class MinidumpGenerator { public: // Creates an instance with the given dump path. explicit MinidumpGenerator(const std::wstring& dump_path); ~MinidumpGenerator(); // Writes the minidump with the given parameters. Stores the // dump file path in the dump_path parameter if dump generation // succeeds. bool WriteMinidump(HANDLE process_handle, DWORD process_id, DWORD thread_id, DWORD requesting_thread_id, EXCEPTION_POINTERS* exception_pointers, MDRawAssertionInfo* assert_info, MINIDUMP_TYPE dump_type, bool is_client_pointers, std::wstring* dump_path); private: // Function pointer type for MiniDumpWriteDump, which is looked up // dynamically. typedef BOOL (WINAPI* MiniDumpWriteDumpType)( HANDLE hProcess, DWORD ProcessId, HANDLE hFile, MINIDUMP_TYPE DumpType, CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam); // Function pointer type for UuidCreate, which is looked up dynamically. typedef RPC_STATUS (RPC_ENTRY* UuidCreateType)(UUID* Uuid); // Loads the appropriate DLL lazily in a thread safe way. HMODULE GetDbghelpModule(); // Loads the appropriate DLL and gets a pointer to the MiniDumpWriteDump // function lazily and in a thread-safe manner. MiniDumpWriteDumpType GetWriteDump(); // Loads the appropriate DLL lazily in a thread safe way. HMODULE GetRpcrt4Module(); // Loads the appropriate DLL and gets a pointer to the UuidCreate // function lazily and in a thread-safe manner. UuidCreateType GetCreateUuid(); // Returns the path for the file to write dump to. bool GenerateDumpFilePath(std::wstring* file_path); // Handle to dynamically loaded DbgHelp.dll. HMODULE dbghelp_module_; // Pointer to the MiniDumpWriteDump function. MiniDumpWriteDumpType write_dump_; // Handle to dynamically loaded rpcrt4.dll. HMODULE rpcrt4_module_; // Pointer to the UuidCreate function. UuidCreateType create_uuid_; // Folder path to store dump files. std::wstring dump_path_; // Critical section to sychronize action of loading modules dynamically. CRITICAL_SECTION module_load_sync_; // Critical section to synchronize action of dynamically getting function // addresses from modules. CRITICAL_SECTION get_proc_address_sync_; }; } // namespace google_breakpad #endif // CLIENT_WINDOWS_CRASH_GENERATION_MINIDUMP_GENERATION_H__ scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/client/windows/handler/000077500000000000000000000000001271170121200301115ustar00rootroot00000000000000exception_handler.cc000066400000000000000000000741721271170121200340470ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/client/windows/handler// Copyright (c) 2006, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include #include "common/windows/string_utils-inl.h" #include "client/windows/common/ipc_protocol.h" #include "client/windows/handler/exception_handler.h" #include "common/windows/guid_string.h" namespace google_breakpad { static const int kWaitForHandlerThreadMs = 60000; static const int kExceptionHandlerThreadInitialStackSize = 64 * 1024; vector* ExceptionHandler::handler_stack_ = NULL; LONG ExceptionHandler::handler_stack_index_ = 0; CRITICAL_SECTION ExceptionHandler::handler_stack_critical_section_; volatile LONG ExceptionHandler::instance_count_ = 0; ExceptionHandler::ExceptionHandler(const wstring& dump_path, FilterCallback filter, MinidumpCallback callback, void* callback_context, int handler_types, MINIDUMP_TYPE dump_type, const wchar_t* pipe_name, const CustomClientInfo* custom_info) { Initialize(dump_path, filter, callback, callback_context, handler_types, dump_type, pipe_name, custom_info); } ExceptionHandler::ExceptionHandler(const wstring &dump_path, FilterCallback filter, MinidumpCallback callback, void* callback_context, int handler_types) { Initialize(dump_path, filter, callback, callback_context, handler_types, MiniDumpNormal, NULL, NULL); } void ExceptionHandler::Initialize(const wstring& dump_path, FilterCallback filter, MinidumpCallback callback, void* callback_context, int handler_types, MINIDUMP_TYPE dump_type, const wchar_t* pipe_name, const CustomClientInfo* custom_info) { LONG instance_count = InterlockedIncrement(&instance_count_); filter_ = filter; callback_ = callback; callback_context_ = callback_context; dump_path_c_ = NULL; next_minidump_id_c_ = NULL; next_minidump_path_c_ = NULL; dbghelp_module_ = NULL; minidump_write_dump_ = NULL; dump_type_ = dump_type; rpcrt4_module_ = NULL; uuid_create_ = NULL; handler_types_ = handler_types; previous_filter_ = NULL; #if _MSC_VER >= 1400 // MSVC 2005/8 previous_iph_ = NULL; #endif // _MSC_VER >= 1400 previous_pch_ = NULL; handler_thread_ = NULL; is_shutdown_ = false; handler_start_semaphore_ = NULL; handler_finish_semaphore_ = NULL; requesting_thread_id_ = 0; exception_info_ = NULL; assertion_ = NULL; handler_return_value_ = false; handle_debug_exceptions_ = false; // Attempt to use out-of-process if user has specified pipe name. if (pipe_name != NULL) { scoped_ptr client( new CrashGenerationClient(pipe_name, dump_type_, custom_info)); // If successful in registering with the monitoring process, // there is no need to setup in-process crash generation. if (client->Register()) { crash_generation_client_.reset(client.release()); } } if (!IsOutOfProcess()) { // Either client did not ask for out-of-process crash generation // or registration with the server process failed. In either case, // setup to do in-process crash generation. // Set synchronization primitives and the handler thread. Each // ExceptionHandler object gets its own handler thread because that's the // only way to reliably guarantee sufficient stack space in an exception, // and it allows an easy way to get a snapshot of the requesting thread's // context outside of an exception. InitializeCriticalSection(&handler_critical_section_); handler_start_semaphore_ = CreateSemaphore(NULL, 0, 1, NULL); assert(handler_start_semaphore_ != NULL); handler_finish_semaphore_ = CreateSemaphore(NULL, 0, 1, NULL); assert(handler_finish_semaphore_ != NULL); // Don't attempt to create the thread if we could not create the semaphores. if (handler_finish_semaphore_ != NULL && handler_start_semaphore_ != NULL) { DWORD thread_id; handler_thread_ = CreateThread(NULL, // lpThreadAttributes kExceptionHandlerThreadInitialStackSize, ExceptionHandlerThreadMain, this, // lpParameter 0, // dwCreationFlags &thread_id); assert(handler_thread_ != NULL); } dbghelp_module_ = LoadLibrary(L"dbghelp.dll"); if (dbghelp_module_) { minidump_write_dump_ = reinterpret_cast( GetProcAddress(dbghelp_module_, "MiniDumpWriteDump")); } // Load this library dynamically to not affect existing projects. Most // projects don't link against this directly, it's usually dynamically // loaded by dependent code. rpcrt4_module_ = LoadLibrary(L"rpcrt4.dll"); if (rpcrt4_module_) { uuid_create_ = reinterpret_cast( GetProcAddress(rpcrt4_module_, "UuidCreate")); } // set_dump_path calls UpdateNextID. This sets up all of the path and id // strings, and their equivalent c_str pointers. set_dump_path(dump_path); } // There is a race condition here. If the first instance has not yet // initialized the critical section, the second (and later) instances may // try to use uninitialized critical section object. The feature of multiple // instances in one module is not used much, so leave it as is for now. // One way to solve this in the current design (that is, keeping the static // handler stack) is to use spin locks with volatile bools to synchronize // the handler stack. This works only if the compiler guarantees to generate // cache coherent code for volatile. // TODO(munjal): Fix this in a better way by changing the design if possible. // Lazy initialization of the handler_stack_critical_section_ if (instance_count == 1) { InitializeCriticalSection(&handler_stack_critical_section_); } if (handler_types != HANDLER_NONE) { EnterCriticalSection(&handler_stack_critical_section_); // The first time an ExceptionHandler that installs a handler is // created, set up the handler stack. if (!handler_stack_) { handler_stack_ = new vector(); } handler_stack_->push_back(this); if (handler_types & HANDLER_EXCEPTION) previous_filter_ = SetUnhandledExceptionFilter(HandleException); #if _MSC_VER >= 1400 // MSVC 2005/8 if (handler_types & HANDLER_INVALID_PARAMETER) previous_iph_ = _set_invalid_parameter_handler(HandleInvalidParameter); #endif // _MSC_VER >= 1400 if (handler_types & HANDLER_PURECALL) previous_pch_ = _set_purecall_handler(HandlePureVirtualCall); LeaveCriticalSection(&handler_stack_critical_section_); } } ExceptionHandler::~ExceptionHandler() { if (dbghelp_module_) { FreeLibrary(dbghelp_module_); } if (rpcrt4_module_) { FreeLibrary(rpcrt4_module_); } if (handler_types_ != HANDLER_NONE) { EnterCriticalSection(&handler_stack_critical_section_); if (handler_types_ & HANDLER_EXCEPTION) SetUnhandledExceptionFilter(previous_filter_); #if _MSC_VER >= 1400 // MSVC 2005/8 if (handler_types_ & HANDLER_INVALID_PARAMETER) _set_invalid_parameter_handler(previous_iph_); #endif // _MSC_VER >= 1400 if (handler_types_ & HANDLER_PURECALL) _set_purecall_handler(previous_pch_); if (handler_stack_->back() == this) { handler_stack_->pop_back(); } else { // TODO(mmentovai): use advapi32!ReportEvent to log the warning to the // system's application event log. fprintf(stderr, "warning: removing Breakpad handler out of order\n"); for (vector::iterator iterator = handler_stack_->begin(); iterator != handler_stack_->end(); ++iterator) { if (*iterator == this) { handler_stack_->erase(iterator); } } } if (handler_stack_->empty()) { // When destroying the last ExceptionHandler that installed a handler, // clean up the handler stack. delete handler_stack_; handler_stack_ = NULL; } LeaveCriticalSection(&handler_stack_critical_section_); } // Some of the objects were only initialized if out of process // registration was not done. if (!IsOutOfProcess()) { #ifdef BREAKPAD_NO_TERMINATE_THREAD // Clean up the handler thread and synchronization primitives. The handler // thread is either waiting on the semaphore to handle a crash or it is // handling a crash. Coming out of the wait is fast but wait more in the // eventuality a crash is handled. This compilation option results in a // deadlock if the exception handler is destroyed while executing code // inside DllMain. is_shutdown_ = true; ReleaseSemaphore(handler_start_semaphore_, 1, NULL); WaitForSingleObject(handler_thread_, kWaitForHandlerThreadMs); #else TerminateThread(handler_thread_, 1); #endif // BREAKPAD_NO_TERMINATE_THREAD CloseHandle(handler_thread_); handler_thread_ = NULL; DeleteCriticalSection(&handler_critical_section_); CloseHandle(handler_start_semaphore_); CloseHandle(handler_finish_semaphore_); } // There is a race condition in the code below: if this instance is // deleting the static critical section and a new instance of the class // is created, then there is a possibility that the critical section be // initialized while the same critical section is being deleted. Given the // usage pattern for the code, this race condition is unlikely to hit, but it // is a race condition nonetheless. if (InterlockedDecrement(&instance_count_) == 0) { DeleteCriticalSection(&handler_stack_critical_section_); } } // static DWORD ExceptionHandler::ExceptionHandlerThreadMain(void* lpParameter) { ExceptionHandler* self = reinterpret_cast(lpParameter); assert(self); assert(self->handler_start_semaphore_ != NULL); assert(self->handler_finish_semaphore_ != NULL); while (true) { if (WaitForSingleObject(self->handler_start_semaphore_, INFINITE) == WAIT_OBJECT_0) { // Perform the requested action. if (self->is_shutdown_) { // The instance of the exception handler is being destroyed. break; } else { self->handler_return_value_ = self->WriteMinidumpWithException(self->requesting_thread_id_, self->exception_info_, self->assertion_); } // Allow the requesting thread to proceed. ReleaseSemaphore(self->handler_finish_semaphore_, 1, NULL); } } // This statement is not reached when the thread is unconditionally // terminated by the ExceptionHandler destructor. return 0; } // HandleException and HandleInvalidParameter must create an // AutoExceptionHandler object to maintain static state and to determine which // ExceptionHandler instance to use. The constructor locates the correct // instance, and makes it available through get_handler(). The destructor // restores the state in effect prior to allocating the AutoExceptionHandler. class AutoExceptionHandler { public: AutoExceptionHandler() { // Increment handler_stack_index_ so that if another Breakpad handler is // registered using this same HandleException function, and it needs to be // called while this handler is running (either becaause this handler // declines to handle the exception, or an exception occurs during // handling), HandleException will find the appropriate ExceptionHandler // object in handler_stack_ to deliver the exception to. // // Because handler_stack_ is addressed in reverse (as |size - index|), // preincrementing handler_stack_index_ avoids needing to subtract 1 from // the argument to |at|. // // The index is maintained instead of popping elements off of the handler // stack and pushing them at the end of this method. This avoids ruining // the order of elements in the stack in the event that some other thread // decides to manipulate the handler stack (such as creating a new // ExceptionHandler object) while an exception is being handled. EnterCriticalSection(&ExceptionHandler::handler_stack_critical_section_); handler_ = ExceptionHandler::handler_stack_->at( ExceptionHandler::handler_stack_->size() - ++ExceptionHandler::handler_stack_index_); LeaveCriticalSection(&ExceptionHandler::handler_stack_critical_section_); // In case another exception occurs while this handler is doing its thing, // it should be delivered to the previous filter. SetUnhandledExceptionFilter(handler_->previous_filter_); #if _MSC_VER >= 1400 // MSVC 2005/8 _set_invalid_parameter_handler(handler_->previous_iph_); #endif // _MSC_VER >= 1400 _set_purecall_handler(handler_->previous_pch_); } ~AutoExceptionHandler() { // Put things back the way they were before entering this handler. SetUnhandledExceptionFilter(ExceptionHandler::HandleException); #if _MSC_VER >= 1400 // MSVC 2005/8 _set_invalid_parameter_handler(ExceptionHandler::HandleInvalidParameter); #endif // _MSC_VER >= 1400 _set_purecall_handler(ExceptionHandler::HandlePureVirtualCall); EnterCriticalSection(&ExceptionHandler::handler_stack_critical_section_); --ExceptionHandler::handler_stack_index_; LeaveCriticalSection(&ExceptionHandler::handler_stack_critical_section_); } ExceptionHandler* get_handler() const { return handler_; } private: ExceptionHandler* handler_; }; // static LONG ExceptionHandler::HandleException(EXCEPTION_POINTERS* exinfo) { AutoExceptionHandler auto_exception_handler; ExceptionHandler* current_handler = auto_exception_handler.get_handler(); // Ignore EXCEPTION_BREAKPOINT and EXCEPTION_SINGLE_STEP exceptions. This // logic will short-circuit before calling WriteMinidumpOnHandlerThread, // allowing something else to handle the breakpoint without incurring the // overhead transitioning to and from the handler thread. This behavior // can be overridden by calling ExceptionHandler::set_handle_debug_exceptions. DWORD code = exinfo->ExceptionRecord->ExceptionCode; LONG action; bool is_debug_exception = (code == EXCEPTION_BREAKPOINT) || (code == EXCEPTION_SINGLE_STEP); bool success = false; if (!is_debug_exception || current_handler->get_handle_debug_exceptions()) { // If out-of-proc crash handler client is available, we have to use that // to generate dump and we cannot fall back on in-proc dump generation // because we never prepared for an in-proc dump generation // In case of out-of-process dump generation, directly call // WriteMinidumpWithException since there is no separate thread running. if (current_handler->IsOutOfProcess()) { success = current_handler->WriteMinidumpWithException( GetCurrentThreadId(), exinfo, NULL); } else { success = current_handler->WriteMinidumpOnHandlerThread(exinfo, NULL); } } // The handler fully handled the exception. Returning // EXCEPTION_EXECUTE_HANDLER indicates this to the system, and usually // results in the application being terminated. // // Note: If the application was launched from within the Cygwin // environment, returning EXCEPTION_EXECUTE_HANDLER seems to cause the // application to be restarted. if (success) { action = EXCEPTION_EXECUTE_HANDLER; } else { // There was an exception, it was a breakpoint or something else ignored // above, or it was passed to the handler, which decided not to handle it. // This could be because the filter callback didn't want it, because // minidump writing failed for some reason, or because the post-minidump // callback function indicated failure. Give the previous handler a // chance to do something with the exception. If there is no previous // handler, return EXCEPTION_CONTINUE_SEARCH, which will allow a debugger // or native "crashed" dialog to handle the exception. if (current_handler->previous_filter_) { action = current_handler->previous_filter_(exinfo); } else { action = EXCEPTION_CONTINUE_SEARCH; } } return action; } #if _MSC_VER >= 1400 // MSVC 2005/8 // static void ExceptionHandler::HandleInvalidParameter(const wchar_t* expression, const wchar_t* function, const wchar_t* file, unsigned int line, uintptr_t reserved) { // This is an invalid parameter, not an exception. It's safe to play with // sprintf here. AutoExceptionHandler auto_exception_handler; ExceptionHandler* current_handler = auto_exception_handler.get_handler(); MDRawAssertionInfo assertion; memset(&assertion, 0, sizeof(assertion)); _snwprintf_s(reinterpret_cast(assertion.expression), sizeof(assertion.expression) / sizeof(assertion.expression[0]), _TRUNCATE, L"%s", expression); _snwprintf_s(reinterpret_cast(assertion.function), sizeof(assertion.function) / sizeof(assertion.function[0]), _TRUNCATE, L"%s", function); _snwprintf_s(reinterpret_cast(assertion.file), sizeof(assertion.file) / sizeof(assertion.file[0]), _TRUNCATE, L"%s", file); assertion.line = line; assertion.type = MD_ASSERTION_INFO_TYPE_INVALID_PARAMETER; bool success = false; // In case of out-of-process dump generation, directly call // WriteMinidumpWithException since there is no separate thread running. if (current_handler->IsOutOfProcess()) { success = current_handler->WriteMinidumpWithException( GetCurrentThreadId(), NULL, &assertion); } else { success = current_handler->WriteMinidumpOnHandlerThread(NULL, &assertion); } if (!success) { if (current_handler->previous_iph_) { // The handler didn't fully handle the exception. Give it to the // previous invalid parameter handler. current_handler->previous_iph_(expression, function, file, line, reserved); } else { // If there's no previous handler, pass the exception back in to the // invalid parameter handler's core. That's the routine that called this // function, but now, since this function is no longer registered (and in // fact, no function at all is registered), this will result in the // default code path being taken: _CRT_DEBUGGER_HOOK and _invoke_watson. // Use _invalid_parameter where it exists (in _DEBUG builds) as it passes // more information through. In non-debug builds, it is not available, // so fall back to using _invalid_parameter_noinfo. See invarg.c in the // CRT source. #ifdef _DEBUG _invalid_parameter(expression, function, file, line, reserved); #else // _DEBUG _invalid_parameter_noinfo(); #endif // _DEBUG } } // The handler either took care of the invalid parameter problem itself, // or passed it on to another handler. "Swallow" it by exiting, paralleling // the behavior of "swallowing" exceptions. exit(0); } #endif // _MSC_VER >= 1400 // static void ExceptionHandler::HandlePureVirtualCall() { AutoExceptionHandler auto_exception_handler; ExceptionHandler* current_handler = auto_exception_handler.get_handler(); MDRawAssertionInfo assertion; memset(&assertion, 0, sizeof(assertion)); assertion.type = MD_ASSERTION_INFO_TYPE_PURE_VIRTUAL_CALL; bool success = false; // In case of out-of-process dump generation, directly call // WriteMinidumpWithException since there is no separate thread running. if (current_handler->IsOutOfProcess()) { success = current_handler->WriteMinidumpWithException( GetCurrentThreadId(), NULL, &assertion); } else { success = current_handler->WriteMinidumpOnHandlerThread(NULL, &assertion); } if (!success) { if (current_handler->previous_pch_) { // The handler didn't fully handle the exception. Give it to the // previous purecall handler. current_handler->previous_pch_(); } else { // If there's no previous handler, return and let _purecall handle it. // This will just put up an assertion dialog. return; } } // The handler either took care of the invalid parameter problem itself, // or passed it on to another handler. "Swallow" it by exiting, paralleling // the behavior of "swallowing" exceptions. exit(0); } bool ExceptionHandler::WriteMinidumpOnHandlerThread( EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion) { EnterCriticalSection(&handler_critical_section_); // There isn't much we can do if the handler thread // was not successfully created. if (handler_thread_ == NULL) { LeaveCriticalSection(&handler_critical_section_); return false; } // The handler thread should only be created when the semaphores are valid. assert(handler_start_semaphore_ != NULL); assert(handler_finish_semaphore_ != NULL); // Set up data to be passed in to the handler thread. requesting_thread_id_ = GetCurrentThreadId(); exception_info_ = exinfo; assertion_ = assertion; // This causes the handler thread to call WriteMinidumpWithException. ReleaseSemaphore(handler_start_semaphore_, 1, NULL); // Wait until WriteMinidumpWithException is done and collect its return value. WaitForSingleObject(handler_finish_semaphore_, INFINITE); bool status = handler_return_value_; // Clean up. requesting_thread_id_ = 0; exception_info_ = NULL; assertion_ = NULL; LeaveCriticalSection(&handler_critical_section_); return status; } bool ExceptionHandler::WriteMinidump() { return WriteMinidumpForException(NULL); } bool ExceptionHandler::WriteMinidumpForException(EXCEPTION_POINTERS* exinfo) { // In case of out-of-process dump generation, directly call // WriteMinidumpWithException since there is no separate thread running. if (IsOutOfProcess()) { return WriteMinidumpWithException(GetCurrentThreadId(), exinfo, NULL); } bool success = WriteMinidumpOnHandlerThread(exinfo, NULL); UpdateNextID(); return success; } // static bool ExceptionHandler::WriteMinidump(const wstring &dump_path, MinidumpCallback callback, void* callback_context) { ExceptionHandler handler(dump_path, NULL, callback, callback_context, HANDLER_NONE); return handler.WriteMinidump(); } bool ExceptionHandler::WriteMinidumpWithException( DWORD requesting_thread_id, EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion) { // Give user code a chance to approve or prevent writing a minidump. If the // filter returns false, don't handle the exception at all. If this method // was called as a result of an exception, returning false will cause // HandleException to call any previous handler or return // EXCEPTION_CONTINUE_SEARCH on the exception thread, allowing it to appear // as though this handler were not present at all. if (filter_ && !filter_(callback_context_, exinfo, assertion)) { return false; } bool success = false; if (IsOutOfProcess()) { // Use the EXCEPTION_POINTERS overload for RequestDump if // both exinfo and assertion are NULL. if (!assertion) { success = crash_generation_client_->RequestDump(exinfo); } else { success = crash_generation_client_->RequestDump(assertion); } } else { if (minidump_write_dump_) { HANDLE dump_file = CreateFile(next_minidump_path_c_, GENERIC_WRITE, 0, // no sharing NULL, CREATE_NEW, // fail if exists FILE_ATTRIBUTE_NORMAL, NULL); if (dump_file != INVALID_HANDLE_VALUE) { MINIDUMP_EXCEPTION_INFORMATION except_info; except_info.ThreadId = requesting_thread_id; except_info.ExceptionPointers = exinfo; except_info.ClientPointers = FALSE; // Add an MDRawBreakpadInfo stream to the minidump, to provide additional // information about the exception handler to the Breakpad processor. The // information will help the processor determine which threads are // relevant. The Breakpad processor does not require this information but // can function better with Breakpad-generated dumps when it is present. // The native debugger is not harmed by the presence of this information. MDRawBreakpadInfo breakpad_info; breakpad_info.validity = MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID | MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID; breakpad_info.dump_thread_id = GetCurrentThreadId(); breakpad_info.requesting_thread_id = requesting_thread_id; // Leave room in user_stream_array for a possible assertion info stream. MINIDUMP_USER_STREAM user_stream_array[2]; user_stream_array[0].Type = MD_BREAKPAD_INFO_STREAM; user_stream_array[0].BufferSize = sizeof(breakpad_info); user_stream_array[0].Buffer = &breakpad_info; MINIDUMP_USER_STREAM_INFORMATION user_streams; user_streams.UserStreamCount = 1; user_streams.UserStreamArray = user_stream_array; if (assertion) { user_stream_array[1].Type = MD_ASSERTION_INFO_STREAM; user_stream_array[1].BufferSize = sizeof(MDRawAssertionInfo); user_stream_array[1].Buffer = assertion; ++user_streams.UserStreamCount; } // The explicit comparison to TRUE avoids a warning (C4800). success = (minidump_write_dump_(GetCurrentProcess(), GetCurrentProcessId(), dump_file, dump_type_, exinfo ? &except_info : NULL, &user_streams, NULL) == TRUE); CloseHandle(dump_file); } } } if (callback_) { // TODO(munjal): In case of out-of-process dump generation, both // dump_path_c_ and next_minidump_id_ will be NULL. For out-of-process // scenario, the server process ends up creating the dump path and dump // id so they are not known to the client. success = callback_(dump_path_c_, next_minidump_id_c_, callback_context_, exinfo, assertion, success); } return success; } void ExceptionHandler::UpdateNextID() { assert(uuid_create_); UUID id = {0}; if (uuid_create_) { uuid_create_(&id); } next_minidump_id_ = GUIDString::GUIDToWString(&id); next_minidump_id_c_ = next_minidump_id_.c_str(); wchar_t minidump_path[MAX_PATH]; swprintf(minidump_path, MAX_PATH, L"%s\\%s.dmp", dump_path_c_, next_minidump_id_c_); // remove when VC++7.1 is no longer supported minidump_path[MAX_PATH - 1] = L'\0'; next_minidump_path_ = minidump_path; next_minidump_path_c_ = next_minidump_path_.c_str(); } } // namespace google_breakpad exception_handler.h000066400000000000000000000456741271170121200337160ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/client/windows/handler// Copyright (c) 2006, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // ExceptionHandler can write a minidump file when an exception occurs, // or when WriteMinidump() is called explicitly by your program. // // To have the exception handler write minidumps when an uncaught exception // (crash) occurs, you should create an instance early in the execution // of your program, and keep it around for the entire time you want to // have crash handling active (typically, until shutdown). // // If you want to write minidumps without installing the exception handler, // you can create an ExceptionHandler with install_handler set to false, // then call WriteMinidump. You can also use this technique if you want to // use different minidump callbacks for different call sites. // // In either case, a callback function is called when a minidump is written, // which receives the unqiue id of the minidump. The caller can use this // id to collect and write additional application state, and to launch an // external crash-reporting application. // // It is important that creation and destruction of ExceptionHandler objects // be nested cleanly, when using install_handler = true. // Avoid the following pattern: // ExceptionHandler *e = new ExceptionHandler(...); // ExceptionHandler *f = new ExceptionHandler(...); // delete e; // This will put the exception filter stack into an inconsistent state. #ifndef CLIENT_WINDOWS_HANDLER_EXCEPTION_HANDLER_H__ #define CLIENT_WINDOWS_HANDLER_EXCEPTION_HANDLER_H__ #include #include #include #include #pragma warning( push ) // Disable exception handler warnings. #pragma warning( disable : 4530 ) #include #include #include "client/windows/common/ipc_protocol.h" #include "client/windows/crash_generation/crash_generation_client.h" #include "google_breakpad/common/minidump_format.h" #include "processor/scoped_ptr.h" namespace google_breakpad { using std::vector; using std::wstring; class ExceptionHandler { public: // A callback function to run before Breakpad performs any substantial // processing of an exception. A FilterCallback is called before writing // a minidump. context is the parameter supplied by the user as // callback_context when the handler was created. exinfo points to the // exception record, if any; assertion points to assertion information, // if any. // // If a FilterCallback returns true, Breakpad will continue processing, // attempting to write a minidump. If a FilterCallback returns false, Breakpad // will immediately report the exception as unhandled without writing a // minidump, allowing another handler the opportunity to handle it. typedef bool (*FilterCallback)(void* context, EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion); // A callback function to run after the minidump has been written. // minidump_id is a unique id for the dump, so the minidump // file is \.dmp. context is the parameter supplied // by the user as callback_context when the handler was created. exinfo // points to the exception record, or NULL if no exception occurred. // succeeded indicates whether a minidump file was successfully written. // assertion points to information about an assertion if the handler was // invoked by an assertion. // // If an exception occurred and the callback returns true, Breakpad will treat // the exception as fully-handled, suppressing any other handlers from being // notified of the exception. If the callback returns false, Breakpad will // treat the exception as unhandled, and allow another handler to handle it. // If there are no other handlers, Breakpad will report the exception to the // system as unhandled, allowing a debugger or native crash dialog the // opportunity to handle the exception. Most callback implementations // should normally return the value of |succeeded|, or when they wish to // not report an exception of handled, false. Callbacks will rarely want to // return true directly (unless |succeeded| is true). // // For out-of-process dump generation, dump path and minidump ID will always // be NULL. In case of out-of-process dump generation, the dump path and // minidump id are controlled by the server process and are not communicated // back to the crashing process. typedef bool (*MinidumpCallback)(const wchar_t* dump_path, const wchar_t* minidump_id, void* context, EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion, bool succeeded); // HandlerType specifies which types of handlers should be installed, if // any. Use HANDLER_NONE for an ExceptionHandler that remains idle, // without catching any failures on its own. This type of handler may // still be triggered by calling WriteMinidump. Otherwise, use a // combination of the other HANDLER_ values, or HANDLER_ALL to install // all handlers. enum HandlerType { HANDLER_NONE = 0, HANDLER_EXCEPTION = 1 << 0, // SetUnhandledExceptionFilter HANDLER_INVALID_PARAMETER = 1 << 1, // _set_invalid_parameter_handler HANDLER_PURECALL = 1 << 2, // _set_purecall_handler HANDLER_ALL = HANDLER_EXCEPTION | HANDLER_INVALID_PARAMETER | HANDLER_PURECALL }; // Creates a new ExceptionHandler instance to handle writing minidumps. // Before writing a minidump, the optional filter callback will be called. // Its return value determines whether or not Breakpad should write a // minidump. Minidump files will be written to dump_path, and the optional // callback is called after writing the dump file, as described above. // handler_types specifies the types of handlers that should be installed. ExceptionHandler(const wstring& dump_path, FilterCallback filter, MinidumpCallback callback, void* callback_context, int handler_types); // Creates a new ExcetpionHandler instance that can attempt to perform // out-of-process dump generation if pipe_name is not NULL. If pipe_name is // NULL, or if out-of-process dump generation registration step fails, // in-process dump generation will be used. This also allows specifying // the dump type to generate. ExceptionHandler(const wstring& dump_path, FilterCallback filter, MinidumpCallback callback, void* callback_context, int handler_types, MINIDUMP_TYPE dump_type, const wchar_t* pipe_name, const CustomClientInfo* custom_info); ~ExceptionHandler(); // Get and set the minidump path. wstring dump_path() const { return dump_path_; } void set_dump_path(const wstring &dump_path) { dump_path_ = dump_path; dump_path_c_ = dump_path_.c_str(); UpdateNextID(); // Necessary to put dump_path_ in next_minidump_path_. } // Writes a minidump immediately. This can be used to capture the // execution state independently of a crash. Returns true on success. bool WriteMinidump(); // Writes a minidump immediately, with the user-supplied exception // information. bool WriteMinidumpForException(EXCEPTION_POINTERS* exinfo); // Convenience form of WriteMinidump which does not require an // ExceptionHandler instance. static bool WriteMinidump(const wstring &dump_path, MinidumpCallback callback, void* callback_context); // Get the thread ID of the thread requesting the dump (either the exception // thread or any other thread that called WriteMinidump directly). This // may be useful if you want to include additional thread state in your // dumps. DWORD get_requesting_thread_id() const { return requesting_thread_id_; } // Controls behavior of EXCEPTION_BREAKPOINT and EXCEPTION_SINGLE_STEP. bool get_handle_debug_exceptions() const { return handle_debug_exceptions_; } void set_handle_debug_exceptions(bool handle_debug_exceptions) { handle_debug_exceptions_ = handle_debug_exceptions; } // Returns whether out-of-process dump generation is used or not. bool IsOutOfProcess() const { return crash_generation_client_.get() != NULL; } private: friend class AutoExceptionHandler; // Initializes the instance with given values. void Initialize(const wstring& dump_path, FilterCallback filter, MinidumpCallback callback, void* callback_context, int handler_types, MINIDUMP_TYPE dump_type, const wchar_t* pipe_name, const CustomClientInfo* custom_info); // Function pointer type for MiniDumpWriteDump, which is looked up // dynamically. typedef BOOL (WINAPI *MiniDumpWriteDump_type)( HANDLE hProcess, DWORD dwPid, HANDLE hFile, MINIDUMP_TYPE DumpType, CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam); // Function pointer type for UuidCreate, which is looked up dynamically. typedef RPC_STATUS (RPC_ENTRY *UuidCreate_type)(UUID* Uuid); // Runs the main loop for the exception handler thread. static DWORD WINAPI ExceptionHandlerThreadMain(void* lpParameter); // Called on the exception thread when an unhandled exception occurs. // Signals the exception handler thread to handle the exception. static LONG WINAPI HandleException(EXCEPTION_POINTERS* exinfo); #if _MSC_VER >= 1400 // MSVC 2005/8 // This function will be called by some CRT functions when they detect // that they were passed an invalid parameter. Note that in _DEBUG builds, // the CRT may display an assertion dialog before calling this function, // and the function will not be called unless the assertion dialog is // dismissed by clicking "Ignore." static void HandleInvalidParameter(const wchar_t* expression, const wchar_t* function, const wchar_t* file, unsigned int line, uintptr_t reserved); #endif // _MSC_VER >= 1400 // This function will be called by the CRT when a pure virtual // function is called. static void HandlePureVirtualCall(); // This is called on the exception thread or on another thread that // the user wishes to produce a dump from. It calls // WriteMinidumpWithException on the handler thread, avoiding stack // overflows and inconsistent dumps due to writing the dump from // the exception thread. If the dump is requested as a result of an // exception, exinfo contains exception information, otherwise, it // is NULL. If the dump is requested as a result of an assertion // (such as an invalid parameter being passed to a CRT function), // assertion contains data about the assertion, otherwise, it is NULL. bool WriteMinidumpOnHandlerThread(EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion); // This function does the actual writing of a minidump. It is called // on the handler thread. requesting_thread_id is the ID of the thread // that requested the dump. If the dump is requested as a result of // an exception, exinfo contains exception information, otherwise, // it is NULL. bool WriteMinidumpWithException(DWORD requesting_thread_id, EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion); // Generates a new ID and stores it in next_minidump_id_, and stores the // path of the next minidump to be written in next_minidump_path_. void UpdateNextID(); FilterCallback filter_; MinidumpCallback callback_; void* callback_context_; scoped_ptr crash_generation_client_; // The directory in which a minidump will be written, set by the dump_path // argument to the constructor, or set_dump_path. wstring dump_path_; // The basename of the next minidump to be written, without the extension. wstring next_minidump_id_; // The full pathname of the next minidump to be written, including the file // extension. wstring next_minidump_path_; // Pointers to C-string representations of the above. These are set when // the above wstring versions are set in order to avoid calling c_str during // an exception, as c_str may attempt to allocate heap memory. These // pointers are not owned by the ExceptionHandler object, but their lifetimes // should be equivalent to the lifetimes of the associated wstring, provided // that the wstrings are not altered. const wchar_t* dump_path_c_; const wchar_t* next_minidump_id_c_; const wchar_t* next_minidump_path_c_; HMODULE dbghelp_module_; MiniDumpWriteDump_type minidump_write_dump_; MINIDUMP_TYPE dump_type_; HMODULE rpcrt4_module_; UuidCreate_type uuid_create_; // Tracks the handler types that were installed according to the // handler_types constructor argument. int handler_types_; // When installed_handler_ is true, previous_filter_ is the unhandled // exception filter that was set prior to installing ExceptionHandler as // the unhandled exception filter and pointing it to |this|. NULL indicates // that there is no previous unhandled exception filter. LPTOP_LEVEL_EXCEPTION_FILTER previous_filter_; #if _MSC_VER >= 1400 // MSVC 2005/8 // Beginning in VC 8, the CRT provides an invalid parameter handler that will // be called when some CRT functions are passed invalid parameters. In // earlier CRTs, the same conditions would cause unexpected behavior or // crashes. _invalid_parameter_handler previous_iph_; #endif // _MSC_VER >= 1400 // The CRT allows you to override the default handler for pure // virtual function calls. _purecall_handler previous_pch_; // The exception handler thread. HANDLE handler_thread_; // True if the exception handler is being destroyed. // Starting with MSVC 2005, Visual C has stronger guarantees on volatile vars. // It has release semantics on write and acquire semantics on reads. // See the msdn documentation. volatile bool is_shutdown_; // The critical section enforcing the requirement that only one exception be // handled by a handler at a time. CRITICAL_SECTION handler_critical_section_; // Semaphores used to move exception handling between the exception thread // and the handler thread. handler_start_semaphore_ is signalled by the // exception thread to wake up the handler thread when an exception occurs. // handler_finish_semaphore_ is signalled by the handler thread to wake up // the exception thread when handling is complete. HANDLE handler_start_semaphore_; HANDLE handler_finish_semaphore_; // The next 2 fields contain data passed from the requesting thread to // the handler thread. // The thread ID of the thread requesting the dump (either the exception // thread or any other thread that called WriteMinidump directly). DWORD requesting_thread_id_; // The exception info passed to the exception handler on the exception // thread, if an exception occurred. NULL for user-requested dumps. EXCEPTION_POINTERS* exception_info_; // If the handler is invoked due to an assertion, this will contain a // pointer to the assertion information. It is NULL at other times. MDRawAssertionInfo* assertion_; // The return value of the handler, passed from the handler thread back to // the requesting thread. bool handler_return_value_; // If true, the handler will intercept EXCEPTION_BREAKPOINT and // EXCEPTION_SINGLE_STEP exceptions. Leave this false (the default) // to not interfere with debuggers. bool handle_debug_exceptions_; // A stack of ExceptionHandler objects that have installed unhandled // exception filters. This vector is used by HandleException to determine // which ExceptionHandler object to route an exception to. When an // ExceptionHandler is created with install_handler true, it will append // itself to this list. static vector* handler_stack_; // The index of the ExceptionHandler in handler_stack_ that will handle the // next exception. Note that 0 means the last entry in handler_stack_, 1 // means the next-to-last entry, and so on. This is used by HandleException // to support multiple stacked Breakpad handlers. static LONG handler_stack_index_; // handler_stack_critical_section_ guards operations on handler_stack_ and // handler_stack_index_. The critical section is initialized by the // first instance of the class and destroyed by the last instance of it. static CRITICAL_SECTION handler_stack_critical_section_; // The number of instances of this class. volatile static LONG instance_count_; // disallow copy ctor and operator= explicit ExceptionHandler(const ExceptionHandler &); void operator=(const ExceptionHandler &); }; } // namespace google_breakpad #pragma warning( pop ) #endif // CLIENT_WINDOWS_HANDLER_EXCEPTION_HANDLER_H__ scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/common/000077500000000000000000000000001271170121200250145ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/common/minidump_format.h000066400000000000000000000741001271170121200303610ustar00rootroot00000000000000/* Copyright (c) 2006, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* minidump_format.h: A cross-platform reimplementation of minidump-related * portions of DbgHelp.h from the Windows Platform SDK. * * (This is C99 source, please don't corrupt it with C++.) * * Structures that are defined by Microsoft to contain a zero-length array * are instead defined here to contain an array with one element, as * zero-length arrays are forbidden by standard C and C++. In these cases, * *_minsize constants are provided to be used in place of sizeof. For a * cleaner interface to these sizes when using C++, see minidump_size.h. * * These structures are also sufficient to populate minidump files. * * These definitions may be extended to support handling minidump files * for other CPUs and other operating systems. * * Because precise data type sizes are crucial for this implementation to * function properly and portably in terms of interoperability with minidumps * produced by DbgHelp on Windows, a set of primitive types with known sizes * are used as the basis of each structure defined by this file. DbgHelp * on Windows is assumed to be the reference implementation; this file * seeks to provide a cross-platform compatible implementation. To avoid * collisions with the types and values defined and used by DbgHelp in the * event that this implementation is used on Windows, each type and value * defined here is given a new name, beginning with "MD". Names of the * equivalent types and values in the Windows Platform SDK are given in * comments. * * Author: Mark Mentovai */ #ifndef GOOGLE_BREAKPAD_COMMON_MINIDUMP_FORMAT_H__ #define GOOGLE_BREAKPAD_COMMON_MINIDUMP_FORMAT_H__ #include #include "google_breakpad/common/breakpad_types.h" #if defined(_MSC_VER) /* Disable "zero-sized array in struct/union" warnings when compiling in * MSVC. DbgHelp.h does this too. */ #pragma warning(push) #pragma warning(disable:4200) #endif /* _MSC_VER */ /* * guiddef.h */ typedef struct { u_int32_t data1; u_int16_t data2; u_int16_t data3; u_int8_t data4[8]; } MDGUID; /* GUID */ /* * WinNT.h */ /* Non-x86 CPU identifiers found in the high 26 bits of * (MDRawContext*).context_flags. These aren't used by Breakpad, but are * defined here for reference, to avoid assigning values that conflict * (although some values already conflict). */ #define MD_CONTEXT_IA64 0x00080000 /* CONTEXT_IA64 */ #define MD_CONTEXT_AMD64 0x00100000 /* CONTEXT_AMD64 */ /* Additional values from winnt.h in the Windows CE 5.0 SDK: */ #define MD_CONTEXT_SHX 0x000000c0 /* CONTEXT_SH4 (Super-H, includes SH3) */ #define MD_CONTEXT_ARM 0x00000040 /* CONTEXT_ARM (0x40 bit set in SHx?) */ #define MD_CONTEXT_MIPS 0x00010000 /* CONTEXT_R4000 (same value as x86?) */ #define MD_CONTEXT_ALPHA 0x00020000 /* CONTEXT_ALPHA */ #define MD_CONTEXT_CPU_MASK 0xffffffc0 /* This is a base type for MDRawContextX86 and MDRawContextPPC. This * structure should never be allocated directly. The actual structure type * can be determined by examining the context_flags field. */ typedef struct { u_int32_t context_flags; } MDRawContextBase; #include "minidump_cpu_sparc.h" #include "minidump_cpu_x86.h" #include "minidump_cpu_ppc.h" #include "minidump_cpu_ppc64.h" #include "minidump_cpu_amd64.h" /* * WinVer.h */ typedef struct { u_int32_t signature; u_int32_t struct_version; u_int32_t file_version_hi; u_int32_t file_version_lo; u_int32_t product_version_hi; u_int32_t product_version_lo; u_int32_t file_flags_mask; /* Identifies valid bits in fileFlags */ u_int32_t file_flags; u_int32_t file_os; u_int32_t file_type; u_int32_t file_subtype; u_int32_t file_date_hi; u_int32_t file_date_lo; } MDVSFixedFileInfo; /* VS_FIXEDFILEINFO */ /* For (MDVSFixedFileInfo).signature */ #define MD_VSFIXEDFILEINFO_SIGNATURE 0xfeef04bd /* VS_FFI_SIGNATURE */ /* For (MDVSFixedFileInfo).version */ #define MD_VSFIXEDFILEINFO_VERSION 0x00010000 /* VS_FFI_STRUCVERSION */ /* For (MDVSFixedFileInfo).file_flags_mask and * (MDVSFixedFileInfo).file_flags */ #define MD_VSFIXEDFILEINFO_FILE_FLAGS_DEBUG 0x00000001 /* VS_FF_DEBUG */ #define MD_VSFIXEDFILEINFO_FILE_FLAGS_PRERELEASE 0x00000002 /* VS_FF_PRERELEASE */ #define MD_VSFIXEDFILEINFO_FILE_FLAGS_PATCHED 0x00000004 /* VS_FF_PATCHED */ #define MD_VSFIXEDFILEINFO_FILE_FLAGS_PRIVATEBUILD 0x00000008 /* VS_FF_PRIVATEBUILD */ #define MD_VSFIXEDFILEINFO_FILE_FLAGS_INFOINFERRED 0x00000010 /* VS_FF_INFOINFERRED */ #define MD_VSFIXEDFILEINFO_FILE_FLAGS_SPECIALBUILD 0x00000020 /* VS_FF_SPECIALBUILD */ /* For (MDVSFixedFileInfo).file_os: high 16 bits */ #define MD_VSFIXEDFILEINFO_FILE_OS_UNKNOWN 0 /* VOS_UNKNOWN */ #define MD_VSFIXEDFILEINFO_FILE_OS_DOS (1 << 16) /* VOS_DOS */ #define MD_VSFIXEDFILEINFO_FILE_OS_OS216 (2 << 16) /* VOS_OS216 */ #define MD_VSFIXEDFILEINFO_FILE_OS_OS232 (3 << 16) /* VOS_OS232 */ #define MD_VSFIXEDFILEINFO_FILE_OS_NT (4 << 16) /* VOS_NT */ #define MD_VSFIXEDFILEINFO_FILE_OS_WINCE (5 << 16) /* VOS_WINCE */ /* Low 16 bits */ #define MD_VSFIXEDFILEINFO_FILE_OS__BASE 0 /* VOS__BASE */ #define MD_VSFIXEDFILEINFO_FILE_OS__WINDOWS16 1 /* VOS__WINDOWS16 */ #define MD_VSFIXEDFILEINFO_FILE_OS__PM16 2 /* VOS__PM16 */ #define MD_VSFIXEDFILEINFO_FILE_OS__PM32 3 /* VOS__PM32 */ #define MD_VSFIXEDFILEINFO_FILE_OS__WINDOWS32 4 /* VOS__WINDOWS32 */ /* For (MDVSFixedFileInfo).file_type */ #define MD_VSFIXEDFILEINFO_FILE_TYPE_UNKNOWN 0 /* VFT_UNKNOWN */ #define MD_VSFIXEDFILEINFO_FILE_TYPE_APP 1 /* VFT_APP */ #define MD_VSFIXEDFILEINFO_FILE_TYPE_DLL 2 /* VFT_DLL */ #define MD_VSFIXEDFILEINFO_FILE_TYPE_DRV 3 /* VFT_DLL */ #define MD_VSFIXEDFILEINFO_FILE_TYPE_FONT 4 /* VFT_FONT */ #define MD_VSFIXEDFILEINFO_FILE_TYPE_VXD 5 /* VFT_VXD */ #define MD_VSFIXEDFILEINFO_FILE_TYPE_STATIC_LIB 7 /* VFT_STATIC_LIB */ /* For (MDVSFixedFileInfo).file_subtype */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_UNKNOWN 0 /* VFT2_UNKNOWN */ /* with file_type = MD_VSFIXEDFILEINFO_FILETYPE_DRV */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_PRINTER 1 /* VFT2_DRV_PRINTER */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_KEYBOARD 2 /* VFT2_DRV_KEYBOARD */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_LANGUAGE 3 /* VFT2_DRV_LANGUAGE */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_DISPLAY 4 /* VFT2_DRV_DISPLAY */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_MOUSE 5 /* VFT2_DRV_MOUSE */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_NETWORK 6 /* VFT2_DRV_NETWORK */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_SYSTEM 7 /* VFT2_DRV_SYSTEM */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_INSTALLABLE 8 /* VFT2_DRV_INSTALLABLE */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_SOUND 9 /* VFT2_DRV_SOUND */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_COMM 10 /* VFT2_DRV_COMM */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_INPUTMETHOD 11 /* VFT2_DRV_INPUTMETHOD */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_VERSIONED_PRINTER 12 /* VFT2_DRV_VERSIONED_PRINTER */ /* with file_type = MD_VSFIXEDFILEINFO_FILETYPE_FONT */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_FONT_RASTER 1 /* VFT2_FONT_RASTER */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_FONT_VECTOR 2 /* VFT2_FONT_VECTOR */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_FONT_TRUETYPE 3 /* VFT2_FONT_TRUETYPE */ /* * DbgHelp.h */ /* An MDRVA is an offset into the minidump file. The beginning of the * MDRawHeader is at offset 0. */ typedef u_int32_t MDRVA; /* RVA */ typedef struct { u_int32_t data_size; MDRVA rva; } MDLocationDescriptor; /* MINIDUMP_LOCATION_DESCRIPTOR */ typedef struct { /* The base address of the memory range on the host that produced the * minidump. */ u_int64_t start_of_memory_range; MDLocationDescriptor memory; } MDMemoryDescriptor; /* MINIDUMP_MEMORY_DESCRIPTOR */ typedef struct { u_int32_t signature; u_int32_t version; u_int32_t stream_count; MDRVA stream_directory_rva; /* A |stream_count|-sized array of * MDRawDirectory structures. */ u_int32_t checksum; /* Can be 0. In fact, that's all that's * been found in minidump files. */ u_int32_t time_date_stamp; /* time_t */ u_int64_t flags; } MDRawHeader; /* MINIDUMP_HEADER */ /* For (MDRawHeader).signature and (MDRawHeader).version. Note that only the * low 16 bits of (MDRawHeader).version are MD_HEADER_VERSION. Per the * documentation, the high 16 bits are implementation-specific. */ #define MD_HEADER_SIGNATURE 0x504d444d /* 'PMDM' */ /* MINIDUMP_SIGNATURE */ #define MD_HEADER_VERSION 0x0000a793 /* 42899 */ /* MINIDUMP_VERSION */ /* For (MDRawHeader).flags: */ typedef enum { /* MD_NORMAL is the standard type of minidump. It includes full * streams for the thread list, module list, exception, system info, * and miscellaneous info. A memory list stream is also present, * pointing to the same stack memory contained in the thread list, * as well as a 256-byte region around the instruction address that * was executing when the exception occurred. Stack memory is from * 4 bytes below a thread's stack pointer up to the top of the * memory region encompassing the stack. */ MD_NORMAL = 0x00000000, MD_WITH_DATA_SEGS = 0x00000001, MD_WITH_FULL_MEMORY = 0x00000002, MD_WITH_HANDLE_DATA = 0x00000004, MD_FILTER_MEMORY = 0x00000008, MD_SCAN_MEMORY = 0x00000010, MD_WITH_UNLOADED_MODULES = 0x00000020, MD_WITH_INDIRECTLY_REFERENCED_MEMORY = 0x00000040, MD_FILTER_MODULE_PATHS = 0x00000080, MD_WITH_PROCESS_THREAD_DATA = 0x00000100, MD_WITH_PRIVATE_READ_WRITE_MEMORY = 0x00000200, MD_WITHOUT_OPTIONAL_DATA = 0x00000400, MD_WITH_FULL_MEMORY_INFO = 0x00000800, MD_WITH_THREAD_INFO = 0x00001000, MD_WITH_CODE_SEGS = 0x00002000 } MDType; /* MINIDUMP_TYPE */ typedef struct { u_int32_t stream_type; MDLocationDescriptor location; } MDRawDirectory; /* MINIDUMP_DIRECTORY */ /* For (MDRawDirectory).stream_type */ typedef enum { MD_UNUSED_STREAM = 0, MD_RESERVED_STREAM_0 = 1, MD_RESERVED_STREAM_1 = 2, MD_THREAD_LIST_STREAM = 3, /* MDRawThreadList */ MD_MODULE_LIST_STREAM = 4, /* MDRawModuleList */ MD_MEMORY_LIST_STREAM = 5, /* MDRawMemoryList */ MD_EXCEPTION_STREAM = 6, /* MDRawExceptionStream */ MD_SYSTEM_INFO_STREAM = 7, /* MDRawSystemInfo */ MD_THREAD_EX_LIST_STREAM = 8, MD_MEMORY_64_LIST_STREAM = 9, MD_COMMENT_STREAM_A = 10, MD_COMMENT_STREAM_W = 11, MD_HANDLE_DATA_STREAM = 12, MD_FUNCTION_TABLE_STREAM = 13, MD_UNLOADED_MODULE_LIST_STREAM = 14, MD_MISC_INFO_STREAM = 15, /* MDRawMiscInfo */ MD_LAST_RESERVED_STREAM = 0x0000ffff, /* Breakpad extension types. 0x4767 = "Gg" */ MD_BREAKPAD_INFO_STREAM = 0x47670001, /* MDRawBreakpadInfo */ MD_ASSERTION_INFO_STREAM = 0x47670002 /* MDRawAssertionInfo */ } MDStreamType; /* MINIDUMP_STREAM_TYPE */ typedef struct { u_int32_t length; /* Length of buffer in bytes (not characters), * excluding 0-terminator */ u_int16_t buffer[1]; /* UTF-16-encoded, 0-terminated */ } MDString; /* MINIDUMP_STRING */ static const size_t MDString_minsize = offsetof(MDString, buffer[0]); typedef struct { u_int32_t thread_id; u_int32_t suspend_count; u_int32_t priority_class; u_int32_t priority; u_int64_t teb; /* Thread environment block */ MDMemoryDescriptor stack; MDLocationDescriptor thread_context; /* MDRawContext[CPU] */ } MDRawThread; /* MINIDUMP_THREAD */ typedef struct { u_int32_t number_of_threads; MDRawThread threads[1]; } MDRawThreadList; /* MINIDUMP_THREAD_LIST */ static const size_t MDRawThreadList_minsize = offsetof(MDRawThreadList, threads[0]); typedef struct { u_int64_t base_of_image; u_int32_t size_of_image; u_int32_t checksum; /* 0 if unknown */ u_int32_t time_date_stamp; /* time_t */ MDRVA module_name_rva; /* MDString, pathname or filename */ MDVSFixedFileInfo version_info; /* The next field stores a CodeView record and is populated when a module's * debug information resides in a PDB file. It identifies the PDB file. */ MDLocationDescriptor cv_record; /* The next field is populated when a module's debug information resides * in a DBG file. It identifies the DBG file. This field is effectively * obsolete with modules built by recent toolchains. */ MDLocationDescriptor misc_record; /* Alignment problem: reserved0 and reserved1 are defined by the platform * SDK as 64-bit quantities. However, that results in a structure whose * alignment is unpredictable on different CPUs and ABIs. If the ABI * specifies full alignment of 64-bit quantities in structures (as ppc * does), there will be padding between miscRecord and reserved0. If * 64-bit quantities can be aligned on 32-bit boundaries (as on x86), * this padding will not exist. (Note that the structure up to this point * contains 1 64-bit member followed by 21 32-bit members.) * As a workaround, reserved0 and reserved1 are instead defined here as * four 32-bit quantities. This should be harmless, as there are * currently no known uses for these fields. */ u_int32_t reserved0[2]; u_int32_t reserved1[2]; } MDRawModule; /* MINIDUMP_MODULE */ /* The inclusion of a 64-bit type in MINIDUMP_MODULE forces the struct to * be tail-padded out to a multiple of 64 bits under some ABIs (such as PPC). * This doesn't occur on systems that don't tail-pad in this manner. Define * this macro to be the usable size of the MDRawModule struct, and use it in * place of sizeof(MDRawModule). */ #define MD_MODULE_SIZE 108 /* (MDRawModule).cv_record can reference MDCVInfoPDB20 or MDCVInfoPDB70. * Ref.: http://www.debuginfo.com/articles/debuginfomatch.html * MDCVInfoPDB70 is the expected structure type with recent toolchains. */ typedef struct { u_int32_t signature; u_int32_t offset; /* Offset to debug data (expect 0 in minidump) */ } MDCVHeader; typedef struct { MDCVHeader cv_header; u_int32_t signature; /* time_t debug information created */ u_int32_t age; /* revision of PDB file */ u_int8_t pdb_file_name[1]; /* Pathname or filename of PDB file */ } MDCVInfoPDB20; static const size_t MDCVInfoPDB20_minsize = offsetof(MDCVInfoPDB20, pdb_file_name[0]); #define MD_CVINFOPDB20_SIGNATURE 0x3031424e /* cvHeader.signature = '01BN' */ typedef struct { u_int32_t cv_signature; MDGUID signature; /* GUID, identifies PDB file */ u_int32_t age; /* Identifies incremental changes to PDB file */ u_int8_t pdb_file_name[1]; /* Pathname or filename of PDB file, * 0-terminated 8-bit character data (UTF-8?) */ } MDCVInfoPDB70; static const size_t MDCVInfoPDB70_minsize = offsetof(MDCVInfoPDB70, pdb_file_name[0]); #define MD_CVINFOPDB70_SIGNATURE 0x53445352 /* cvSignature = 'SDSR' */ typedef struct { u_int32_t data1[2]; u_int32_t data2; u_int32_t data3; u_int32_t data4; u_int32_t data5[3]; u_int8_t extra[2]; } MDCVInfoELF; /* In addition to the two CodeView record formats above, used for linking * to external pdb files, it is possible for debugging data to be carried * directly in the CodeView record itself. These signature values will * be found in the first 4 bytes of the CodeView record. Additional values * not commonly experienced in the wild are given by "Microsoft Symbol and * Type Information", http://www.x86.org/ftp/manuals/tools/sym.pdf, section * 7.2. An in-depth description of the CodeView 4.1 format is given by * "Undocumented Windows 2000 Secrets", Windows 2000 Debugging Support/ * Microsoft Symbol File Internals/CodeView Subsections, * http://www.rawol.com/features/undocumented/sbs-w2k-1-windows-2000-debugging-support.pdf */ #define MD_CVINFOCV41_SIGNATURE 0x3930424e /* '90BN', CodeView 4.10. */ #define MD_CVINFOCV50_SIGNATURE 0x3131424e /* '11BN', CodeView 5.0, * MS C7-format (/Z7). */ #define MD_CVINFOUNKNOWN_SIGNATURE 0xffffffff /* An unlikely value. */ /* (MDRawModule).miscRecord can reference MDImageDebugMisc. The Windows * structure is actually defined in WinNT.h. This structure is effectively * obsolete with modules built by recent toolchains. */ typedef struct { u_int32_t data_type; /* IMAGE_DEBUG_TYPE_*, not defined here because * this debug record type is mostly obsolete. */ u_int32_t length; /* Length of entire MDImageDebugMisc structure */ u_int8_t unicode; /* True if data is multibyte */ u_int8_t reserved[3]; u_int8_t data[1]; } MDImageDebugMisc; /* IMAGE_DEBUG_MISC */ static const size_t MDImageDebugMisc_minsize = offsetof(MDImageDebugMisc, data[0]); typedef struct { u_int32_t number_of_modules; MDRawModule modules[1]; } MDRawModuleList; /* MINIDUMP_MODULE_LIST */ static const size_t MDRawModuleList_minsize = offsetof(MDRawModuleList, modules[0]); typedef struct { u_int32_t number_of_memory_ranges; MDMemoryDescriptor memory_ranges[1]; } MDRawMemoryList; /* MINIDUMP_MEMORY_LIST */ static const size_t MDRawMemoryList_minsize = offsetof(MDRawMemoryList, memory_ranges[0]); #define MD_EXCEPTION_MAXIMUM_PARAMETERS 15 typedef struct { u_int32_t exception_code; /* Windows: MDExceptionCodeWin, * Mac OS X: MDExceptionMac, * Linux: MDExceptionCodeLinux. */ u_int32_t exception_flags; /* Windows: 1 if noncontinuable, Mac OS X: MDExceptionCodeMac. */ u_int64_t exception_record; /* Address (in the minidump-producing host's * memory) of another MDException, for * nested exceptions. */ u_int64_t exception_address; /* The address that caused the exception. * Mac OS X: exception subcode (which is * typically the address). */ u_int32_t number_parameters; /* Number of valid elements in * exception_information. */ u_int32_t __align; u_int64_t exception_information[MD_EXCEPTION_MAXIMUM_PARAMETERS]; } MDException; /* MINIDUMP_EXCEPTION */ #include "minidump_exception_win32.h" #include "minidump_exception_mac.h" #include "minidump_exception_linux.h" #include "minidump_exception_solaris.h" typedef struct { u_int32_t thread_id; /* Thread in which the exception * occurred. Corresponds to * (MDRawThread).thread_id. */ u_int32_t __align; MDException exception_record; MDLocationDescriptor thread_context; /* MDRawContext[CPU] */ } MDRawExceptionStream; /* MINIDUMP_EXCEPTION_STREAM */ typedef union { struct { u_int32_t vendor_id[3]; /* cpuid 0: ebx, edx, ecx */ u_int32_t version_information; /* cpuid 1: eax */ u_int32_t feature_information; /* cpuid 1: edx */ u_int32_t amd_extended_cpu_features; /* cpuid 0x80000001, ebx */ } x86_cpu_info; struct { u_int64_t processor_features[2]; } other_cpu_info; } MDCPUInformation; /* CPU_INFORMATION */ typedef struct { /* The next 3 fields and numberOfProcessors are from the SYSTEM_INFO * structure as returned by GetSystemInfo */ u_int16_t processor_architecture; u_int16_t processor_level; /* x86: 5 = 586, 6 = 686, ... */ u_int16_t processor_revision; /* x86: 0xMMSS, where MM=model, * SS=stepping */ u_int8_t number_of_processors; u_int8_t product_type; /* Windows: VER_NT_* from WinNT.h */ /* The next 5 fields are from the OSVERSIONINFO structure as returned * by GetVersionEx */ u_int32_t major_version; u_int32_t minor_version; u_int32_t build_number; u_int32_t platform_id; MDRVA csd_version_rva; /* MDString further identifying the * host OS. * Windows: name of the installed OS * service pack. * Mac OS X: the Apple OS build number * (sw_vers -buildVersion). * Linux: uname -srvmo */ u_int16_t suite_mask; /* Windows: VER_SUITE_* from WinNT.h */ u_int16_t reserved2; MDCPUInformation cpu; } MDRawSystemInfo; /* MINIDUMP_SYSTEM_INFO */ /* For (MDRawSystemInfo).processor_architecture: */ typedef enum { MD_CPU_ARCHITECTURE_X86 = 0, /* PROCESSOR_ARCHITECTURE_INTEL */ MD_CPU_ARCHITECTURE_MIPS = 1, /* PROCESSOR_ARCHITECTURE_MIPS */ MD_CPU_ARCHITECTURE_ALPHA = 2, /* PROCESSOR_ARCHITECTURE_ALPHA */ MD_CPU_ARCHITECTURE_PPC = 3, /* PROCESSOR_ARCHITECTURE_PPC */ MD_CPU_ARCHITECTURE_SHX = 4, /* PROCESSOR_ARCHITECTURE_SHX * (Super-H) */ MD_CPU_ARCHITECTURE_ARM = 5, /* PROCESSOR_ARCHITECTURE_ARM */ MD_CPU_ARCHITECTURE_IA64 = 6, /* PROCESSOR_ARCHITECTURE_IA64 */ MD_CPU_ARCHITECTURE_ALPHA64 = 7, /* PROCESSOR_ARCHITECTURE_ALPHA64 */ MD_CPU_ARCHITECTURE_MSIL = 8, /* PROCESSOR_ARCHITECTURE_MSIL * (Microsoft Intermediate Language) */ MD_CPU_ARCHITECTURE_AMD64 = 9, /* PROCESSOR_ARCHITECTURE_AMD64 */ MD_CPU_ARCHITECTURE_X86_WIN64 = 10, /* PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 (WoW64) */ MD_CPU_ARCHITECTURE_SPARC = 0x8001, /* Breakpad-defined value for SPARC */ MD_CPU_ARCHITECTURE_UNKNOWN = 0xffff /* PROCESSOR_ARCHITECTURE_UNKNOWN */ } MDCPUArchitecture; /* For (MDRawSystemInfo).platform_id: */ typedef enum { MD_OS_WIN32S = 0, /* VER_PLATFORM_WIN32s (Windows 3.1) */ MD_OS_WIN32_WINDOWS = 1, /* VER_PLATFORM_WIN32_WINDOWS (Windows 95-98-Me) */ MD_OS_WIN32_NT = 2, /* VER_PLATFORM_WIN32_NT (Windows NT, 2000+) */ MD_OS_WIN32_CE = 3, /* VER_PLATFORM_WIN32_CE, VER_PLATFORM_WIN32_HH * (Windows CE, Windows Mobile, "Handheld") */ /* The following values are Breakpad-defined. */ MD_OS_UNIX = 0x8000, /* Generic Unix-ish */ MD_OS_MAC_OS_X = 0x8101, /* Mac OS X/Darwin */ MD_OS_LINUX = 0x8201, /* Linux */ MD_OS_SOLARIS = 0x8202 /* Solaris */ } MDOSPlatform; typedef struct { u_int32_t size_of_info; /* Length of entire MDRawMiscInfo structure. */ u_int32_t flags1; /* The next field is only valid if flags1 contains * MD_MISCINFO_FLAGS1_PROCESS_ID. */ u_int32_t process_id; /* The next 3 fields are only valid if flags1 contains * MD_MISCINFO_FLAGS1_PROCESS_TIMES. */ u_int32_t process_create_time; /* time_t process started */ u_int32_t process_user_time; /* seconds of user CPU time */ u_int32_t process_kernel_time; /* seconds of kernel CPU time */ /* The following fields are not present in MINIDUMP_MISC_INFO but are * in MINIDUMP_MISC_INFO_2. When this struct is populated, these values * may not be set. Use flags1 or sizeOfInfo to determine whether these * values are present. These are only valid when flags1 contains * MD_MISCINFO_FLAGS1_PROCESSOR_POWER_INFO. */ u_int32_t processor_max_mhz; u_int32_t processor_current_mhz; u_int32_t processor_mhz_limit; u_int32_t processor_max_idle_state; u_int32_t processor_current_idle_state; } MDRawMiscInfo; /* MINIDUMP_MISC_INFO, MINIDUMP_MISC_INFO2 */ #define MD_MISCINFO_SIZE 24 #define MD_MISCINFO2_SIZE 44 /* For (MDRawMiscInfo).flags1. These values indicate which fields in the * MDRawMiscInfoStructure are valid. */ typedef enum { MD_MISCINFO_FLAGS1_PROCESS_ID = 0x00000001, /* MINIDUMP_MISC1_PROCESS_ID */ MD_MISCINFO_FLAGS1_PROCESS_TIMES = 0x00000002, /* MINIDUMP_MISC1_PROCESS_TIMES */ MD_MISCINFO_FLAGS1_PROCESSOR_POWER_INFO = 0x00000004 /* MINIDUMP_MISC1_PROCESSOR_POWER_INFO */ } MDMiscInfoFlags1; /* * Breakpad extension types */ typedef struct { /* validity is a bitmask with values from MDBreakpadInfoValidity, indicating * which of the other fields in the structure are valid. */ u_int32_t validity; /* Thread ID of the handler thread. dump_thread_id should correspond to * the thread_id of an MDRawThread in the minidump's MDRawThreadList if * a dedicated thread in that list was used to produce the minidump. If * the MDRawThreadList does not contain a dedicated thread used to produce * the minidump, this field should be set to 0 and the validity field * must not contain MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID. */ u_int32_t dump_thread_id; /* Thread ID of the thread that requested the minidump be produced. As * with dump_thread_id, requesting_thread_id should correspond to the * thread_id of an MDRawThread in the minidump's MDRawThreadList. For * minidumps produced as a result of an exception, requesting_thread_id * will be the same as the MDRawExceptionStream's thread_id field. For * minidumps produced "manually" at the program's request, * requesting_thread_id will indicate which thread caused the dump to be * written. If the minidump was produced at the request of something * other than a thread in the MDRawThreadList, this field should be set * to 0 and the validity field must not contain * MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID. */ u_int32_t requesting_thread_id; } MDRawBreakpadInfo; /* For (MDRawBreakpadInfo).validity: */ typedef enum { /* When set, the dump_thread_id field is valid. */ MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID = 1 << 0, /* When set, the requesting_thread_id field is valid. */ MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID = 1 << 1 } MDBreakpadInfoValidity; typedef struct { /* expression, function, and file are 0-terminated UTF-16 strings. They * may be truncated if necessary, but should always be 0-terminated when * written to a file. * Fixed-length strings are used because MiniDumpWriteDump doesn't offer * a way for user streams to point to arbitrary RVAs for strings. */ u_int16_t expression[128]; /* Assertion that failed... */ u_int16_t function[128]; /* ...within this function... */ u_int16_t file[128]; /* ...in this file... */ u_int32_t line; /* ...at this line. */ u_int32_t type; } MDRawAssertionInfo; /* For (MDRawAssertionInfo).type: */ typedef enum { MD_ASSERTION_INFO_TYPE_UNKNOWN = 0, /* Used for assertions that would be raised by the MSVC CRT but are * directed to an invalid parameter handler instead. */ MD_ASSERTION_INFO_TYPE_INVALID_PARAMETER, /* Used for assertions that would be raised by the MSVC CRT but are * directed to a pure virtual call handler instead. */ MD_ASSERTION_INFO_TYPE_PURE_VIRTUAL_CALL } MDAssertionInfoData; #if defined(_MSC_VER) #pragma warning(pop) #endif /* _MSC_VER */ #endif /* GOOGLE_BREAKPAD_COMMON_MINIDUMP_FORMAT_H__ */ scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/common/windows/000077500000000000000000000000001271170121200265065ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/common/windows/guid_string.cc000066400000000000000000000057421271170121200313430ustar00rootroot00000000000000// Copyright (c) 2006, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // guid_string.cc: Convert GUIDs to strings. // // See guid_string.h for documentation. #include #include "common/windows/string_utils-inl.h" #include "common/windows/guid_string.h" namespace google_breakpad { // static wstring GUIDString::GUIDToWString(GUID *guid) { wchar_t guid_string[37]; swprintf( guid_string, sizeof(guid_string) / sizeof(guid_string[0]), L"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", guid->Data1, guid->Data2, guid->Data3, guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3], guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7]); // remove when VC++7.1 is no longer supported guid_string[sizeof(guid_string) / sizeof(guid_string[0]) - 1] = L'\0'; return wstring(guid_string); } // static wstring GUIDString::GUIDToSymbolServerWString(GUID *guid) { wchar_t guid_string[33]; swprintf( guid_string, sizeof(guid_string) / sizeof(guid_string[0]), L"%08X%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X", guid->Data1, guid->Data2, guid->Data3, guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3], guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7]); // remove when VC++7.1 is no longer supported guid_string[sizeof(guid_string) / sizeof(guid_string[0]) - 1] = L'\0'; return wstring(guid_string); } } // namespace google_breakpad scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/common/windows/guid_string.h000066400000000000000000000045151271170121200312020ustar00rootroot00000000000000// Copyright (c) 2006, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // guid_string.cc: Convert GUIDs to strings. #ifndef COMMON_WINDOWS_GUID_STRING_H__ #define COMMON_WINDOWS_GUID_STRING_H__ #include #include namespace google_breakpad { using std::wstring; class GUIDString { public: // Converts guid to a string in the format recommended by RFC 4122 and // returns the string. static wstring GUIDToWString(GUID *guid); // Converts guid to a string formatted as uppercase hexadecimal, with // no separators, and returns the string. This is the format used for // symbol server identifiers, although identifiers have an age tacked // on to the string. static wstring GUIDToSymbolServerWString(GUID *guid); }; } // namespace google_breakpad #endif // COMMON_WINDOWS_GUID_STRING_H__ scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/common/windows/string_utils-inl.h000066400000000000000000000135511271170121200321720ustar00rootroot00000000000000// Copyright (c) 2006, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // string_utils-inl.h: Safer string manipulation on Windows, supporting // pre-MSVC8 environments. #ifndef COMMON_WINDOWS_STRING_UTILS_INL_H__ #define COMMON_WINDOWS_STRING_UTILS_INL_H__ #include #include #include // The "ll" printf format size specifier corresponding to |long long| was // intrudced in MSVC8. Earlier versions did not provide this size specifier, // but "I64" can be used to print 64-bit types. Don't use "I64" where "ll" // is available, in the event of oddball systems where |long long| is not // 64 bits wide. #if _MSC_VER >= 1400 // MSVC 2005/8 #define WIN_STRING_FORMAT_LL "ll" #else // MSC_VER >= 1400 #define WIN_STRING_FORMAT_LL "I64" #endif // MSC_VER >= 1400 // A nonconforming version of swprintf, without the length argument, was // included with the CRT prior to MSVC8. Although a conforming version was // also available via an overload, it is not reliably chosen. _snwprintf // behaves as a standards-confirming swprintf should, so force the use of // _snwprintf when using older CRTs. #if _MSC_VER < 1400 // MSVC 2005/8 #define swprintf _snwprintf #else // For MSVC8 and newer, swprintf_s is the recommended method. Conveniently, // it takes the same argument list as swprintf. #define swprintf swprintf_s #endif // MSC_VER < 1400 namespace google_breakpad { using std::string; using std::wstring; class WindowsStringUtils { public: // Roughly equivalent to MSVC8's wcscpy_s, except pre-MSVC8, this does // not fail if source is longer than destination_size. The destination // buffer is always 0-terminated. static void safe_wcscpy(wchar_t *destination, size_t destination_size, const wchar_t *source); // Roughly equivalent to MSVC8's wcsncpy_s, except that _TRUNCATE cannot // be passed directly, and pre-MSVC8, this will not fail if source or count // are longer than destination_size. The destination buffer is always // 0-terminated. static void safe_wcsncpy(wchar_t *destination, size_t destination_size, const wchar_t *source, size_t count); // Performs multi-byte to wide character conversion on C++ strings, using // mbstowcs_s (MSVC8) or mbstowcs (pre-MSVC8). Returns false on failure, // without setting wcs. static bool safe_mbstowcs(const string &mbs, wstring *wcs); // Returns the base name of a file, e.g. strips off the path. static wstring GetBaseName(const wstring &filename); private: // Disallow instantiation and other object-based operations. WindowsStringUtils(); WindowsStringUtils(const WindowsStringUtils&); ~WindowsStringUtils(); void operator=(const WindowsStringUtils&); }; // static inline void WindowsStringUtils::safe_wcscpy(wchar_t *destination, size_t destination_size, const wchar_t *source) { #if _MSC_VER >= 1400 // MSVC 2005/8 wcscpy_s(destination, destination_size, source); #else // _MSC_VER >= 1400 // Pre-MSVC 2005/8 doesn't have wcscpy_s. Simulate it with wcsncpy. // wcsncpy doesn't 0-terminate the destination buffer if the source string // is longer than size. Ensure that the destination is 0-terminated. wcsncpy(destination, source, destination_size); if (destination && destination_size) destination[destination_size - 1] = 0; #endif // _MSC_VER >= 1400 } // static inline void WindowsStringUtils::safe_wcsncpy(wchar_t *destination, size_t destination_size, const wchar_t *source, size_t count) { #if _MSC_VER >= 1400 // MSVC 2005/8 wcsncpy_s(destination, destination_size, source, count); #else // _MSC_VER >= 1400 // Pre-MSVC 2005/8 doesn't have wcsncpy_s. Simulate it with wcsncpy. // wcsncpy doesn't 0-terminate the destination buffer if the source string // is longer than size. Ensure that the destination is 0-terminated. if (destination_size < count) count = destination_size; wcsncpy(destination, source, count); if (destination && count) destination[count - 1] = 0; #endif // _MSC_VER >= 1400 } } // namespace google_breakpad #endif // COMMON_WINDOWS_STRING_UTILS_INL_H__ scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/google_breakpad/000077500000000000000000000000001271170121200266315ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/google_breakpad/common/000077500000000000000000000000001271170121200301215ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/google_breakpad/common/breakpad_types.h000066400000000000000000000057451271170121200333020ustar00rootroot00000000000000/* Copyright (c) 2006, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* breakpad_types.h: Precise-width types * * (This is C99 source, please don't corrupt it with C++.) * * This file ensures that types u_intN_t are defined for N = 8, 16, 32, and * 64. Types of precise widths are crucial to the task of writing data * structures on one platform and reading them on another. * * Author: Mark Mentovai */ #ifndef GOOGLE_BREAKPAD_COMMON_BREAKPAD_TYPES_H__ #define GOOGLE_BREAKPAD_COMMON_BREAKPAD_TYPES_H__ #ifndef _WIN32 #include #ifndef __STDC_FORMAT_MACROS #define __STDC_FORMAT_MACROS #endif /* __STDC_FORMAT_MACROS */ #include #if defined(__SUNPRO_CC) || (defined(__GNUC__) && defined(__sun__)) typedef uint8_t u_int8_t; typedef uint16_t u_int16_t; typedef uint32_t u_int32_t; typedef uint64_t u_int64_t; #endif #else /* !_WIN32 */ #include typedef unsigned __int8 u_int8_t; typedef unsigned __int16 u_int16_t; typedef unsigned __int32 u_int32_t; typedef unsigned __int64 u_int64_t; #endif /* !_WIN32 */ typedef struct { u_int64_t high; u_int64_t low; } u_int128_t; typedef u_int64_t breakpad_time_t; /* Try to get PRIx64 from inttypes.h, but if it's not defined, fall back to * llx, which is the format string for "long long" - this is a 64-bit * integral type on many systems. */ #ifndef PRIx64 #define PRIx64 "llx" #endif /* !PRIx64 */ #endif /* GOOGLE_BREAKPAD_COMMON_BREAKPAD_TYPES_H__ */ minidump_cpu_amd64.h000066400000000000000000000210301271170121200336730ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/google_breakpad/common/* Copyright (c) 2006, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* minidump_format.h: A cross-platform reimplementation of minidump-related * portions of DbgHelp.h from the Windows Platform SDK. * * (This is C99 source, please don't corrupt it with C++.) * * This file contains the necessary definitions to read minidump files * produced on amd64. These files may be read on any platform provided * that the alignments of these structures on the processing system are * identical to the alignments of these structures on the producing system. * For this reason, precise-sized types are used. The structures defined * by this file have been laid out to minimize alignment problems by ensuring * ensuring that all members are aligned on their natural boundaries. In * In some cases, tail-padding may be significant when different ABIs specify * different tail-padding behaviors. To avoid problems when reading or * writing affected structures, MD_*_SIZE macros are provided where needed, * containing the useful size of the structures without padding. * * Structures that are defined by Microsoft to contain a zero-length array * are instead defined here to contain an array with one element, as * zero-length arrays are forbidden by standard C and C++. In these cases, * *_minsize constants are provided to be used in place of sizeof. For a * cleaner interface to these sizes when using C++, see minidump_size.h. * * These structures are also sufficient to populate minidump files. * * These definitions may be extended to support handling minidump files * for other CPUs and other operating systems. * * Because precise data type sizes are crucial for this implementation to * function properly and portably in terms of interoperability with minidumps * produced by DbgHelp on Windows, a set of primitive types with known sizes * are used as the basis of each structure defined by this file. DbgHelp * on Windows is assumed to be the reference implementation; this file * seeks to provide a cross-platform compatible implementation. To avoid * collisions with the types and values defined and used by DbgHelp in the * event that this implementation is used on Windows, each type and value * defined here is given a new name, beginning with "MD". Names of the * equivalent types and values in the Windows Platform SDK are given in * comments. * * Author: Mark Mentovai * Change to split into its own file: Neal Sidhwaney */ #ifndef GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_AMD64_H__ #define GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_AMD64_H__ /* * AMD64 support, see WINNT.H */ typedef struct { u_int16_t control_word; u_int16_t status_word; u_int8_t tag_word; u_int8_t reserved1; u_int16_t error_opcode; u_int32_t error_offset; u_int16_t error_selector; u_int16_t reserved2; u_int32_t data_offset; u_int16_t data_selector; u_int16_t reserved3; u_int32_t mx_csr; u_int32_t mx_csr_mask; u_int128_t float_registers[8]; u_int128_t xmm_registers[16]; u_int8_t reserved4[96]; } MDXmmSaveArea32AMD64; /* XMM_SAVE_AREA32 */ #define MD_CONTEXT_AMD64_VR_COUNT 26 typedef struct { /* * Register parameter home addresses. */ u_int64_t p1_home; u_int64_t p2_home; u_int64_t p3_home; u_int64_t p4_home; u_int64_t p5_home; u_int64_t p6_home; /* The next field determines the layout of the structure, and which parts * of it are populated */ u_int32_t context_flags; u_int32_t mx_csr; /* The next register is included with MD_CONTEXT_AMD64_CONTROL */ u_int16_t cs; /* The next 4 registers are included with MD_CONTEXT_AMD64_SEGMENTS */ u_int16_t ds; u_int16_t es; u_int16_t fs; u_int16_t gs; /* The next 2 registers are included with MD_CONTEXT_AMD64_CONTROL */ u_int16_t ss; u_int32_t eflags; /* The next 6 registers are included with MD_CONTEXT_AMD64_DEBUG_REGISTERS */ u_int64_t dr0; u_int64_t dr1; u_int64_t dr2; u_int64_t dr3; u_int64_t dr6; u_int64_t dr7; /* The next 4 registers are included with MD_CONTEXT_AMD64_INTEGER */ u_int64_t rax; u_int64_t rcx; u_int64_t rdx; u_int64_t rbx; /* The next register is included with MD_CONTEXT_AMD64_CONTROL */ u_int64_t rsp; /* The next 11 registers are included with MD_CONTEXT_AMD64_INTEGER */ u_int64_t rbp; u_int64_t rsi; u_int64_t rdi; u_int64_t r8; u_int64_t r9; u_int64_t r10; u_int64_t r11; u_int64_t r12; u_int64_t r13; u_int64_t r14; u_int64_t r15; /* The next register is included with MD_CONTEXT_AMD64_CONTROL */ u_int64_t rip; /* The next set of registers are included with * MD_CONTEXT_AMD64_FLOATING_POINT */ union { MDXmmSaveArea32AMD64 flt_save; struct { u_int128_t header[2]; u_int128_t legacy[8]; u_int128_t xmm0; u_int128_t xmm1; u_int128_t xmm2; u_int128_t xmm3; u_int128_t xmm4; u_int128_t xmm5; u_int128_t xmm6; u_int128_t xmm7; u_int128_t xmm8; u_int128_t xmm9; u_int128_t xmm10; u_int128_t xmm11; u_int128_t xmm12; u_int128_t xmm13; u_int128_t xmm14; u_int128_t xmm15; } sse_registers; }; u_int128_t vector_register[MD_CONTEXT_AMD64_VR_COUNT]; u_int64_t vector_control; /* The next 5 registers are included with MD_CONTEXT_AMD64_DEBUG_REGISTERS */ u_int64_t debug_control; u_int64_t last_branch_to_rip; u_int64_t last_branch_from_rip; u_int64_t last_exception_to_rip; u_int64_t last_exception_from_rip; } MDRawContextAMD64; /* CONTEXT */ /* For (MDRawContextAMD64).context_flags. These values indicate the type of * context stored in the structure. The high 26 bits identify the CPU, the * low 6 bits identify the type of context saved. */ #define MD_CONTEXT_AMD64_CONTROL (MD_CONTEXT_AMD64 | 0x00000001) /* CONTEXT_CONTROL */ #define MD_CONTEXT_AMD64_INTEGER (MD_CONTEXT_AMD64 | 0x00000002) /* CONTEXT_INTEGER */ #define MD_CONTEXT_AMD64_SEGMENTS (MD_CONTEXT_AMD64 | 0x00000004) /* CONTEXT_SEGMENTS */ #define MD_CONTEXT_AMD64_FLOATING_POINT (MD_CONTEXT_AMD64 | 0x00000008) /* CONTEXT_FLOATING_POINT */ #define MD_CONTEXT_AMD64_DEBUG_REGISTERS (MD_CONTEXT_AMD64 | 0x00000010) /* CONTEXT_DEBUG_REGISTERS */ /* WinNT.h refers to CONTEXT_MMX_REGISTERS but doesn't appear to define it * I think it really means CONTEXT_FLOATING_POINT. */ #define MD_CONTEXT_AMD64_FULL (MD_CONTEXT_AMD64_CONTROL | \ MD_CONTEXT_AMD64_INTEGER | \ MD_CONTEXT_AMD64_FLOATING_POINT) /* CONTEXT_FULL */ #define MD_CONTEXT_AMD64_ALL (MD_CONTEXT_AMD64_FULL | \ MD_CONTEXT_AMD64_SEGMENTS | \ MD_CONTEXT_X86_DEBUG_REGISTERS) /* CONTEXT_ALL */ #endif /* GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_AMD64_H__ */ scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/google_breakpad/common/minidump_cpu_ppc.h000066400000000000000000000170131271170121200336270ustar00rootroot00000000000000/* Copyright (c) 2006, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* minidump_format.h: A cross-platform reimplementation of minidump-related * portions of DbgHelp.h from the Windows Platform SDK. * * (This is C99 source, please don't corrupt it with C++.) * * This file contains the necessary definitions to read minidump files * produced on ppc. These files may be read on any platform provided * that the alignments of these structures on the processing system are * identical to the alignments of these structures on the producing system. * For this reason, precise-sized types are used. The structures defined * by this file have been laid out to minimize alignment problems by ensuring * ensuring that all members are aligned on their natural boundaries. In * In some cases, tail-padding may be significant when different ABIs specify * different tail-padding behaviors. To avoid problems when reading or * writing affected structures, MD_*_SIZE macros are provided where needed, * containing the useful size of the structures without padding. * * Structures that are defined by Microsoft to contain a zero-length array * are instead defined here to contain an array with one element, as * zero-length arrays are forbidden by standard C and C++. In these cases, * *_minsize constants are provided to be used in place of sizeof. For a * cleaner interface to these sizes when using C++, see minidump_size.h. * * These structures are also sufficient to populate minidump files. * * These definitions may be extended to support handling minidump files * for other CPUs and other operating systems. * * Because precise data type sizes are crucial for this implementation to * function properly and portably in terms of interoperability with minidumps * produced by DbgHelp on Windows, a set of primitive types with known sizes * are used as the basis of each structure defined by this file. DbgHelp * on Windows is assumed to be the reference implementation; this file * seeks to provide a cross-platform compatible implementation. To avoid * collisions with the types and values defined and used by DbgHelp in the * event that this implementation is used on Windows, each type and value * defined here is given a new name, beginning with "MD". Names of the * equivalent types and values in the Windows Platform SDK are given in * comments. * * Author: Mark Mentovai * Change to split into its own file: Neal Sidhwaney */ /* * Breakpad minidump extension for PowerPC support. Based on Darwin/Mac OS X' * mach/ppc/_types.h */ #ifndef GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_PPC_H__ #define GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_PPC_H__ #define MD_FLOATINGSAVEAREA_PPC_FPR_COUNT 32 typedef struct { /* fpregs is a double[32] in mach/ppc/_types.h, but a u_int64_t is used * here for precise sizing. */ u_int64_t fpregs[MD_FLOATINGSAVEAREA_PPC_FPR_COUNT]; u_int32_t fpscr_pad; u_int32_t fpscr; /* Status/control */ } MDFloatingSaveAreaPPC; /* Based on ppc_float_state */ #define MD_VECTORSAVEAREA_PPC_VR_COUNT 32 typedef struct { /* Vector registers (including vscr) are 128 bits, but mach/ppc/_types.h * exposes them as four 32-bit quantities. */ u_int128_t save_vr[MD_VECTORSAVEAREA_PPC_VR_COUNT]; u_int128_t save_vscr; /* Status/control */ u_int32_t save_pad5[4]; u_int32_t save_vrvalid; /* Identifies which vector registers are saved */ u_int32_t save_pad6[7]; } MDVectorSaveAreaPPC; /* ppc_vector_state */ #define MD_CONTEXT_PPC_GPR_COUNT 32 /* Use the same 32-bit alignment when accessing this structure from 64-bit code * as is used natively in 32-bit code. #pragma pack is a MSVC extension * supported by gcc. */ #if defined(__SUNPRO_C) || defined(__SUNPRO_CC) #pragma pack(4) #else #pragma pack(push, 4) #endif typedef struct { /* context_flags is not present in ppc_thread_state, but it aids * identification of MDRawContextPPC among other raw context types, * and it guarantees alignment when we get to float_save. */ u_int32_t context_flags; u_int32_t srr0; /* Machine status save/restore: stores pc * (instruction) */ u_int32_t srr1; /* Machine status save/restore: stores msr * (ps, program/machine state) */ /* ppc_thread_state contains 32 fields, r0 .. r31. Here, an array is * used for brevity. */ u_int32_t gpr[MD_CONTEXT_PPC_GPR_COUNT]; u_int32_t cr; /* Condition */ u_int32_t xer; /* Integer (fiXed-point) exception */ u_int32_t lr; /* Link */ u_int32_t ctr; /* Count */ u_int32_t mq; /* Multiply/Quotient (PPC 601, POWER only) */ u_int32_t vrsave; /* Vector save */ /* float_save and vector_save aren't present in ppc_thread_state, but * are represented in separate structures that still define a thread's * context. */ MDFloatingSaveAreaPPC float_save; MDVectorSaveAreaPPC vector_save; } MDRawContextPPC; /* Based on ppc_thread_state */ #if defined(__SUNPRO_C) || defined(__SUNPRO_CC) #pragma pack(0) #else #pragma pack(pop) #endif /* For (MDRawContextPPC).context_flags. These values indicate the type of * context stored in the structure. MD_CONTEXT_PPC is Breakpad-defined. Its * value was chosen to avoid likely conflicts with MD_CONTEXT_* for other * CPUs. */ #define MD_CONTEXT_PPC 0x20000000 #define MD_CONTEXT_PPC_BASE (MD_CONTEXT_PPC | 0x00000001) #define MD_CONTEXT_PPC_FLOATING_POINT (MD_CONTEXT_PPC | 0x00000008) #define MD_CONTEXT_PPC_VECTOR (MD_CONTEXT_PPC | 0x00000020) #define MD_CONTEXT_PPC_FULL MD_CONTEXT_PPC_BASE #define MD_CONTEXT_PPC_ALL (MD_CONTEXT_PPC_FULL | \ MD_CONTEXT_PPC_FLOATING_POINT | \ MD_CONTEXT_PPC_VECTOR) #endif /* GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_PPC_H__ */ minidump_cpu_ppc64.h000066400000000000000000000146301271170121200337240ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/google_breakpad/common/* Copyright (c) 2008, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* minidump_format.h: A cross-platform reimplementation of minidump-related * portions of DbgHelp.h from the Windows Platform SDK. * * (This is C99 source, please don't corrupt it with C++.) * * This file contains the necessary definitions to read minidump files * produced on ppc64. These files may be read on any platform provided * that the alignments of these structures on the processing system are * identical to the alignments of these structures on the producing system. * For this reason, precise-sized types are used. The structures defined * by this file have been laid out to minimize alignment problems by ensuring * ensuring that all members are aligned on their natural boundaries. In * In some cases, tail-padding may be significant when different ABIs specify * different tail-padding behaviors. To avoid problems when reading or * writing affected structures, MD_*_SIZE macros are provided where needed, * containing the useful size of the structures without padding. * * Structures that are defined by Microsoft to contain a zero-length array * are instead defined here to contain an array with one element, as * zero-length arrays are forbidden by standard C and C++. In these cases, * *_minsize constants are provided to be used in place of sizeof. For a * cleaner interface to these sizes when using C++, see minidump_size.h. * * These structures are also sufficient to populate minidump files. * * These definitions may be extended to support handling minidump files * for other CPUs and other operating systems. * * Because precise data type sizes are crucial for this implementation to * function properly and portably in terms of interoperability with minidumps * produced by DbgHelp on Windows, a set of primitive types with known sizes * are used as the basis of each structure defined by this file. DbgHelp * on Windows is assumed to be the reference implementation; this file * seeks to provide a cross-platform compatible implementation. To avoid * collisions with the types and values defined and used by DbgHelp in the * event that this implementation is used on Windows, each type and value * defined here is given a new name, beginning with "MD". Names of the * equivalent types and values in the Windows Platform SDK are given in * comments. * * Author: Neal Sidhwaney */ /* * Breakpad minidump extension for PPC64 support. Based on Darwin/Mac OS X' * mach/ppc/_types.h */ #ifndef GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_PPC64_H__ #define GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_PPC64_H__ #include "minidump_cpu_ppc.h" // these types are the same in ppc64 & ppc typedef MDFloatingSaveAreaPPC MDFloatingSaveAreaPPC64; typedef MDVectorSaveAreaPPC MDVectorSaveAreaPPC64; #define MD_CONTEXT_PPC64_GPR_COUNT MD_CONTEXT_PPC_GPR_COUNT typedef struct { /* context_flags is not present in ppc_thread_state, but it aids * identification of MDRawContextPPC among other raw context types, * and it guarantees alignment when we get to float_save. */ u_int64_t context_flags; u_int64_t srr0; /* Machine status save/restore: stores pc * (instruction) */ u_int64_t srr1; /* Machine status save/restore: stores msr * (ps, program/machine state) */ /* ppc_thread_state contains 32 fields, r0 .. r31. Here, an array is * used for brevity. */ u_int64_t gpr[MD_CONTEXT_PPC64_GPR_COUNT]; u_int64_t cr; /* Condition */ u_int64_t xer; /* Integer (fiXed-point) exception */ u_int64_t lr; /* Link */ u_int64_t ctr; /* Count */ u_int64_t vrsave; /* Vector save */ /* float_save and vector_save aren't present in ppc_thread_state, but * are represented in separate structures that still define a thread's * context. */ MDFloatingSaveAreaPPC float_save; MDVectorSaveAreaPPC vector_save; } MDRawContextPPC64; /* Based on ppc_thread_state */ /* For (MDRawContextPPC).context_flags. These values indicate the type of * context stored in the structure. MD_CONTEXT_PPC is Breakpad-defined. Its * value was chosen to avoid likely conflicts with MD_CONTEXT_* for other * CPUs. */ #define MD_CONTEXT_PPC 0x20000000 #define MD_CONTEXT_PPC_BASE (MD_CONTEXT_PPC | 0x00000001) #define MD_CONTEXT_PPC_FLOATING_POINT (MD_CONTEXT_PPC | 0x00000008) #define MD_CONTEXT_PPC_VECTOR (MD_CONTEXT_PPC | 0x00000020) #define MD_CONTEXT_PPC_FULL MD_CONTEXT_PPC_BASE #define MD_CONTEXT_PPC_ALL (MD_CONTEXT_PPC_FULL | \ MD_CONTEXT_PPC_FLOATING_POINT | \ MD_CONTEXT_PPC_VECTOR) #endif /* GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_PPC64_H__ */ minidump_cpu_sparc.h000066400000000000000000000155211271170121200341000ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/google_breakpad/common/* Copyright (c) 2006, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* minidump_format.h: A cross-platform reimplementation of minidump-related * portions of DbgHelp.h from the Windows Platform SDK. * * (This is C99 source, please don't corrupt it with C++.) * * This file contains the necessary definitions to read minidump files * produced on sparc. These files may be read on any platform provided * that the alignments of these structures on the processing system are * identical to the alignments of these structures on the producing system. * For this reason, precise-sized types are used. The structures defined * by this file have been laid out to minimize alignment problems by ensuring * ensuring that all members are aligned on their natural boundaries. In * In some cases, tail-padding may be significant when different ABIs specify * different tail-padding behaviors. To avoid problems when reading or * writing affected structures, MD_*_SIZE macros are provided where needed, * containing the useful size of the structures without padding. * * Structures that are defined by Microsoft to contain a zero-length array * are instead defined here to contain an array with one element, as * zero-length arrays are forbidden by standard C and C++. In these cases, * *_minsize constants are provided to be used in place of sizeof. For a * cleaner interface to these sizes when using C++, see minidump_size.h. * * These structures are also sufficient to populate minidump files. * * These definitions may be extended to support handling minidump files * for other CPUs and other operating systems. * * Because precise data type sizes are crucial for this implementation to * function properly and portably in terms of interoperability with minidumps * produced by DbgHelp on Windows, a set of primitive types with known sizes * are used as the basis of each structure defined by this file. DbgHelp * on Windows is assumed to be the reference implementation; this file * seeks to provide a cross-platform compatible implementation. To avoid * collisions with the types and values defined and used by DbgHelp in the * event that this implementation is used on Windows, each type and value * defined here is given a new name, beginning with "MD". Names of the * equivalent types and values in the Windows Platform SDK are given in * comments. * * Author: Mark Mentovai * Change to split into its own file: Neal Sidhwaney */ /* * SPARC support, see (solaris)sys/procfs_isa.h also */ #ifndef GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_SPARC_H__ #define GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_SPARC_H__ #define MD_FLOATINGSAVEAREA_SPARC_FPR_COUNT 32 typedef struct { /* FPU floating point regs */ u_int64_t regs[MD_FLOATINGSAVEAREA_SPARC_FPR_COUNT]; u_int64_t filler; u_int64_t fsr; /* FPU status register */ } MDFloatingSaveAreaSPARC; /* FLOATING_SAVE_AREA */ #define MD_CONTEXT_SPARC_GPR_COUNT 32 typedef struct { /* The next field determines the layout of the structure, and which parts * of it are populated */ u_int32_t context_flags; u_int32_t flag_pad; /* * General register access (SPARC). * Don't confuse definitions here with definitions in . * Registers are 32 bits for ILP32, 64 bits for LP64. * SPARC V7/V8 is for 32bit, SPARC V9 is for 64bit */ /* 32 Integer working registers */ /* g_r[0-7] global registers(g0-g7) * g_r[8-15] out registers(o0-o7) * g_r[16-23] local registers(l0-l7) * g_r[24-31] in registers(i0-i7) */ u_int64_t g_r[MD_CONTEXT_SPARC_GPR_COUNT]; /* several control registers */ /* Processor State register(PSR) for SPARC V7/V8 * Condition Code register (CCR) for SPARC V9 */ u_int64_t ccr; u_int64_t pc; /* Program Counter register (PC) */ u_int64_t npc; /* Next Program Counter register (nPC) */ u_int64_t y; /* Y register (Y) */ /* Address Space Identifier register (ASI) for SPARC V9 * WIM for SPARC V7/V8 */ u_int64_t asi; /* Floating-Point Registers State register (FPRS) for SPARC V9 * TBR for for SPARC V7/V8 */ u_int64_t fprs; /* The next field is included with MD_CONTEXT_SPARC_FLOATING_POINT */ MDFloatingSaveAreaSPARC float_save; } MDRawContextSPARC; /* CONTEXT_SPARC */ /* For (MDRawContextSPARC).context_flags. These values indicate the type of * context stored in the structure. MD_CONTEXT_SPARC is Breakpad-defined. Its * value was chosen to avoid likely conflicts with MD_CONTEXT_* for other * CPUs. */ #define MD_CONTEXT_SPARC 0x10000000 #define MD_CONTEXT_SPARC_CONTROL (MD_CONTEXT_SPARC | 0x00000001) #define MD_CONTEXT_SPARC_INTEGER (MD_CONTEXT_SPARC | 0x00000002) #define MD_CONTEXT_SAPARC_FLOATING_POINT (MD_CONTEXT_SPARC | 0x00000004) #define MD_CONTEXT_SAPARC_EXTRA (MD_CONTEXT_SPARC | 0x00000008) #define MD_CONTEXT_SPARC_FULL (MD_CONTEXT_SPARC_CONTROL | \ MD_CONTEXT_SPARC_INTEGER) #define MD_CONTEXT_SPARC_ALL (MD_CONTEXT_SPARC_FULL | \ MD_CONTEXT_SAPARC_FLOATING_POINT | \ MD_CONTEXT_SAPARC_EXTRA) #endif /* GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_SPARC_H__ */ scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/google_breakpad/common/minidump_cpu_x86.h000066400000000000000000000177661271170121200335110ustar00rootroot00000000000000/* Copyright (c) 2006, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* minidump_format.h: A cross-platform reimplementation of minidump-related * portions of DbgHelp.h from the Windows Platform SDK. * * (This is C99 source, please don't corrupt it with C++.) * * This file contains the necessary definitions to read minidump files * produced on x86. These files may be read on any platform provided * that the alignments of these structures on the processing system are * identical to the alignments of these structures on the producing system. * For this reason, precise-sized types are used. The structures defined * by this file have been laid out to minimize alignment problems by ensuring * ensuring that all members are aligned on their natural boundaries. In * In some cases, tail-padding may be significant when different ABIs specify * different tail-padding behaviors. To avoid problems when reading or * writing affected structures, MD_*_SIZE macros are provided where needed, * containing the useful size of the structures without padding. * * Structures that are defined by Microsoft to contain a zero-length array * are instead defined here to contain an array with one element, as * zero-length arrays are forbidden by standard C and C++. In these cases, * *_minsize constants are provided to be used in place of sizeof. For a * cleaner interface to these sizes when using C++, see minidump_size.h. * * These structures are also sufficient to populate minidump files. * * These definitions may be extended to support handling minidump files * for other CPUs and other operating systems. * * Because precise data type sizes are crucial for this implementation to * function properly and portably in terms of interoperability with minidumps * produced by DbgHelp on Windows, a set of primitive types with known sizes * are used as the basis of each structure defined by this file. DbgHelp * on Windows is assumed to be the reference implementation; this file * seeks to provide a cross-platform compatible implementation. To avoid * collisions with the types and values defined and used by DbgHelp in the * event that this implementation is used on Windows, each type and value * defined here is given a new name, beginning with "MD". Names of the * equivalent types and values in the Windows Platform SDK are given in * comments. * * Author: Mark Mentovai */ #ifndef GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_X86_H__ #define GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_X86_H__ #define MD_FLOATINGSAVEAREA_X86_REGISTERAREA_SIZE 80 /* SIZE_OF_80387_REGISTERS */ typedef struct { u_int32_t control_word; u_int32_t status_word; u_int32_t tag_word; u_int32_t error_offset; u_int32_t error_selector; u_int32_t data_offset; u_int32_t data_selector; /* register_area contains eight 80-bit (x87 "long double") quantities for * floating-point registers %st0 (%mm0) through %st7 (%mm7). */ u_int8_t register_area[MD_FLOATINGSAVEAREA_X86_REGISTERAREA_SIZE]; u_int32_t cr0_npx_state; } MDFloatingSaveAreaX86; /* FLOATING_SAVE_AREA */ #define MD_CONTEXT_X86_EXTENDED_REGISTERS_SIZE 512 /* MAXIMUM_SUPPORTED_EXTENSION */ typedef struct { /* The next field determines the layout of the structure, and which parts * of it are populated */ u_int32_t context_flags; /* The next 6 registers are included with MD_CONTEXT_X86_DEBUG_REGISTERS */ u_int32_t dr0; u_int32_t dr1; u_int32_t dr2; u_int32_t dr3; u_int32_t dr6; u_int32_t dr7; /* The next field is included with MD_CONTEXT_X86_FLOATING_POINT */ MDFloatingSaveAreaX86 float_save; /* The next 4 registers are included with MD_CONTEXT_X86_SEGMENTS */ u_int32_t gs; u_int32_t fs; u_int32_t es; u_int32_t ds; /* The next 6 registers are included with MD_CONTEXT_X86_INTEGER */ u_int32_t edi; u_int32_t esi; u_int32_t ebx; u_int32_t edx; u_int32_t ecx; u_int32_t eax; /* The next 6 registers are included with MD_CONTEXT_X86_CONTROL */ u_int32_t ebp; u_int32_t eip; u_int32_t cs; /* WinNT.h says "must be sanitized" */ u_int32_t eflags; /* WinNT.h says "must be sanitized" */ u_int32_t esp; u_int32_t ss; /* The next field is included with MD_CONTEXT_X86_EXTENDED_REGISTERS. * It contains vector (MMX/SSE) registers. It it laid out in the * format used by the fxsave and fsrstor instructions, so it includes * a copy of the x87 floating-point registers as well. See FXSAVE in * "Intel Architecture Software Developer's Manual, Volume 2." */ u_int8_t extended_registers[ MD_CONTEXT_X86_EXTENDED_REGISTERS_SIZE]; } MDRawContextX86; /* CONTEXT */ /* For (MDRawContextX86).context_flags. These values indicate the type of * context stored in the structure. The high 26 bits identify the CPU, the * low 6 bits identify the type of context saved. */ #define MD_CONTEXT_X86 0x00010000 /* CONTEXT_i386, CONTEXT_i486: identifies CPU */ #define MD_CONTEXT_X86_CONTROL (MD_CONTEXT_X86 | 0x00000001) /* CONTEXT_CONTROL */ #define MD_CONTEXT_X86_INTEGER (MD_CONTEXT_X86 | 0x00000002) /* CONTEXT_INTEGER */ #define MD_CONTEXT_X86_SEGMENTS (MD_CONTEXT_X86 | 0x00000004) /* CONTEXT_SEGMENTS */ #define MD_CONTEXT_X86_FLOATING_POINT (MD_CONTEXT_X86 | 0x00000008) /* CONTEXT_FLOATING_POINT */ #define MD_CONTEXT_X86_DEBUG_REGISTERS (MD_CONTEXT_X86 | 0x00000010) /* CONTEXT_DEBUG_REGISTERS */ #define MD_CONTEXT_X86_EXTENDED_REGISTERS (MD_CONTEXT_X86 | 0x00000020) /* CONTEXT_EXTENDED_REGISTERS */ #define MD_CONTEXT_X86_FULL (MD_CONTEXT_X86_CONTROL | \ MD_CONTEXT_X86_INTEGER | \ MD_CONTEXT_X86_SEGMENTS) /* CONTEXT_FULL */ #define MD_CONTEXT_X86_ALL (MD_CONTEXT_X86_FULL | \ MD_CONTEXT_X86_FLOATING_POINT | \ MD_CONTEXT_X86_DEBUG_REGISTERS | \ MD_CONTEXT_X86_EXTENDED_REGISTERS) /* CONTEXT_ALL */ #endif /* GOOGLE_BREAKPAD_COMMON_MINIDUMP_CPU_X86_H__ */ minidump_exception_linux.h000066400000000000000000000106051271170121200353340ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/google_breakpad/common/* Copyright (c) 2006, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* minidump_exception_linux.h: A definition of exception codes for * Linux * * (This is C99 source, please don't corrupt it with C++.) * * Author: Mark Mentovai * Split into its own file: Neal Sidhwaney */ #ifndef GOOGLE_BREAKPAD_COMMON_MINIDUMP_EXCEPTION_LINUX_H__ #define GOOGLE_BREAKPAD_COMMON_MINIDUMP_EXCEPTION_LINUX_H__ #include #include "google_breakpad/common/breakpad_types.h" /* For (MDException).exception_code. These values come from bits/signum.h. */ typedef enum { MD_EXCEPTION_CODE_LIN_SIGHUP = 1, /* Hangup (POSIX) */ MD_EXCEPTION_CODE_LIN_SIGINT = 2, /* Interrupt (ANSI) */ MD_EXCEPTION_CODE_LIN_SIGQUIT = 3, /* Quit (POSIX) */ MD_EXCEPTION_CODE_LIN_SIGILL = 4, /* Illegal instruction (ANSI) */ MD_EXCEPTION_CODE_LIN_SIGTRAP = 5, /* Trace trap (POSIX) */ MD_EXCEPTION_CODE_LIN_SIGABRT = 6, /* Abort (ANSI) */ MD_EXCEPTION_CODE_LIN_SIGBUS = 7, /* BUS error (4.2 BSD) */ MD_EXCEPTION_CODE_LIN_SIGFPE = 8, /* Floating-point exception (ANSI) */ MD_EXCEPTION_CODE_LIN_SIGKILL = 9, /* Kill, unblockable (POSIX) */ MD_EXCEPTION_CODE_LIN_SIGUSR1 = 10, /* User-defined signal 1 (POSIX). */ MD_EXCEPTION_CODE_LIN_SIGSEGV = 11, /* Segmentation violation (ANSI) */ MD_EXCEPTION_CODE_LIN_SIGUSR2 = 12, /* User-defined signal 2 (POSIX) */ MD_EXCEPTION_CODE_LIN_SIGPIPE = 13, /* Broken pipe (POSIX) */ MD_EXCEPTION_CODE_LIN_SIGALRM = 14, /* Alarm clock (POSIX) */ MD_EXCEPTION_CODE_LIN_SIGTERM = 15, /* Termination (ANSI) */ MD_EXCEPTION_CODE_LIN_SIGSTKFLT = 16, /* Stack faultd */ MD_EXCEPTION_CODE_LIN_SIGCHLD = 17, /* Child status has changed (POSIX) */ MD_EXCEPTION_CODE_LIN_SIGCONT = 18, /* Continue (POSIX) */ MD_EXCEPTION_CODE_LIN_SIGSTOP = 19, /* Stop, unblockable (POSIX) */ MD_EXCEPTION_CODE_LIN_SIGTSTP = 20, /* Keyboard stop (POSIX) */ MD_EXCEPTION_CODE_LIN_SIGTTIN = 21, /* Background read from tty (POSIX) */ MD_EXCEPTION_CODE_LIN_SIGTTOU = 22, /* Background write to tty (POSIX) */ MD_EXCEPTION_CODE_LIN_SIGURG = 23, /* Urgent condition on socket (4.2 BSD) */ MD_EXCEPTION_CODE_LIN_SIGXCPU = 24, /* CPU limit exceeded (4.2 BSD) */ MD_EXCEPTION_CODE_LIN_SIGXFSZ = 25, /* File size limit exceeded (4.2 BSD) */ MD_EXCEPTION_CODE_LIN_SIGVTALRM = 26, /* Virtual alarm clock (4.2 BSD) */ MD_EXCEPTION_CODE_LIN_SIGPROF = 27, /* Profiling alarm clock (4.2 BSD) */ MD_EXCEPTION_CODE_LIN_SIGWINCH = 28, /* Window size change (4.3 BSD, Sun) */ MD_EXCEPTION_CODE_LIN_SIGIO = 29, /* I/O now possible (4.2 BSD) */ MD_EXCEPTION_CODE_LIN_SIGPWR = 30, /* Power failure restart (System V) */ MD_EXCEPTION_CODE_LIN_SIGSYS = 31 /* Bad system call */ } MDExceptionCodeLinux; #endif /* GOOGLE_BREAKPAD_COMMON_MINIDUMP_EXCEPTION_LINUX_H__ */ minidump_exception_mac.h000066400000000000000000000214271271170121200347410ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/google_breakpad/common/* Copyright (c) 2006, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* minidump_exception_mac.h: A definition of exception codes for Mac * OS X * * (This is C99 source, please don't corrupt it with C++.) * * Author: Mark Mentovai * Split into its own file: Neal Sidhwaney */ #ifndef GOOGLE_BREAKPAD_COMMON_MINIDUMP_EXCEPTION_MAC_H__ #define GOOGLE_BREAKPAD_COMMON_MINIDUMP_EXCEPTION_MAC_H__ #include #include "google_breakpad/common/breakpad_types.h" /* For (MDException).exception_code. Breakpad minidump extension for Mac OS X * support. Based on Darwin/Mac OS X' mach/exception_types.h. This is * what Mac OS X calls an "exception", not a "code". */ typedef enum { /* Exception code. The high 16 bits of exception_code contains one of * these values. */ MD_EXCEPTION_MAC_BAD_ACCESS = 1, /* code can be a kern_return_t */ /* EXC_BAD_ACCESS */ MD_EXCEPTION_MAC_BAD_INSTRUCTION = 2, /* code is CPU-specific */ /* EXC_BAD_INSTRUCTION */ MD_EXCEPTION_MAC_ARITHMETIC = 3, /* code is CPU-specific */ /* EXC_ARITHMETIC */ MD_EXCEPTION_MAC_EMULATION = 4, /* code is CPU-specific */ /* EXC_EMULATION */ MD_EXCEPTION_MAC_SOFTWARE = 5, /* EXC_SOFTWARE */ MD_EXCEPTION_MAC_BREAKPOINT = 6, /* code is CPU-specific */ /* EXC_BREAKPOINT */ MD_EXCEPTION_MAC_SYSCALL = 7, /* EXC_SYSCALL */ MD_EXCEPTION_MAC_MACH_SYSCALL = 8, /* EXC_MACH_SYSCALL */ MD_EXCEPTION_MAC_RPC_ALERT = 9 /* EXC_RPC_ALERT */ } MDExceptionMac; /* For (MDException).exception_flags. Breakpad minidump extension for Mac OS X * support. Based on Darwin/Mac OS X' mach/ppc/exception.h and * mach/i386/exception.h. This is what Mac OS X calls a "code". */ typedef enum { /* With MD_EXCEPTION_BAD_ACCESS. These are relevant kern_return_t values * from mach/kern_return.h. */ MD_EXCEPTION_CODE_MAC_INVALID_ADDRESS = 1, /* KERN_INVALID_ADDRESS */ MD_EXCEPTION_CODE_MAC_PROTECTION_FAILURE = 2, /* KERN_PROTECTION_FAILURE */ MD_EXCEPTION_CODE_MAC_NO_ACCESS = 8, /* KERN_NO_ACCESS */ MD_EXCEPTION_CODE_MAC_MEMORY_FAILURE = 9, /* KERN_MEMORY_FAILURE */ MD_EXCEPTION_CODE_MAC_MEMORY_ERROR = 10, /* KERN_MEMORY_ERROR */ /* With MD_EXCEPTION_SOFTWARE */ MD_EXCEPTION_CODE_MAC_BAD_SYSCALL = 0x00010000, /* Mach SIGSYS */ MD_EXCEPTION_CODE_MAC_BAD_PIPE = 0x00010001, /* Mach SIGPIPE */ MD_EXCEPTION_CODE_MAC_ABORT = 0x00010002, /* Mach SIGABRT */ /* With MD_EXCEPTION_MAC_BAD_ACCESS on ppc */ MD_EXCEPTION_CODE_MAC_PPC_VM_PROT_READ = 0x0101, /* EXC_PPC_VM_PROT_READ */ MD_EXCEPTION_CODE_MAC_PPC_BADSPACE = 0x0102, /* EXC_PPC_BADSPACE */ MD_EXCEPTION_CODE_MAC_PPC_UNALIGNED = 0x0103, /* EXC_PPC_UNALIGNED */ /* With MD_EXCEPTION_MAC_BAD_INSTRUCTION on ppc */ MD_EXCEPTION_CODE_MAC_PPC_INVALID_SYSCALL = 1, /* EXC_PPC_INVALID_SYSCALL */ MD_EXCEPTION_CODE_MAC_PPC_UNIMPLEMENTED_INSTRUCTION = 2, /* EXC_PPC_UNIPL_INST */ MD_EXCEPTION_CODE_MAC_PPC_PRIVILEGED_INSTRUCTION = 3, /* EXC_PPC_PRIVINST */ MD_EXCEPTION_CODE_MAC_PPC_PRIVILEGED_REGISTER = 4, /* EXC_PPC_PRIVREG */ MD_EXCEPTION_CODE_MAC_PPC_TRACE = 5, /* EXC_PPC_TRACE */ MD_EXCEPTION_CODE_MAC_PPC_PERFORMANCE_MONITOR = 6, /* EXC_PPC_PERFMON */ /* With MD_EXCEPTION_MAC_ARITHMETIC on ppc */ MD_EXCEPTION_CODE_MAC_PPC_OVERFLOW = 1, /* EXC_PPC_OVERFLOW */ MD_EXCEPTION_CODE_MAC_PPC_ZERO_DIVIDE = 2, /* EXC_PPC_ZERO_DIVIDE */ MD_EXCEPTION_CODE_MAC_PPC_FLOAT_INEXACT = 3, /* EXC_FLT_INEXACT */ MD_EXCEPTION_CODE_MAC_PPC_FLOAT_ZERO_DIVIDE = 4, /* EXC_PPC_FLT_ZERO_DIVIDE */ MD_EXCEPTION_CODE_MAC_PPC_FLOAT_UNDERFLOW = 5, /* EXC_PPC_FLT_UNDERFLOW */ MD_EXCEPTION_CODE_MAC_PPC_FLOAT_OVERFLOW = 6, /* EXC_PPC_FLT_OVERFLOW */ MD_EXCEPTION_CODE_MAC_PPC_FLOAT_NOT_A_NUMBER = 7, /* EXC_PPC_FLT_NOT_A_NUMBER */ /* With MD_EXCEPTION_MAC_EMULATION on ppc */ MD_EXCEPTION_CODE_MAC_PPC_NO_EMULATION = 8, /* EXC_PPC_NOEMULATION */ MD_EXCEPTION_CODE_MAC_PPC_ALTIVEC_ASSIST = 9, /* EXC_PPC_ALTIVECASSIST */ /* With MD_EXCEPTION_MAC_SOFTWARE on ppc */ MD_EXCEPTION_CODE_MAC_PPC_TRAP = 0x00000001, /* EXC_PPC_TRAP */ MD_EXCEPTION_CODE_MAC_PPC_MIGRATE = 0x00010100, /* EXC_PPC_MIGRATE */ /* With MD_EXCEPTION_MAC_BREAKPOINT on ppc */ MD_EXCEPTION_CODE_MAC_PPC_BREAKPOINT = 1, /* EXC_PPC_BREAKPOINT */ /* With MD_EXCEPTION_MAC_BAD_INSTRUCTION on x86, see also x86 interrupt * values below. */ MD_EXCEPTION_CODE_MAC_X86_INVALID_OPERATION = 1, /* EXC_I386_INVOP */ /* With MD_EXCEPTION_MAC_ARITHMETIC on x86 */ MD_EXCEPTION_CODE_MAC_X86_DIV = 1, /* EXC_I386_DIV */ MD_EXCEPTION_CODE_MAC_X86_INTO = 2, /* EXC_I386_INTO */ MD_EXCEPTION_CODE_MAC_X86_NOEXT = 3, /* EXC_I386_NOEXT */ MD_EXCEPTION_CODE_MAC_X86_EXTOVR = 4, /* EXC_I386_EXTOVR */ MD_EXCEPTION_CODE_MAC_X86_EXTERR = 5, /* EXC_I386_EXTERR */ MD_EXCEPTION_CODE_MAC_X86_EMERR = 6, /* EXC_I386_EMERR */ MD_EXCEPTION_CODE_MAC_X86_BOUND = 7, /* EXC_I386_BOUND */ MD_EXCEPTION_CODE_MAC_X86_SSEEXTERR = 8, /* EXC_I386_SSEEXTERR */ /* With MD_EXCEPTION_MAC_BREAKPOINT on x86 */ MD_EXCEPTION_CODE_MAC_X86_SGL = 1, /* EXC_I386_SGL */ MD_EXCEPTION_CODE_MAC_X86_BPT = 2, /* EXC_I386_BPT */ /* With MD_EXCEPTION_MAC_BAD_INSTRUCTION on x86. These are the raw * x86 interrupt codes. Most of these are mapped to other Mach * exceptions and codes, are handled, or should not occur in user space. * A few of these will do occur with MD_EXCEPTION_MAC_BAD_INSTRUCTION. */ /* EXC_I386_DIVERR = 0: mapped to EXC_ARITHMETIC/EXC_I386_DIV */ /* EXC_I386_SGLSTP = 1: mapped to EXC_BREAKPOINT/EXC_I386_SGL */ /* EXC_I386_NMIFLT = 2: should not occur in user space */ /* EXC_I386_BPTFLT = 3: mapped to EXC_BREAKPOINT/EXC_I386_BPT */ /* EXC_I386_INTOFLT = 4: mapped to EXC_ARITHMETIC/EXC_I386_INTO */ /* EXC_I386_BOUNDFLT = 5: mapped to EXC_ARITHMETIC/EXC_I386_BOUND */ /* EXC_I386_INVOPFLT = 6: mapped to EXC_BAD_INSTRUCTION/EXC_I386_INVOP */ /* EXC_I386_NOEXTFLT = 7: should be handled by the kernel */ /* EXC_I386_DBLFLT = 8: should be handled (if possible) by the kernel */ /* EXC_I386_EXTOVRFLT = 9: mapped to EXC_BAD_ACCESS/(PROT_READ|PROT_EXEC) */ MD_EXCEPTION_CODE_MAC_X86_INVALID_TASK_STATE_SEGMENT = 10, /* EXC_INVTSSFLT */ MD_EXCEPTION_CODE_MAC_X86_SEGMENT_NOT_PRESENT = 11, /* EXC_SEGNPFLT */ MD_EXCEPTION_CODE_MAC_X86_STACK_FAULT = 12, /* EXC_STKFLT */ MD_EXCEPTION_CODE_MAC_X86_GENERAL_PROTECTION_FAULT = 13, /* EXC_GPFLT */ /* EXC_I386_PGFLT = 14: should not occur in user space */ /* EXC_I386_EXTERRFLT = 16: mapped to EXC_ARITHMETIC/EXC_I386_EXTERR */ MD_EXCEPTION_CODE_MAC_X86_ALIGNMENT_FAULT = 17 /* EXC_ALIGNFLT (for vector operations) */ /* EXC_I386_ENOEXTFLT = 32: should be handled by the kernel */ /* EXC_I386_ENDPERR = 33: should not occur */ } MDExceptionCodeMac; #endif /* GOOGLE_BREAKPAD_COMMON_MINIDUMP_EXCEPTION_MAC_OSX_H__ */ minidump_exception_solaris.h000066400000000000000000000126011271170121200356470ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/google_breakpad/common/* Copyright (c) 2006, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* minidump_exception_solaris.h: A definition of exception codes for * Solaris * * (This is C99 source, please don't corrupt it with C++.) * * Author: Mark Mentovai * Split into its own file: Neal Sidhwaney */ #ifndef GOOGLE_BREAKPAD_COMMON_MINIDUMP_EXCEPTION_SOLARIS_H__ #define GOOGLE_BREAKPAD_COMMON_MINIDUMP_EXCEPTION_SOLARIS_H__ #include #include "google_breakpad/common/breakpad_types.h" /* For (MDException).exception_code. These values come from sys/iso/signal_iso.h */ typedef enum { MD_EXCEPTION_CODE_SOL_SIGHUP = 1, /* Hangup */ MD_EXCEPTION_CODE_SOL_SIGINT = 2, /* interrupt (rubout) */ MD_EXCEPTION_CODE_SOL_SIGQUIT = 3, /* quit (ASCII FS) */ MD_EXCEPTION_CODE_SOL_SIGILL = 4, /* illegal instruction (not reset when caught) */ MD_EXCEPTION_CODE_SOL_SIGTRAP = 5, /* trace trap (not reset when caught) */ MD_EXCEPTION_CODE_SOL_SIGIOT = 6, /* IOT instruction */ MD_EXCEPTION_CODE_SOL_SIGABRT = 6, /* used by abort, replace SIGIOT in the future */ MD_EXCEPTION_CODE_SOL_SIGEMT = 7, /* EMT instruction */ MD_EXCEPTION_CODE_SOL_SIGFPE = 8, /* floating point exception */ MD_EXCEPTION_CODE_SOL_SIGKILL = 9, /* kill (cannot be caught or ignored) */ MD_EXCEPTION_CODE_SOL_SIGBUS = 10, /* bus error */ MD_EXCEPTION_CODE_SOL_SIGSEGV = 11, /* segmentation violation */ MD_EXCEPTION_CODE_SOL_SIGSYS = 12, /* bad argument to system call */ MD_EXCEPTION_CODE_SOL_SIGPIPE = 13, /* write on a pipe with no one to read it */ MD_EXCEPTION_CODE_SOL_SIGALRM = 14, /* alarm clock */ MD_EXCEPTION_CODE_SOL_SIGTERM = 15, /* software termination signal from kill */ MD_EXCEPTION_CODE_SOL_SIGUSR1 = 16, /* user defined signal 1 */ MD_EXCEPTION_CODE_SOL_SIGUSR2 = 17, /* user defined signal 2 */ MD_EXCEPTION_CODE_SOL_SIGCLD = 18, /* child status change */ MD_EXCEPTION_CODE_SOL_SIGCHLD = 18, /* child status change alias (POSIX) */ MD_EXCEPTION_CODE_SOL_SIGPWR = 19, /* power-fail restart */ MD_EXCEPTION_CODE_SOL_SIGWINCH = 20, /* window size change */ MD_EXCEPTION_CODE_SOL_SIGURG = 21, /* urgent socket condition */ MD_EXCEPTION_CODE_SOL_SIGPOLL = 22, /* pollable event occured */ MD_EXCEPTION_CODE_SOL_SIGIO = 22, /* socket I/O possible (SIGPOLL alias) */ MD_EXCEPTION_CODE_SOL_SIGSTOP = 23, /* stop (cannot be caught or ignored) */ MD_EXCEPTION_CODE_SOL_SIGTSTP = 24, /* user stop requested from tty */ MD_EXCEPTION_CODE_SOL_SIGCONT = 25, /* stopped process has been continued */ MD_EXCEPTION_CODE_SOL_SIGTTIN = 26, /* background tty read attempted */ MD_EXCEPTION_CODE_SOL_SIGTTOU = 27, /* background tty write attempted */ MD_EXCEPTION_CODE_SOL_SIGVTALRM = 28, /* virtual timer expired */ MD_EXCEPTION_CODE_SOL_SIGPROF = 29, /* profiling timer expired */ MD_EXCEPTION_CODE_SOL_SIGXCPU = 30, /* exceeded cpu limit */ MD_EXCEPTION_CODE_SOL_SIGXFSZ = 31, /* exceeded file size limit */ MD_EXCEPTION_CODE_SOL_SIGWAITING = 32, /* reserved signal no longer used by threading code */ MD_EXCEPTION_CODE_SOL_SIGLWP = 33, /* reserved signal no longer used by threading code */ MD_EXCEPTION_CODE_SOL_SIGFREEZE = 34, /* special signal used by CPR */ MD_EXCEPTION_CODE_SOL_SIGTHAW = 35, /* special signal used by CPR */ MD_EXCEPTION_CODE_SOL_SIGCANCEL = 36, /* reserved signal for thread cancellation */ MD_EXCEPTION_CODE_SOL_SIGLOST = 37, /* resource lost (eg, record-lock lost) */ MD_EXCEPTION_CODE_SOL_SIGXRES = 38, /* resource control exceeded */ MD_EXCEPTION_CODE_SOL_SIGJVM1 = 39, /* reserved signal for Java Virtual Machine */ MD_EXCEPTION_CODE_SOL_SIGJVM2 = 40 /* reserved signal for Java Virtual Machine */ } MDExceptionCodeSolaris; #endif /* GOOGLE_BREAKPAD_COMMON_MINIDUMP_EXCEPTION_SOLARIS_H__ */ minidump_exception_win32.h000066400000000000000000000112721271170121200351400ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/google_breakpad/common/* Copyright (c) 2006, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* minidump_exception_win32.h: Definitions of exception codes for * Win32 platform * * (This is C99 source, please don't corrupt it with C++.) * * Author: Mark Mentovai * Split into its own file: Neal Sidhwaney */ #ifndef GOOGLE_BREAKPAD_COMMON_MINIDUMP_EXCEPTION_WIN32_H__ #define GOOGLE_BREAKPAD_COMMON_MINIDUMP_EXCEPTION_WIN32_H__ #include #include "google_breakpad/common/breakpad_types.h" /* For (MDException).exception_code. These values come from WinBase.h * and WinNT.h (names beginning with EXCEPTION_ are in WinBase.h, * they are STATUS_ in WinNT.h). */ typedef enum { MD_EXCEPTION_CODE_WIN_CONTROL_C = 0x40010005, /* DBG_CONTROL_C */ MD_EXCEPTION_CODE_WIN_GUARD_PAGE_VIOLATION = 0x80000001, /* EXCEPTION_GUARD_PAGE */ MD_EXCEPTION_CODE_WIN_DATATYPE_MISALIGNMENT = 0x80000002, /* EXCEPTION_DATATYPE_MISALIGNMENT */ MD_EXCEPTION_CODE_WIN_BREAKPOINT = 0x80000003, /* EXCEPTION_BREAKPOINT */ MD_EXCEPTION_CODE_WIN_SINGLE_STEP = 0x80000004, /* EXCEPTION_SINGLE_STEP */ MD_EXCEPTION_CODE_WIN_ACCESS_VIOLATION = 0xc0000005, /* EXCEPTION_ACCESS_VIOLATION */ MD_EXCEPTION_CODE_WIN_IN_PAGE_ERROR = 0xc0000006, /* EXCEPTION_IN_PAGE_ERROR */ MD_EXCEPTION_CODE_WIN_INVALID_HANDLE = 0xc0000008, /* EXCEPTION_INVALID_HANDLE */ MD_EXCEPTION_CODE_WIN_ILLEGAL_INSTRUCTION = 0xc000001d, /* EXCEPTION_ILLEGAL_INSTRUCTION */ MD_EXCEPTION_CODE_WIN_NONCONTINUABLE_EXCEPTION = 0xc0000025, /* EXCEPTION_NONCONTINUABLE_EXCEPTION */ MD_EXCEPTION_CODE_WIN_INVALID_DISPOSITION = 0xc0000026, /* EXCEPTION_INVALID_DISPOSITION */ MD_EXCEPTION_CODE_WIN_ARRAY_BOUNDS_EXCEEDED = 0xc000008c, /* EXCEPTION_BOUNDS_EXCEEDED */ MD_EXCEPTION_CODE_WIN_FLOAT_DENORMAL_OPERAND = 0xc000008d, /* EXCEPTION_FLT_DENORMAL_OPERAND */ MD_EXCEPTION_CODE_WIN_FLOAT_DIVIDE_BY_ZERO = 0xc000008e, /* EXCEPTION_FLT_DIVIDE_BY_ZERO */ MD_EXCEPTION_CODE_WIN_FLOAT_INEXACT_RESULT = 0xc000008f, /* EXCEPTION_FLT_INEXACT_RESULT */ MD_EXCEPTION_CODE_WIN_FLOAT_INVALID_OPERATION = 0xc0000090, /* EXCEPTION_FLT_INVALID_OPERATION */ MD_EXCEPTION_CODE_WIN_FLOAT_OVERFLOW = 0xc0000091, /* EXCEPTION_FLT_OVERFLOW */ MD_EXCEPTION_CODE_WIN_FLOAT_STACK_CHECK = 0xc0000092, /* EXCEPTION_FLT_STACK_CHECK */ MD_EXCEPTION_CODE_WIN_FLOAT_UNDERFLOW = 0xc0000093, /* EXCEPTION_FLT_UNDERFLOW */ MD_EXCEPTION_CODE_WIN_INTEGER_DIVIDE_BY_ZERO = 0xc0000094, /* EXCEPTION_INT_DIVIDE_BY_ZERO */ MD_EXCEPTION_CODE_WIN_INTEGER_OVERFLOW = 0xc0000095, /* EXCEPTION_INT_OVERFLOW */ MD_EXCEPTION_CODE_WIN_PRIVILEGED_INSTRUCTION = 0xc0000096, /* EXCEPTION_PRIV_INSTRUCTION */ MD_EXCEPTION_CODE_WIN_STACK_OVERFLOW = 0xc00000fd, /* EXCEPTION_STACK_OVERFLOW */ MD_EXCEPTION_CODE_WIN_POSSIBLE_DEADLOCK = 0xc0000194 /* EXCEPTION_POSSIBLE_DEADLOCK */ } MDExceptionCodeWin; #endif /* GOOGLE_BREAKPAD_COMMON_MINIDUMP_EXCEPTION_WIN32_H__ */ scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/google_breakpad/common/minidump_format.h000066400000000000000000000741001271170121200334660ustar00rootroot00000000000000/* Copyright (c) 2006, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* minidump_format.h: A cross-platform reimplementation of minidump-related * portions of DbgHelp.h from the Windows Platform SDK. * * (This is C99 source, please don't corrupt it with C++.) * * Structures that are defined by Microsoft to contain a zero-length array * are instead defined here to contain an array with one element, as * zero-length arrays are forbidden by standard C and C++. In these cases, * *_minsize constants are provided to be used in place of sizeof. For a * cleaner interface to these sizes when using C++, see minidump_size.h. * * These structures are also sufficient to populate minidump files. * * These definitions may be extended to support handling minidump files * for other CPUs and other operating systems. * * Because precise data type sizes are crucial for this implementation to * function properly and portably in terms of interoperability with minidumps * produced by DbgHelp on Windows, a set of primitive types with known sizes * are used as the basis of each structure defined by this file. DbgHelp * on Windows is assumed to be the reference implementation; this file * seeks to provide a cross-platform compatible implementation. To avoid * collisions with the types and values defined and used by DbgHelp in the * event that this implementation is used on Windows, each type and value * defined here is given a new name, beginning with "MD". Names of the * equivalent types and values in the Windows Platform SDK are given in * comments. * * Author: Mark Mentovai */ #ifndef GOOGLE_BREAKPAD_COMMON_MINIDUMP_FORMAT_H__ #define GOOGLE_BREAKPAD_COMMON_MINIDUMP_FORMAT_H__ #include #include "google_breakpad/common/breakpad_types.h" #if defined(_MSC_VER) /* Disable "zero-sized array in struct/union" warnings when compiling in * MSVC. DbgHelp.h does this too. */ #pragma warning(push) #pragma warning(disable:4200) #endif /* _MSC_VER */ /* * guiddef.h */ typedef struct { u_int32_t data1; u_int16_t data2; u_int16_t data3; u_int8_t data4[8]; } MDGUID; /* GUID */ /* * WinNT.h */ /* Non-x86 CPU identifiers found in the high 26 bits of * (MDRawContext*).context_flags. These aren't used by Breakpad, but are * defined here for reference, to avoid assigning values that conflict * (although some values already conflict). */ #define MD_CONTEXT_IA64 0x00080000 /* CONTEXT_IA64 */ #define MD_CONTEXT_AMD64 0x00100000 /* CONTEXT_AMD64 */ /* Additional values from winnt.h in the Windows CE 5.0 SDK: */ #define MD_CONTEXT_SHX 0x000000c0 /* CONTEXT_SH4 (Super-H, includes SH3) */ #define MD_CONTEXT_ARM 0x00000040 /* CONTEXT_ARM (0x40 bit set in SHx?) */ #define MD_CONTEXT_MIPS 0x00010000 /* CONTEXT_R4000 (same value as x86?) */ #define MD_CONTEXT_ALPHA 0x00020000 /* CONTEXT_ALPHA */ #define MD_CONTEXT_CPU_MASK 0xffffffc0 /* This is a base type for MDRawContextX86 and MDRawContextPPC. This * structure should never be allocated directly. The actual structure type * can be determined by examining the context_flags field. */ typedef struct { u_int32_t context_flags; } MDRawContextBase; #include "minidump_cpu_sparc.h" #include "minidump_cpu_x86.h" #include "minidump_cpu_ppc.h" #include "minidump_cpu_ppc64.h" #include "minidump_cpu_amd64.h" /* * WinVer.h */ typedef struct { u_int32_t signature; u_int32_t struct_version; u_int32_t file_version_hi; u_int32_t file_version_lo; u_int32_t product_version_hi; u_int32_t product_version_lo; u_int32_t file_flags_mask; /* Identifies valid bits in fileFlags */ u_int32_t file_flags; u_int32_t file_os; u_int32_t file_type; u_int32_t file_subtype; u_int32_t file_date_hi; u_int32_t file_date_lo; } MDVSFixedFileInfo; /* VS_FIXEDFILEINFO */ /* For (MDVSFixedFileInfo).signature */ #define MD_VSFIXEDFILEINFO_SIGNATURE 0xfeef04bd /* VS_FFI_SIGNATURE */ /* For (MDVSFixedFileInfo).version */ #define MD_VSFIXEDFILEINFO_VERSION 0x00010000 /* VS_FFI_STRUCVERSION */ /* For (MDVSFixedFileInfo).file_flags_mask and * (MDVSFixedFileInfo).file_flags */ #define MD_VSFIXEDFILEINFO_FILE_FLAGS_DEBUG 0x00000001 /* VS_FF_DEBUG */ #define MD_VSFIXEDFILEINFO_FILE_FLAGS_PRERELEASE 0x00000002 /* VS_FF_PRERELEASE */ #define MD_VSFIXEDFILEINFO_FILE_FLAGS_PATCHED 0x00000004 /* VS_FF_PATCHED */ #define MD_VSFIXEDFILEINFO_FILE_FLAGS_PRIVATEBUILD 0x00000008 /* VS_FF_PRIVATEBUILD */ #define MD_VSFIXEDFILEINFO_FILE_FLAGS_INFOINFERRED 0x00000010 /* VS_FF_INFOINFERRED */ #define MD_VSFIXEDFILEINFO_FILE_FLAGS_SPECIALBUILD 0x00000020 /* VS_FF_SPECIALBUILD */ /* For (MDVSFixedFileInfo).file_os: high 16 bits */ #define MD_VSFIXEDFILEINFO_FILE_OS_UNKNOWN 0 /* VOS_UNKNOWN */ #define MD_VSFIXEDFILEINFO_FILE_OS_DOS (1 << 16) /* VOS_DOS */ #define MD_VSFIXEDFILEINFO_FILE_OS_OS216 (2 << 16) /* VOS_OS216 */ #define MD_VSFIXEDFILEINFO_FILE_OS_OS232 (3 << 16) /* VOS_OS232 */ #define MD_VSFIXEDFILEINFO_FILE_OS_NT (4 << 16) /* VOS_NT */ #define MD_VSFIXEDFILEINFO_FILE_OS_WINCE (5 << 16) /* VOS_WINCE */ /* Low 16 bits */ #define MD_VSFIXEDFILEINFO_FILE_OS__BASE 0 /* VOS__BASE */ #define MD_VSFIXEDFILEINFO_FILE_OS__WINDOWS16 1 /* VOS__WINDOWS16 */ #define MD_VSFIXEDFILEINFO_FILE_OS__PM16 2 /* VOS__PM16 */ #define MD_VSFIXEDFILEINFO_FILE_OS__PM32 3 /* VOS__PM32 */ #define MD_VSFIXEDFILEINFO_FILE_OS__WINDOWS32 4 /* VOS__WINDOWS32 */ /* For (MDVSFixedFileInfo).file_type */ #define MD_VSFIXEDFILEINFO_FILE_TYPE_UNKNOWN 0 /* VFT_UNKNOWN */ #define MD_VSFIXEDFILEINFO_FILE_TYPE_APP 1 /* VFT_APP */ #define MD_VSFIXEDFILEINFO_FILE_TYPE_DLL 2 /* VFT_DLL */ #define MD_VSFIXEDFILEINFO_FILE_TYPE_DRV 3 /* VFT_DLL */ #define MD_VSFIXEDFILEINFO_FILE_TYPE_FONT 4 /* VFT_FONT */ #define MD_VSFIXEDFILEINFO_FILE_TYPE_VXD 5 /* VFT_VXD */ #define MD_VSFIXEDFILEINFO_FILE_TYPE_STATIC_LIB 7 /* VFT_STATIC_LIB */ /* For (MDVSFixedFileInfo).file_subtype */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_UNKNOWN 0 /* VFT2_UNKNOWN */ /* with file_type = MD_VSFIXEDFILEINFO_FILETYPE_DRV */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_PRINTER 1 /* VFT2_DRV_PRINTER */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_KEYBOARD 2 /* VFT2_DRV_KEYBOARD */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_LANGUAGE 3 /* VFT2_DRV_LANGUAGE */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_DISPLAY 4 /* VFT2_DRV_DISPLAY */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_MOUSE 5 /* VFT2_DRV_MOUSE */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_NETWORK 6 /* VFT2_DRV_NETWORK */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_SYSTEM 7 /* VFT2_DRV_SYSTEM */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_INSTALLABLE 8 /* VFT2_DRV_INSTALLABLE */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_SOUND 9 /* VFT2_DRV_SOUND */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_COMM 10 /* VFT2_DRV_COMM */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_INPUTMETHOD 11 /* VFT2_DRV_INPUTMETHOD */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_DRV_VERSIONED_PRINTER 12 /* VFT2_DRV_VERSIONED_PRINTER */ /* with file_type = MD_VSFIXEDFILEINFO_FILETYPE_FONT */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_FONT_RASTER 1 /* VFT2_FONT_RASTER */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_FONT_VECTOR 2 /* VFT2_FONT_VECTOR */ #define MD_VSFIXEDFILEINFO_FILE_SUBTYPE_FONT_TRUETYPE 3 /* VFT2_FONT_TRUETYPE */ /* * DbgHelp.h */ /* An MDRVA is an offset into the minidump file. The beginning of the * MDRawHeader is at offset 0. */ typedef u_int32_t MDRVA; /* RVA */ typedef struct { u_int32_t data_size; MDRVA rva; } MDLocationDescriptor; /* MINIDUMP_LOCATION_DESCRIPTOR */ typedef struct { /* The base address of the memory range on the host that produced the * minidump. */ u_int64_t start_of_memory_range; MDLocationDescriptor memory; } MDMemoryDescriptor; /* MINIDUMP_MEMORY_DESCRIPTOR */ typedef struct { u_int32_t signature; u_int32_t version; u_int32_t stream_count; MDRVA stream_directory_rva; /* A |stream_count|-sized array of * MDRawDirectory structures. */ u_int32_t checksum; /* Can be 0. In fact, that's all that's * been found in minidump files. */ u_int32_t time_date_stamp; /* time_t */ u_int64_t flags; } MDRawHeader; /* MINIDUMP_HEADER */ /* For (MDRawHeader).signature and (MDRawHeader).version. Note that only the * low 16 bits of (MDRawHeader).version are MD_HEADER_VERSION. Per the * documentation, the high 16 bits are implementation-specific. */ #define MD_HEADER_SIGNATURE 0x504d444d /* 'PMDM' */ /* MINIDUMP_SIGNATURE */ #define MD_HEADER_VERSION 0x0000a793 /* 42899 */ /* MINIDUMP_VERSION */ /* For (MDRawHeader).flags: */ typedef enum { /* MD_NORMAL is the standard type of minidump. It includes full * streams for the thread list, module list, exception, system info, * and miscellaneous info. A memory list stream is also present, * pointing to the same stack memory contained in the thread list, * as well as a 256-byte region around the instruction address that * was executing when the exception occurred. Stack memory is from * 4 bytes below a thread's stack pointer up to the top of the * memory region encompassing the stack. */ MD_NORMAL = 0x00000000, MD_WITH_DATA_SEGS = 0x00000001, MD_WITH_FULL_MEMORY = 0x00000002, MD_WITH_HANDLE_DATA = 0x00000004, MD_FILTER_MEMORY = 0x00000008, MD_SCAN_MEMORY = 0x00000010, MD_WITH_UNLOADED_MODULES = 0x00000020, MD_WITH_INDIRECTLY_REFERENCED_MEMORY = 0x00000040, MD_FILTER_MODULE_PATHS = 0x00000080, MD_WITH_PROCESS_THREAD_DATA = 0x00000100, MD_WITH_PRIVATE_READ_WRITE_MEMORY = 0x00000200, MD_WITHOUT_OPTIONAL_DATA = 0x00000400, MD_WITH_FULL_MEMORY_INFO = 0x00000800, MD_WITH_THREAD_INFO = 0x00001000, MD_WITH_CODE_SEGS = 0x00002000 } MDType; /* MINIDUMP_TYPE */ typedef struct { u_int32_t stream_type; MDLocationDescriptor location; } MDRawDirectory; /* MINIDUMP_DIRECTORY */ /* For (MDRawDirectory).stream_type */ typedef enum { MD_UNUSED_STREAM = 0, MD_RESERVED_STREAM_0 = 1, MD_RESERVED_STREAM_1 = 2, MD_THREAD_LIST_STREAM = 3, /* MDRawThreadList */ MD_MODULE_LIST_STREAM = 4, /* MDRawModuleList */ MD_MEMORY_LIST_STREAM = 5, /* MDRawMemoryList */ MD_EXCEPTION_STREAM = 6, /* MDRawExceptionStream */ MD_SYSTEM_INFO_STREAM = 7, /* MDRawSystemInfo */ MD_THREAD_EX_LIST_STREAM = 8, MD_MEMORY_64_LIST_STREAM = 9, MD_COMMENT_STREAM_A = 10, MD_COMMENT_STREAM_W = 11, MD_HANDLE_DATA_STREAM = 12, MD_FUNCTION_TABLE_STREAM = 13, MD_UNLOADED_MODULE_LIST_STREAM = 14, MD_MISC_INFO_STREAM = 15, /* MDRawMiscInfo */ MD_LAST_RESERVED_STREAM = 0x0000ffff, /* Breakpad extension types. 0x4767 = "Gg" */ MD_BREAKPAD_INFO_STREAM = 0x47670001, /* MDRawBreakpadInfo */ MD_ASSERTION_INFO_STREAM = 0x47670002 /* MDRawAssertionInfo */ } MDStreamType; /* MINIDUMP_STREAM_TYPE */ typedef struct { u_int32_t length; /* Length of buffer in bytes (not characters), * excluding 0-terminator */ u_int16_t buffer[1]; /* UTF-16-encoded, 0-terminated */ } MDString; /* MINIDUMP_STRING */ static const size_t MDString_minsize = offsetof(MDString, buffer[0]); typedef struct { u_int32_t thread_id; u_int32_t suspend_count; u_int32_t priority_class; u_int32_t priority; u_int64_t teb; /* Thread environment block */ MDMemoryDescriptor stack; MDLocationDescriptor thread_context; /* MDRawContext[CPU] */ } MDRawThread; /* MINIDUMP_THREAD */ typedef struct { u_int32_t number_of_threads; MDRawThread threads[1]; } MDRawThreadList; /* MINIDUMP_THREAD_LIST */ static const size_t MDRawThreadList_minsize = offsetof(MDRawThreadList, threads[0]); typedef struct { u_int64_t base_of_image; u_int32_t size_of_image; u_int32_t checksum; /* 0 if unknown */ u_int32_t time_date_stamp; /* time_t */ MDRVA module_name_rva; /* MDString, pathname or filename */ MDVSFixedFileInfo version_info; /* The next field stores a CodeView record and is populated when a module's * debug information resides in a PDB file. It identifies the PDB file. */ MDLocationDescriptor cv_record; /* The next field is populated when a module's debug information resides * in a DBG file. It identifies the DBG file. This field is effectively * obsolete with modules built by recent toolchains. */ MDLocationDescriptor misc_record; /* Alignment problem: reserved0 and reserved1 are defined by the platform * SDK as 64-bit quantities. However, that results in a structure whose * alignment is unpredictable on different CPUs and ABIs. If the ABI * specifies full alignment of 64-bit quantities in structures (as ppc * does), there will be padding between miscRecord and reserved0. If * 64-bit quantities can be aligned on 32-bit boundaries (as on x86), * this padding will not exist. (Note that the structure up to this point * contains 1 64-bit member followed by 21 32-bit members.) * As a workaround, reserved0 and reserved1 are instead defined here as * four 32-bit quantities. This should be harmless, as there are * currently no known uses for these fields. */ u_int32_t reserved0[2]; u_int32_t reserved1[2]; } MDRawModule; /* MINIDUMP_MODULE */ /* The inclusion of a 64-bit type in MINIDUMP_MODULE forces the struct to * be tail-padded out to a multiple of 64 bits under some ABIs (such as PPC). * This doesn't occur on systems that don't tail-pad in this manner. Define * this macro to be the usable size of the MDRawModule struct, and use it in * place of sizeof(MDRawModule). */ #define MD_MODULE_SIZE 108 /* (MDRawModule).cv_record can reference MDCVInfoPDB20 or MDCVInfoPDB70. * Ref.: http://www.debuginfo.com/articles/debuginfomatch.html * MDCVInfoPDB70 is the expected structure type with recent toolchains. */ typedef struct { u_int32_t signature; u_int32_t offset; /* Offset to debug data (expect 0 in minidump) */ } MDCVHeader; typedef struct { MDCVHeader cv_header; u_int32_t signature; /* time_t debug information created */ u_int32_t age; /* revision of PDB file */ u_int8_t pdb_file_name[1]; /* Pathname or filename of PDB file */ } MDCVInfoPDB20; static const size_t MDCVInfoPDB20_minsize = offsetof(MDCVInfoPDB20, pdb_file_name[0]); #define MD_CVINFOPDB20_SIGNATURE 0x3031424e /* cvHeader.signature = '01BN' */ typedef struct { u_int32_t cv_signature; MDGUID signature; /* GUID, identifies PDB file */ u_int32_t age; /* Identifies incremental changes to PDB file */ u_int8_t pdb_file_name[1]; /* Pathname or filename of PDB file, * 0-terminated 8-bit character data (UTF-8?) */ } MDCVInfoPDB70; static const size_t MDCVInfoPDB70_minsize = offsetof(MDCVInfoPDB70, pdb_file_name[0]); #define MD_CVINFOPDB70_SIGNATURE 0x53445352 /* cvSignature = 'SDSR' */ typedef struct { u_int32_t data1[2]; u_int32_t data2; u_int32_t data3; u_int32_t data4; u_int32_t data5[3]; u_int8_t extra[2]; } MDCVInfoELF; /* In addition to the two CodeView record formats above, used for linking * to external pdb files, it is possible for debugging data to be carried * directly in the CodeView record itself. These signature values will * be found in the first 4 bytes of the CodeView record. Additional values * not commonly experienced in the wild are given by "Microsoft Symbol and * Type Information", http://www.x86.org/ftp/manuals/tools/sym.pdf, section * 7.2. An in-depth description of the CodeView 4.1 format is given by * "Undocumented Windows 2000 Secrets", Windows 2000 Debugging Support/ * Microsoft Symbol File Internals/CodeView Subsections, * http://www.rawol.com/features/undocumented/sbs-w2k-1-windows-2000-debugging-support.pdf */ #define MD_CVINFOCV41_SIGNATURE 0x3930424e /* '90BN', CodeView 4.10. */ #define MD_CVINFOCV50_SIGNATURE 0x3131424e /* '11BN', CodeView 5.0, * MS C7-format (/Z7). */ #define MD_CVINFOUNKNOWN_SIGNATURE 0xffffffff /* An unlikely value. */ /* (MDRawModule).miscRecord can reference MDImageDebugMisc. The Windows * structure is actually defined in WinNT.h. This structure is effectively * obsolete with modules built by recent toolchains. */ typedef struct { u_int32_t data_type; /* IMAGE_DEBUG_TYPE_*, not defined here because * this debug record type is mostly obsolete. */ u_int32_t length; /* Length of entire MDImageDebugMisc structure */ u_int8_t unicode; /* True if data is multibyte */ u_int8_t reserved[3]; u_int8_t data[1]; } MDImageDebugMisc; /* IMAGE_DEBUG_MISC */ static const size_t MDImageDebugMisc_minsize = offsetof(MDImageDebugMisc, data[0]); typedef struct { u_int32_t number_of_modules; MDRawModule modules[1]; } MDRawModuleList; /* MINIDUMP_MODULE_LIST */ static const size_t MDRawModuleList_minsize = offsetof(MDRawModuleList, modules[0]); typedef struct { u_int32_t number_of_memory_ranges; MDMemoryDescriptor memory_ranges[1]; } MDRawMemoryList; /* MINIDUMP_MEMORY_LIST */ static const size_t MDRawMemoryList_minsize = offsetof(MDRawMemoryList, memory_ranges[0]); #define MD_EXCEPTION_MAXIMUM_PARAMETERS 15 typedef struct { u_int32_t exception_code; /* Windows: MDExceptionCodeWin, * Mac OS X: MDExceptionMac, * Linux: MDExceptionCodeLinux. */ u_int32_t exception_flags; /* Windows: 1 if noncontinuable, Mac OS X: MDExceptionCodeMac. */ u_int64_t exception_record; /* Address (in the minidump-producing host's * memory) of another MDException, for * nested exceptions. */ u_int64_t exception_address; /* The address that caused the exception. * Mac OS X: exception subcode (which is * typically the address). */ u_int32_t number_parameters; /* Number of valid elements in * exception_information. */ u_int32_t __align; u_int64_t exception_information[MD_EXCEPTION_MAXIMUM_PARAMETERS]; } MDException; /* MINIDUMP_EXCEPTION */ #include "minidump_exception_win32.h" #include "minidump_exception_mac.h" #include "minidump_exception_linux.h" #include "minidump_exception_solaris.h" typedef struct { u_int32_t thread_id; /* Thread in which the exception * occurred. Corresponds to * (MDRawThread).thread_id. */ u_int32_t __align; MDException exception_record; MDLocationDescriptor thread_context; /* MDRawContext[CPU] */ } MDRawExceptionStream; /* MINIDUMP_EXCEPTION_STREAM */ typedef union { struct { u_int32_t vendor_id[3]; /* cpuid 0: ebx, edx, ecx */ u_int32_t version_information; /* cpuid 1: eax */ u_int32_t feature_information; /* cpuid 1: edx */ u_int32_t amd_extended_cpu_features; /* cpuid 0x80000001, ebx */ } x86_cpu_info; struct { u_int64_t processor_features[2]; } other_cpu_info; } MDCPUInformation; /* CPU_INFORMATION */ typedef struct { /* The next 3 fields and numberOfProcessors are from the SYSTEM_INFO * structure as returned by GetSystemInfo */ u_int16_t processor_architecture; u_int16_t processor_level; /* x86: 5 = 586, 6 = 686, ... */ u_int16_t processor_revision; /* x86: 0xMMSS, where MM=model, * SS=stepping */ u_int8_t number_of_processors; u_int8_t product_type; /* Windows: VER_NT_* from WinNT.h */ /* The next 5 fields are from the OSVERSIONINFO structure as returned * by GetVersionEx */ u_int32_t major_version; u_int32_t minor_version; u_int32_t build_number; u_int32_t platform_id; MDRVA csd_version_rva; /* MDString further identifying the * host OS. * Windows: name of the installed OS * service pack. * Mac OS X: the Apple OS build number * (sw_vers -buildVersion). * Linux: uname -srvmo */ u_int16_t suite_mask; /* Windows: VER_SUITE_* from WinNT.h */ u_int16_t reserved2; MDCPUInformation cpu; } MDRawSystemInfo; /* MINIDUMP_SYSTEM_INFO */ /* For (MDRawSystemInfo).processor_architecture: */ typedef enum { MD_CPU_ARCHITECTURE_X86 = 0, /* PROCESSOR_ARCHITECTURE_INTEL */ MD_CPU_ARCHITECTURE_MIPS = 1, /* PROCESSOR_ARCHITECTURE_MIPS */ MD_CPU_ARCHITECTURE_ALPHA = 2, /* PROCESSOR_ARCHITECTURE_ALPHA */ MD_CPU_ARCHITECTURE_PPC = 3, /* PROCESSOR_ARCHITECTURE_PPC */ MD_CPU_ARCHITECTURE_SHX = 4, /* PROCESSOR_ARCHITECTURE_SHX * (Super-H) */ MD_CPU_ARCHITECTURE_ARM = 5, /* PROCESSOR_ARCHITECTURE_ARM */ MD_CPU_ARCHITECTURE_IA64 = 6, /* PROCESSOR_ARCHITECTURE_IA64 */ MD_CPU_ARCHITECTURE_ALPHA64 = 7, /* PROCESSOR_ARCHITECTURE_ALPHA64 */ MD_CPU_ARCHITECTURE_MSIL = 8, /* PROCESSOR_ARCHITECTURE_MSIL * (Microsoft Intermediate Language) */ MD_CPU_ARCHITECTURE_AMD64 = 9, /* PROCESSOR_ARCHITECTURE_AMD64 */ MD_CPU_ARCHITECTURE_X86_WIN64 = 10, /* PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 (WoW64) */ MD_CPU_ARCHITECTURE_SPARC = 0x8001, /* Breakpad-defined value for SPARC */ MD_CPU_ARCHITECTURE_UNKNOWN = 0xffff /* PROCESSOR_ARCHITECTURE_UNKNOWN */ } MDCPUArchitecture; /* For (MDRawSystemInfo).platform_id: */ typedef enum { MD_OS_WIN32S = 0, /* VER_PLATFORM_WIN32s (Windows 3.1) */ MD_OS_WIN32_WINDOWS = 1, /* VER_PLATFORM_WIN32_WINDOWS (Windows 95-98-Me) */ MD_OS_WIN32_NT = 2, /* VER_PLATFORM_WIN32_NT (Windows NT, 2000+) */ MD_OS_WIN32_CE = 3, /* VER_PLATFORM_WIN32_CE, VER_PLATFORM_WIN32_HH * (Windows CE, Windows Mobile, "Handheld") */ /* The following values are Breakpad-defined. */ MD_OS_UNIX = 0x8000, /* Generic Unix-ish */ MD_OS_MAC_OS_X = 0x8101, /* Mac OS X/Darwin */ MD_OS_LINUX = 0x8201, /* Linux */ MD_OS_SOLARIS = 0x8202 /* Solaris */ } MDOSPlatform; typedef struct { u_int32_t size_of_info; /* Length of entire MDRawMiscInfo structure. */ u_int32_t flags1; /* The next field is only valid if flags1 contains * MD_MISCINFO_FLAGS1_PROCESS_ID. */ u_int32_t process_id; /* The next 3 fields are only valid if flags1 contains * MD_MISCINFO_FLAGS1_PROCESS_TIMES. */ u_int32_t process_create_time; /* time_t process started */ u_int32_t process_user_time; /* seconds of user CPU time */ u_int32_t process_kernel_time; /* seconds of kernel CPU time */ /* The following fields are not present in MINIDUMP_MISC_INFO but are * in MINIDUMP_MISC_INFO_2. When this struct is populated, these values * may not be set. Use flags1 or sizeOfInfo to determine whether these * values are present. These are only valid when flags1 contains * MD_MISCINFO_FLAGS1_PROCESSOR_POWER_INFO. */ u_int32_t processor_max_mhz; u_int32_t processor_current_mhz; u_int32_t processor_mhz_limit; u_int32_t processor_max_idle_state; u_int32_t processor_current_idle_state; } MDRawMiscInfo; /* MINIDUMP_MISC_INFO, MINIDUMP_MISC_INFO2 */ #define MD_MISCINFO_SIZE 24 #define MD_MISCINFO2_SIZE 44 /* For (MDRawMiscInfo).flags1. These values indicate which fields in the * MDRawMiscInfoStructure are valid. */ typedef enum { MD_MISCINFO_FLAGS1_PROCESS_ID = 0x00000001, /* MINIDUMP_MISC1_PROCESS_ID */ MD_MISCINFO_FLAGS1_PROCESS_TIMES = 0x00000002, /* MINIDUMP_MISC1_PROCESS_TIMES */ MD_MISCINFO_FLAGS1_PROCESSOR_POWER_INFO = 0x00000004 /* MINIDUMP_MISC1_PROCESSOR_POWER_INFO */ } MDMiscInfoFlags1; /* * Breakpad extension types */ typedef struct { /* validity is a bitmask with values from MDBreakpadInfoValidity, indicating * which of the other fields in the structure are valid. */ u_int32_t validity; /* Thread ID of the handler thread. dump_thread_id should correspond to * the thread_id of an MDRawThread in the minidump's MDRawThreadList if * a dedicated thread in that list was used to produce the minidump. If * the MDRawThreadList does not contain a dedicated thread used to produce * the minidump, this field should be set to 0 and the validity field * must not contain MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID. */ u_int32_t dump_thread_id; /* Thread ID of the thread that requested the minidump be produced. As * with dump_thread_id, requesting_thread_id should correspond to the * thread_id of an MDRawThread in the minidump's MDRawThreadList. For * minidumps produced as a result of an exception, requesting_thread_id * will be the same as the MDRawExceptionStream's thread_id field. For * minidumps produced "manually" at the program's request, * requesting_thread_id will indicate which thread caused the dump to be * written. If the minidump was produced at the request of something * other than a thread in the MDRawThreadList, this field should be set * to 0 and the validity field must not contain * MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID. */ u_int32_t requesting_thread_id; } MDRawBreakpadInfo; /* For (MDRawBreakpadInfo).validity: */ typedef enum { /* When set, the dump_thread_id field is valid. */ MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID = 1 << 0, /* When set, the requesting_thread_id field is valid. */ MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID = 1 << 1 } MDBreakpadInfoValidity; typedef struct { /* expression, function, and file are 0-terminated UTF-16 strings. They * may be truncated if necessary, but should always be 0-terminated when * written to a file. * Fixed-length strings are used because MiniDumpWriteDump doesn't offer * a way for user streams to point to arbitrary RVAs for strings. */ u_int16_t expression[128]; /* Assertion that failed... */ u_int16_t function[128]; /* ...within this function... */ u_int16_t file[128]; /* ...in this file... */ u_int32_t line; /* ...at this line. */ u_int32_t type; } MDRawAssertionInfo; /* For (MDRawAssertionInfo).type: */ typedef enum { MD_ASSERTION_INFO_TYPE_UNKNOWN = 0, /* Used for assertions that would be raised by the MSVC CRT but are * directed to an invalid parameter handler instead. */ MD_ASSERTION_INFO_TYPE_INVALID_PARAMETER, /* Used for assertions that would be raised by the MSVC CRT but are * directed to a pure virtual call handler instead. */ MD_ASSERTION_INFO_TYPE_PURE_VIRTUAL_CALL } MDAssertionInfoData; #if defined(_MSC_VER) #pragma warning(pop) #endif /* _MSC_VER */ #endif /* GOOGLE_BREAKPAD_COMMON_MINIDUMP_FORMAT_H__ */ scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/google_breakpad/common/minidump_size.h000066400000000000000000000065761271170121200331640ustar00rootroot00000000000000// Copyright (c) 2007, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // minidump_size.h: Provides a C++ template for programmatic access to // the sizes of various types defined in minidump_format.h. // // Author: Mark Mentovai #ifndef GOOGLE_BREAKPAD_COMMON_MINIDUMP_SIZE_H__ #define GOOGLE_BREAKPAD_COMMON_MINIDUMP_SIZE_H__ #include #include "google_breakpad/common/minidump_format.h" namespace google_breakpad { template class minidump_size { public: static size_t size() { return sizeof(T); } }; // Explicit specializations for variable-length types. The size returned // for these should be the size for an object without its variable-length // section. template<> class minidump_size { public: static size_t size() { return MDString_minsize; } }; template<> class minidump_size { public: static size_t size() { return MDRawThreadList_minsize; } }; template<> class minidump_size { public: static size_t size() { return MDCVInfoPDB20_minsize; } }; template<> class minidump_size { public: static size_t size() { return MDCVInfoPDB70_minsize; } }; template<> class minidump_size { public: static size_t size() { return MDImageDebugMisc_minsize; } }; template<> class minidump_size { public: static size_t size() { return MDRawModuleList_minsize; } }; template<> class minidump_size { public: static size_t size() { return MDRawMemoryList_minsize; } }; // Explicit specialization for MDRawModule, for which sizeof may include // tail-padding on some architectures but not others. template<> class minidump_size { public: static size_t size() { return MD_MODULE_SIZE; } }; } // namespace google_breakpad #endif // GOOGLE_BREAKPAD_COMMON_MINIDUMP_SIZE_H__ scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/processor/000077500000000000000000000000001271170121200255435ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/crash_reporter/google-breakpad/processor/scoped_ptr.h000066400000000000000000000170731271170121200300660ustar00rootroot00000000000000// (C) Copyright Greg Colvin and Beman Dawes 1998, 1999. // Copyright (c) 2001, 2002 Peter Dimov // // Permission to copy, use, modify, sell and distribute this software // is granted provided this copyright notice appears in all copies. // This software is provided "as is" without express or implied // warranty, and with no claim as to its suitability for any purpose. // // See http://www.boost.org/libs/smart_ptr/scoped_ptr.htm for documentation. // // scoped_ptr mimics a built-in pointer except that it guarantees deletion // of the object pointed to, either on destruction of the scoped_ptr or via // an explicit reset(). scoped_ptr is a simple solution for simple needs; // use shared_ptr or std::auto_ptr if your needs are more complex. // *** NOTE *** // If your scoped_ptr is a class member of class FOO pointing to a // forward declared type BAR (as shown below), then you MUST use a non-inlined // version of the destructor. The destructor of a scoped_ptr (called from // FOO's destructor) must have a complete definition of BAR in order to // destroy it. Example: // // -- foo.h -- // class BAR; // // class FOO { // public: // FOO(); // ~FOO(); // Required for sources that instantiate class FOO to compile! // // private: // scoped_ptr bar_; // }; // // -- foo.cc -- // #include "foo.h" // FOO::~FOO() {} // Empty, but must be non-inlined to FOO's class definition. // scoped_ptr_malloc added by Google // When one of these goes out of scope, instead of doing a delete or // delete[], it calls free(). scoped_ptr_malloc is likely to see // much more use than any other specializations. // release() added by Google // Use this to conditionally transfer ownership of a heap-allocated object // to the caller, usually on method success. #ifndef PROCESSOR_SCOPED_PTR_H__ #define PROCESSOR_SCOPED_PTR_H__ #include // for std::ptrdiff_t #include // for assert #include // for free() decl namespace google_breakpad { template class scoped_ptr { private: T* ptr; scoped_ptr(scoped_ptr const &); scoped_ptr & operator=(scoped_ptr const &); public: typedef T element_type; explicit scoped_ptr(T* p = 0): ptr(p) {} ~scoped_ptr() { typedef char type_must_be_complete[sizeof(T)]; delete ptr; } void reset(T* p = 0) { typedef char type_must_be_complete[sizeof(T)]; if (ptr != p) { delete ptr; ptr = p; } } T& operator*() const { assert(ptr != 0); return *ptr; } T* operator->() const { assert(ptr != 0); return ptr; } bool operator==(T* p) const { return ptr == p; } bool operator!=(T* p) const { return ptr != p; } T* get() const { return ptr; } void swap(scoped_ptr & b) { T* tmp = b.ptr; b.ptr = ptr; ptr = tmp; } T* release() { T* tmp = ptr; ptr = 0; return tmp; } private: // no reason to use these: each scoped_ptr should have its own object template bool operator==(scoped_ptr const& p) const; template bool operator!=(scoped_ptr const& p) const; }; template inline void swap(scoped_ptr& a, scoped_ptr& b) { a.swap(b); } template inline bool operator==(T* p, const scoped_ptr& b) { return p == b.get(); } template inline bool operator!=(T* p, const scoped_ptr& b) { return p != b.get(); } // scoped_array extends scoped_ptr to arrays. Deletion of the array pointed to // is guaranteed, either on destruction of the scoped_array or via an explicit // reset(). Use shared_array or std::vector if your needs are more complex. template class scoped_array { private: T* ptr; scoped_array(scoped_array const &); scoped_array & operator=(scoped_array const &); public: typedef T element_type; explicit scoped_array(T* p = 0) : ptr(p) {} ~scoped_array() { typedef char type_must_be_complete[sizeof(T)]; delete[] ptr; } void reset(T* p = 0) { typedef char type_must_be_complete[sizeof(T)]; if (ptr != p) { delete [] ptr; ptr = p; } } T& operator[](std::ptrdiff_t i) const { assert(ptr != 0); assert(i >= 0); return ptr[i]; } bool operator==(T* p) const { return ptr == p; } bool operator!=(T* p) const { return ptr != p; } T* get() const { return ptr; } void swap(scoped_array & b) { T* tmp = b.ptr; b.ptr = ptr; ptr = tmp; } T* release() { T* tmp = ptr; ptr = 0; return tmp; } private: // no reason to use these: each scoped_array should have its own object template bool operator==(scoped_array const& p) const; template bool operator!=(scoped_array const& p) const; }; template inline void swap(scoped_array& a, scoped_array& b) { a.swap(b); } template inline bool operator==(T* p, const scoped_array& b) { return p == b.get(); } template inline bool operator!=(T* p, const scoped_array& b) { return p != b.get(); } // This class wraps the c library function free() in a class that can be // passed as a template argument to scoped_ptr_malloc below. class ScopedPtrMallocFree { public: inline void operator()(void* x) const { free(x); } }; // scoped_ptr_malloc<> is similar to scoped_ptr<>, but it accepts a // second template argument, the functor used to free the object. template class scoped_ptr_malloc { private: T* ptr; scoped_ptr_malloc(scoped_ptr_malloc const &); scoped_ptr_malloc & operator=(scoped_ptr_malloc const &); public: typedef T element_type; explicit scoped_ptr_malloc(T* p = 0): ptr(p) {} ~scoped_ptr_malloc() { typedef char type_must_be_complete[sizeof(T)]; free_((void*) ptr); } void reset(T* p = 0) { typedef char type_must_be_complete[sizeof(T)]; if (ptr != p) { free_((void*) ptr); ptr = p; } } T& operator*() const { assert(ptr != 0); return *ptr; } T* operator->() const { assert(ptr != 0); return ptr; } bool operator==(T* p) const { return ptr == p; } bool operator!=(T* p) const { return ptr != p; } T* get() const { return ptr; } void swap(scoped_ptr_malloc & b) { T* tmp = b.ptr; b.ptr = ptr; ptr = tmp; } T* release() { T* tmp = ptr; ptr = 0; return tmp; } private: // no reason to use these: each scoped_ptr_malloc should have its own object template bool operator==(scoped_ptr_malloc const& p) const; template bool operator!=(scoped_ptr_malloc const& p) const; static FreeProc const free_; }; template FP const scoped_ptr_malloc::free_ = FP(); template inline void swap(scoped_ptr_malloc& a, scoped_ptr_malloc& b) { a.swap(b); } template inline bool operator==(T* p, const scoped_ptr_malloc& b) { return p == b.get(); } template inline bool operator!=(T* p, const scoped_ptr_malloc& b) { return p != b.get(); } } // namespace google_breakpad #endif // PROCESSOR_SCOPED_PTR_H__ scantailor-RELEASE_0_9_12_2/crash_reporter/main.cpp000066400000000000000000000035431271170121200221360ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "config.h" #include "CrashReportDialog.h" #include #include #include #include int main(int argc, char** argv) { QApplication app(argc, argv); QString const translation("crashreporter_"+QLocale::system().name()); QTranslator translator; // Try loading from the current directory. if (!translator.load(translation)) { // Now try loading from where it's supposed to be. QString path(QString::fromUtf8(TRANSLATIONS_DIR_ABS)); path += QChar('/'); path += translation; if (!translator.load(path)) { path = QString::fromUtf8(TRANSLATIONS_DIR_REL); path += QChar('/'); path += translation; translator.load(path); } } app.installTranslator(&translator); // Note that we use app.arguments() rather than argv, // because the former is Unicode-safe under Windows. QStringList const args(app.arguments()); if (args.size() < 3) { return 1; } CrashReportDialog* dialog = new CrashReportDialog(args[1], args[2]); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); return app.exec(); } scantailor-RELEASE_0_9_12_2/crash_reporter/ui/000077500000000000000000000000001271170121200211165ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/crash_reporter/ui/CMakeLists.txt000066400000000000000000000000531271170121200236540ustar00rootroot00000000000000SOURCE_GROUP("UI Files" FILES ${ui_files}) scantailor-RELEASE_0_9_12_2/crash_reporter/ui/CrashReportDialog.ui000066400000000000000000000072071271170121200250370ustar00rootroot00000000000000 CrashReportDialog 0 0 404 349 Crash Report Scan Tailor crashed. That's usually caused by errors in the software. You can help us find and fix the problem by submitting this crash report. true Information to be sent true Qt::Horizontal 40 20 If you have additional information, for example you know how to reproduce the crash, please provide it here. English or Russian please. If you are willing to provide further assistance to developers to help fix this problem, please provide your email address. true Email [optional] Status Qt::AlignCenter Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox rejected() CrashReportDialog reject() 325 339 286 274 scantailor-RELEASE_0_9_12_2/dewarping/000077500000000000000000000000001271170121200174375ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/dewarping/CMakeLists.txt000066400000000000000000000011401271170121200221730ustar00rootroot00000000000000PROJECT("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 ) SOURCE_GROUP("Sources" FILES ${sources}) ADD_LIBRARY(dewarping STATIC ${sources}) scantailor-RELEASE_0_9_12_2/dewarping/Curve.cpp000066400000000000000000000123311271170121200212270ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Curve.h" #include "XmlMarshaller.h" #include "XmlUnmarshaller.h" #include "VecNT.h" #ifndef Q_MOC_RUN #include #endif #include #include #include #include #include #include namespace dewarping { struct Curve::CloseEnough { bool operator()(QPointF const& p1, QPointF const& p2) { QPointF const d(p1 - p2); return d.x() * d.x() + d.y() * d.y() <= 0.01 * 0.01; } }; Curve::Curve() { } Curve::Curve(std::vector const& polyline) : m_polyline(polyline) { } Curve::Curve(XSpline const& xspline) : m_xspline(xspline), m_polyline(xspline.toPolyline()) { } Curve::Curve(QDomElement const& el) : m_xspline(deserializeXSpline(el.namedItem("xspline").toElement())), m_polyline(deserializePolyline(el.namedItem("polyline").toElement())) { } QDomElement Curve::toXml(QDomDocument& doc, QString const& name) const { if (!isValid()) { return QDomElement(); } QDomElement el(doc.createElement(name)); el.appendChild(serializeXSpline(m_xspline, doc, "xspline")); el.appendChild(serializePolyline(m_polyline, doc, "polyline")); return el; } bool Curve::isValid() const { return m_polyline.size() > 1 && m_polyline.front() != m_polyline.back(); } bool Curve::matches(Curve const& other) const { if (!approxPolylineMatch(m_polyline, other.m_polyline)) { return false; } return true; } std::vector Curve::deserializePolyline(QDomElement const& el) { QByteArray ba(QByteArray::fromBase64(el.text().trimmed().toLatin1())); QDataStream strm(&ba, QIODevice::ReadOnly); strm.setVersion(QDataStream::Qt_4_4); strm.setByteOrder(QDataStream::LittleEndian); unsigned const num_points = 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.push_back(QPointF(x, y)); } return points; } QDomElement Curve::serializePolyline( std::vector const& polyline, QDomDocument& doc, QString const& name) { if (polyline.empty()) { return QDomElement(); } QByteArray ba; ba.reserve(8 * polyline.size()); QDataStream strm(&ba, QIODevice::WriteOnly); strm.setVersion(QDataStream::Qt_4_4); strm.setByteOrder(QDataStream::LittleEndian); BOOST_FOREACH(QPointF const& pt, polyline) { strm << (float)pt.x() << (float)pt.y(); } QDomElement el(doc.createElement(name)); el.appendChild(doc.createTextNode(QString::fromLatin1(ba.toBase64()))); return el; } bool Curve::approxPolylineMatch( std::vector const& polyline1, std::vector const& polyline2) { if (polyline1.size() != polyline2.size()) { return false; } if (!std::equal(polyline1.begin(), polyline1.end(), polyline2.begin(), CloseEnough())) { return false; } return true; } QDomElement Curve::serializeXSpline( XSpline const& xspline, QDomDocument& doc, QString const& name) { if (xspline.numControlPoints() == 0) { return QDomElement(); } QDomElement el(doc.createElement(name)); XmlMarshaller marshaller(doc); int const num_control_points = xspline.numControlPoints(); for (int i = 0; i < num_control_points; ++i) { QPointF const pt(xspline.controlPointPosition(i)); el.appendChild(marshaller.pointF(pt, "point")); } return el; } XSpline Curve::deserializeXSpline(QDomElement const& el) { XSpline xspline; QString const 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; } bool Curve::splineHasLoops(XSpline const& spline) { int const num_control_points = spline.numControlPoints(); Vec2d const main_direction(spline.pointAt(1) - spline.pointAt(0)); for (int i = 1; i < num_control_points; ++i) { QPointF const cp1(spline.controlPointPosition(i - 1)); QPointF const cp2(spline.controlPointPosition(i)); if (Vec2d(cp2 - cp1).dot(main_direction) < 0) { return true; } #if 0 double const t1 = spline.controlPointIndexToT(i - 1); double const t2 = spline.controlPointIndexToT(i); if (Vec2d(spline.pointAt(t2) - spline.pointAt(t1)).dot(main_direction)) < 0) { return true; } #endif } return false; } } // namespace dewarping scantailor-RELEASE_0_9_12_2/dewarping/Curve.h000066400000000000000000000037771271170121200207120ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DEWARPING_CURVE_H_ #define DEWARPING_CURVE_H_ #include #include "XSpline.h" #include class QDomDocument; class QDomElement; class QString; namespace dewarping { class Curve { public: Curve(); Curve(std::vector const& polyline); Curve(XSpline const& xspline); Curve(QDomElement const& el); QDomElement toXml(QDomDocument& doc, QString const& name) const; bool isValid() const; bool matches(Curve const& other) const; XSpline const& xspline() const { return m_xspline; } std::vector const& polyline() const { return m_polyline; } static bool splineHasLoops(XSpline const& spline); private: struct CloseEnough; static std::vector deserializePolyline(QDomElement const& el); static QDomElement serializePolyline( std::vector const& polyline, QDomDocument& doc, QString const& name); static XSpline deserializeXSpline(QDomElement const& el); static QDomElement serializeXSpline( XSpline const& xspline, QDomDocument& doc, QString const& name); static bool approxPolylineMatch( std::vector const& polyline1, std::vector const& polyline2); XSpline m_xspline; std::vector m_polyline; }; } // namespace dewarping #endif scantailor-RELEASE_0_9_12_2/dewarping/CylindricalSurfaceDewarper.cpp000066400000000000000000000341051271170121200254060ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "CylindricalSurfaceDewarper.h" #include "ToLineProjector.h" #include "MatrixCalc.h" #include "VecNT.h" #include "NumericTraits.h" #include #include #include #ifndef Q_MOC_RUN #include #endif #include #include #include /* Naming conventions: img: Coordinates in the warped image. pln: Coordinates on a plane where the 4 corner points of the curved quadrilateral are supposed to lie. In our model we assume that all 4 lie on the same plane. The corner points are mapped to the following points on the plane: * Start point of curve1 [top curve]: (0, 0) * End point of curve1 [top curve]: (1, 0) * Start point of curve2 [bottom curve]: (0, 1) * End point of curve2 [bottom curve]: (1, 1) pln and img coordinates are linked by a 2D homography, namely m_pln2img and m_img2pln. crv: Dewarped normalized coordinates. crv X coordinates are linked to pln X ccoordinates through m_arcLengthMapper while the Y coordinates are linked by a one dimensional homography that's different for each generatrix. */ namespace dewarping { class CylindricalSurfaceDewarper::CoupledPolylinesIterator { public: CoupledPolylinesIterator( std::vector const& img_directrix1, std::vector const& img_directrix2, HomographicTransform<2, double> const& pln2img, HomographicTransform<2, double> const& 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( std::vector const& img_directrix1, std::vector const& 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); } CylindricalSurfaceDewarper::Generatrix CylindricalSurfaceDewarper::mapGeneratrix(double crv_x, State& state) const { double const pln_x = m_arcLengthMapper.arcLenToX(crv_x, state.m_arcLengthHint); Vec2d const pln_top_pt(pln_x, 0); Vec2d const pln_bottom_pt(pln_x, 1); Vec2d const img_top_pt(m_pln2img(pln_top_pt)); Vec2d const img_bottom_pt(m_pln2img(pln_bottom_pt)); QLineF const img_generatrix(img_top_pt, img_bottom_pt); ToLineProjector const projector(img_generatrix); Vec2d const img_directrix1_pt( m_imgDirectrix1Intersector.intersect(img_generatrix, state.m_intersectionHint1) ); Vec2d const img_directrix2_pt( m_imgDirectrix2Intersector.intersect(img_generatrix, state.m_intersectionHint2) ); Vec2d const img_straight_line_pt(m_pln2img(Vec2d(pln_x, m_plnStraightLineY))); double const img_directrix1_proj(projector.projectionScalar(img_directrix1_pt)); double const img_directrix2_proj(projector.projectionScalar(img_directrix2_pt)); double const 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 (fabs(m_plnStraightLineY) < 0.05 || 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); } QPointF CylindricalSurfaceDewarper::mapToDewarpedSpace(QPointF const& img_pt) const { State state; double const pln_x = m_img2pln(img_pt)[0]; double const crv_x = m_arcLengthMapper.xToArcLen(pln_x, state.m_arcLengthHint); Vec2d const pln_top_pt(pln_x, 0); Vec2d const pln_bottom_pt(pln_x, 1); Vec2d const img_top_pt(m_pln2img(pln_top_pt)); Vec2d const img_bottom_pt(m_pln2img(pln_bottom_pt)); QLineF const img_generatrix(img_top_pt, img_bottom_pt); ToLineProjector const projector(img_generatrix); Vec2d const img_directrix1_pt( m_imgDirectrix1Intersector.intersect(img_generatrix, state.m_intersectionHint1) ); Vec2d const img_directrix2_pt( m_imgDirectrix2Intersector.intersect(img_generatrix, state.m_intersectionHint2) ); Vec2d const img_straight_line_pt(m_pln2img(Vec2d(pln_x, m_plnStraightLineY))); double const img_directrix1_proj(projector.projectionScalar(img_directrix1_pt)); double const img_directrix2_proj(projector.projectionScalar(img_directrix2_pt)); double const 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 (fabs(m_plnStraightLineY) < 0.05 || 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); } HomographicTransform<1, double> const H(threePoint1DHomography(pairs)); double const img_pt_proj(projector.projectionScalar(img_pt)); double const crv_y = H(img_pt_proj); return QPointF(crv_x, crv_y); } QPointF CylindricalSurfaceDewarper::mapToWarpedSpace(QPointF const& crv_pt) const { State state; Generatrix const gtx(mapGeneratrix(crv_pt.x(), state)); return gtx.imgLine.pointAt(gtx.pln2img(crv_pt.y())); } HomographicTransform<2, double> CylindricalSurfaceDewarper::calcPlnToImgHomography( std::vector const& img_directrix1, std::vector const& 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( std::vector const& img_directrix1, std::vector const& img_directrix2, HomographicTransform<2, double> const pln2img, HomographicTransform<2, double> const 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)) { QLineF const img_generatrix(img_curve1_pt, img_curve2_pt); Vec2d const img_line1_pt(pln2img(Vec2d(pln_x, 0))); Vec2d const img_line2_pt(pln2img(Vec2d(pln_x, 1))); ToLineProjector const projector(img_generatrix); double const p1 = 0; double const p2 = projector.projectionScalar(img_line1_pt); double const p3 = projector.projectionScalar(img_line2_pt); double const p4 = 1; double const dp1 = p2 - p1; double const dp2 = p4 - p3; double const weight = fabs(dp1 + dp2); if (weight < 0.01) { continue; } double const p0 = (p3 * dp1 + p2 * dp2) / (dp1 + dp2); Vec2d const 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; } HomographicTransform<2, double> CylindricalSurfaceDewarper::fourPoint2DHomography( boost::array, 4> const& pairs) { VecNT<64, double> A; VecNT<8, double> B; double* pa = A.data(); double* pb = B.data(); int i = 0; typedef std::pair Pair; BOOST_FOREACH(Pair const& pair, pairs) { QPointF const from(pair.first); QPointF const 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); } HomographicTransform<1, double> CylindricalSurfaceDewarper::threePoint1DHomography( boost::array, 3> const& pairs) { VecNT<9, double> A; VecNT<3, double> B; double* pa = A.data(); double* pb = B.data(); typedef std::pair Pair; BOOST_FOREACH(Pair const& pair, pairs) { double const from = pair.first; double const 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( std::vector const& img_directrix1, std::vector const& 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; } QLineF const img_generatrix(img_curve1_pt, img_curve2_pt); Vec2d const img_line1_pt(m_pln2img(Vec2d(pln_x, 0))); Vec2d const img_line2_pt(m_pln2img(Vec2d(pln_x, 1))); ToLineProjector const projector(img_generatrix); double const y1 = projector.projectionScalar(img_line1_pt); double const 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); } /*======================= CoupledPolylinesIterator =========================*/ CylindricalSurfaceDewarper::CoupledPolylinesIterator::CoupledPolylinesIterator( std::vector const& img_directrix1, std::vector const& img_directrix2, HomographicTransform<2, double> const& pln2img, HomographicTransform<2, double> const& 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); return true; } else if (m_seq2It != m_seq2End) { next2(img_pt1, img_pt2, pln_x); return true; } else { return false; } } void CylindricalSurfaceDewarper::CoupledPolylinesIterator::next1(QPointF& img_pt1, QPointF& img_pt2, double& pln_x) { Vec2d const pln_pt1(m_img2pln(m_nextImgPt1)); pln_x = pln_pt1[0]; img_pt1 = m_nextImgPt1; Vec2d const pln_ptx(pln_pt1[0], pln_pt1[1] + 1); Vec2d const 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; } 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) { Vec2d const pln_pt2(m_img2pln(m_nextImgPt2)); pln_x = pln_pt2[0]; img_pt2 = m_nextImgPt2; Vec2d const pln_ptx(pln_pt2[0], pln_pt2[1] + 1); Vec2d const 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; } advance2(); if (m_seq1It != m_seq1End && Vec2d(m_nextImgPt1 - img_pt1).squaredNorm() < 1) { advance1(); } } void CylindricalSurfaceDewarper::CoupledPolylinesIterator::advance1() { if (++m_seq1It == m_seq1End) { return; } 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; } m_prevImgPt2 = m_nextImgPt2; m_nextImgPt2 = *m_seq2It; m_nextPlnX2 = m_img2pln(m_nextImgPt2)[0]; } } // namespace dewarping scantailor-RELEASE_0_9_12_2/dewarping/CylindricalSurfaceDewarper.h000066400000000000000000000076071271170121200250620ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DEWARPING_CYLINDRICAL_SURFACE_DEWARPER_H_ #define DEWARPING_CYLINDRICAL_SURFACE_DEWARPER_H_ #include "HomographicTransform.h" #include "PolylineIntersector.h" #include "ArcLengthMapper.h" #ifndef Q_MOC_RUN #include #endif #include #include #include #include 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(QLineF const& img_line, HomographicTransform<1, double> const& 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( std::vector const& img_directrix1, std::vector const& 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(QPointF const& 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(QPointF const& crv_pt) const; private: class CoupledPolylinesIterator; static HomographicTransform<2, double> calcPlnToImgHomography( std::vector const& img_directrix1, std::vector const& img_directrix2); static double calcPlnStraightLineY( std::vector const& img_directrix1, std::vector const& img_directrix2, HomographicTransform<2, double> pln2img, HomographicTransform<2, double> img2pln); static HomographicTransform<2, double> fourPoint2DHomography( boost::array, 4> const& pairs); static HomographicTransform<1, double> threePoint1DHomography( boost::array, 3> const& pairs); void initArcLengthMapper( std::vector const& img_directrix1, std::vector const& 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 scantailor-RELEASE_0_9_12_2/dewarping/DetectVertContentBounds.cpp000066400000000000000000000324111271170121200247230ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "DetectVertContentBounds.h" #include "DebugImages.h" #include "VecNT.h" #include "SidesOfLine.h" #include "imageproc/BinaryImage.h" #include "imageproc/Constants.h" #include #include #include #include #include #include #include #include #include #include #ifndef Q_MOC_RUN #include #include #include #endif #include #include #include #include #include using namespace imageproc; namespace dewarping { namespace { struct VertRange { int top; int bottom; VertRange() : top(-1), bottom(-1) {} VertRange(int t, int b) : top(t), bottom(b) {} bool isValid() const { return top != -1; } }; struct Segment { QLine line; Vec2d unitVec; int vertDist; bool distToVertLine(int vert_line_x) const { return std::min(abs(line.p1().x() - vert_line_x), abs(line.p2().x() - vert_line_x)); } Segment(QLine const& line, Vec2d const& vec, int dist) : line(line), unitVec(vec), vertDist(dist) {} }; struct RansacModel { std::vector segments; int totalVertDist; // Sum of individual Segment::vertDist RansacModel() : totalVertDist(0) {} void add(Segment const& seg) { segments.push_back(seg); totalVertDist += seg.vertDist; } bool betterThan(RansacModel const& other) const { return totalVertDist > other.totalVertDist; } void swap(RansacModel& other) { segments.swap(other.segments); std::swap(totalVertDist, other.totalVertDist); } }; class RansacAlgo { public: RansacAlgo(std::vector const& segments); void buildAndAssessModel(Segment const& seed_segment); RansacModel& bestModel() { return m_bestModel; } RansacModel const& bestModel() const { return m_bestModel; } private: std::vector const& m_rSegments; RansacModel m_bestModel; double m_cosThreshold; }; class SequentialColumnProcessor { public: enum LeftOrRight { LEFT, RIGHT }; SequentialColumnProcessor(QSize const& page_size, LeftOrRight left_or_right); void process(int x, VertRange const& range); QLineF approximateWithLine(std::vector* dbg_segments = 0) const; QImage visualizeEnvelope(QImage const& background); private: bool topMidBottomConcave(QPoint top, QPoint mid, QPoint bottom) const; static int crossZ(QPoint v1, QPoint v2); bool segmentIsTooLong(QPoint p1, QPoint p2) const; QLineF interpolateSegments(std::vector const& 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; }; RansacAlgo::RansacAlgo(std::vector const& segments) : m_rSegments(segments), m_cosThreshold(cos(4.0 * constants::DEG2RAD)) { } void RansacAlgo::buildAndAssessModel(Segment const& seed_segment) { RansacModel cur_model; cur_model.add(seed_segment); BOOST_FOREACH(Segment const& seg, m_rSegments) { double const 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( QSize const& page_size, LeftOrRight left_or_right) : m_leftMinusOneRightOne(left_or_right == LEFT ? -1 : 1) , m_leftOrRight(left_or_right) { int const w = page_size.width(); int const h = page_size.height(); m_maxSegmentSqLen = (w * w + h * h) / 3; } void SequentialColumnProcessor::process(int x, VertRange const& range) { 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); } return; } if (range.top < m_path.front().y()) { // Growing towards the top. QPoint const top(x, range.top); // Now we decide if we need to trim the path before // adding a new element to it to preserve convexity. size_t const 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); } if (range.bottom > m_path.back().y()) { // Growing towards the bottom. QPoint const bottom(x, range.bottom); // Now we decide if we need to trim the path before // adding a new element to it to preserve convexity. int mid_idx = 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); } } bool SequentialColumnProcessor::topMidBottomConcave(QPoint top, QPoint mid, QPoint bottom) const { int const cross_z = crossZ(mid - top, bottom - mid); return cross_z * m_leftMinusOneRightOne < 0; } int SequentialColumnProcessor::crossZ(QPoint v1, QPoint v2) { return v1.x() * v2.y() - v2.x() * v1.y(); } bool SequentialColumnProcessor::segmentIsTooLong(QPoint const p1, QPoint const p2) const { QPoint const v(p2 - p1); int const sqlen = v.x() * v.x() + v.y() * v.y(); return sqlen > m_maxSegmentSqLen; } QLineF SequentialColumnProcessor::approximateWithLine(std::vector* dbg_segments) const { using namespace boost::lambda; size_t const 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) { QPoint const pt1(m_path[i - 1]); QPoint const pt2(m_path[i]); assert(pt2.y() > pt1.y()); Vec2d vec(pt2 - pt1); if (fabs(vec[0]) > fabs(vec[1])) { // We don't want segments that are more horizontal than vertical. continue; } vec /= sqrt(vec.squaredNorm()); segments.push_back(Segment(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. size_t const 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. int const 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)); } QLineF const 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; } QLineF SequentialColumnProcessor::interpolateSegments(std::vector const& segments) const { assert(!segments.empty()); // First, interpolate the angle of segments. Vec2d accum_vec; double accum_weight = 0; BOOST_FOREACH(Segment const& seg, segments) { double const weight = 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. BOOST_FOREACH(QPoint const& pt, m_path) { if (normal.dot(pt - line.p1()) < 0) { line.setP1(pt); line.setP2(line.p1() + accum_vec); } } return line; } QImage SequentialColumnProcessor::visualizeEnvelope(QImage const& 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()) { std::vector const polyline(m_path.begin(), m_path.end()); painter.drawPolyline(&polyline[0], polyline.size()); } painter.setPen(Qt::NoPen); painter.setBrush(QColor(Qt::blue)); painter.setOpacity(0.7); QRectF rect(0, 0, 9, 9); BOOST_FOREACH(QPoint pt, m_path) { rect.moveCenter(pt + QPointF(0.5, 0.5)); painter.drawEllipse(rect); } return canvas; } QImage visualizeSegments(QImage const& background, std::vector const& segments) { 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); BOOST_FOREACH(Segment const& seg, segments) { painter.drawLine(seg.line); } return canvas; } // For every column in the image, store the top-most and bottom-most black pixel. void calculateVertRanges(imageproc::BinaryImage const& image, std::vector& ranges) { int const width = image.width(); int const height = image.height(); uint32_t const* image_data = image.data(); int const image_stride = image.wordsPerLine(); uint32_t const msb = uint32_t(1) << 31; ranges.reserve(width); for (int x = 0; x < width; ++x) { ranges.push_back(VertRange()); VertRange& range = ranges.back(); uint32_t const mask = msb >> (x & 31); uint32_t const* 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; } } } } QLineF extendLine(QLineF const& line, int height) { QPointF top_intersection; QPointF bottom_intersection; QLineF const top_line(QPointF(0, 0), QPointF(1, 0)); QLineF const bottom_line(QPointF(0, height), QPointF(1, height)); line.intersect(top_line, &top_intersection); line.intersect(bottom_line, &bottom_intersection); return QLineF(top_intersection, bottom_intersection); } } // anonymous namespace std::pair detectVertContentBounds( imageproc::BinaryImage const& image, DebugImages* dbg) { int const width = image.width(); int const 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) { QImage const 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 : 0; 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; } } // namespace dewarping scantailor-RELEASE_0_9_12_2/dewarping/DetectVertContentBounds.h000066400000000000000000000030601271170121200243660ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DEWARPING_DETECT_VERT_CONTENT_BOUNDS_H_ #define DEWARPING_DETECT_VERT_CONTENT_BOUNDS_H_ #include #include class DebugImages; namespace imageproc { class BinaryImage; } namespace dewarping { /** * \brief Detect the left and right content boundaries. * * \param image The image to work on. * \return A pair of left, right boundaries. These lines will span * from top to bottom of the image, and may be partially or even * completely outside of its bounds. * * \note This function assumes a clean image, that is no clutter * or speckles, at least not outside of the content area. */ std::pair detectVertContentBounds( imageproc::BinaryImage const& image, DebugImages* dbg); } // namespace dewarping #endif scantailor-RELEASE_0_9_12_2/dewarping/DewarpingPointMapper.cpp000066400000000000000000000052471271170121200242520ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "DewarpingPointMapper.h" #include "DistortionModel.h" #include #include #include namespace dewarping { DewarpingPointMapper::DewarpingPointMapper( DistortionModel const& distortion_model, double depth_perception, QTransform const& distortion_model_to_output, QRect const& output_content_rect) : m_dewarper( CylindricalSurfaceDewarper( distortion_model.topCurve().polyline(), distortion_model.bottomCurve().polyline(), depth_perception ) ) { // Model domain is a rectangle in output image coordinates that // will be mapped to our curved quadrilateral. QRect const 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. 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; } QPointF DewarpingPointMapper::mapToDewarpedSpace(QPointF const& warped_pt) const { QPointF const crv_pt(m_dewarper.mapToDewarpedSpace(warped_pt)); double const dewarped_x = crv_pt.x() * m_modelXScaleFromNormalized + m_modelDomainLeft; double const dewarped_y = crv_pt.y() * m_modelYScaleFromNormalized + m_modelDomainTop; return QPointF(dewarped_x, dewarped_y); } QPointF DewarpingPointMapper::mapToWarpedSpace(QPointF const& dewarped_pt) const { double const crv_x = (dewarped_pt.x() - m_modelDomainLeft) * m_modelXScaleToNormalized; double const crv_y = (dewarped_pt.y() - m_modelDomainTop) * m_modelYScaleToNormalized; return m_dewarper.mapToWarpedSpace(QPointF(crv_x, crv_y)); } } // namespace dewarping scantailor-RELEASE_0_9_12_2/dewarping/DewarpingPointMapper.h000066400000000000000000000037061271170121200237150ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DEWARPING_DEWARPING_POINT_MAPPER_H_ #define DEWARPING_DEWARPING_POINT_MAPPER_H_ #include "CylindricalSurfaceDewarper.h" class QRect; class QTransform; namespace dewarping { class DistortionModel; class DewarpingPointMapper { public: DewarpingPointMapper( dewarping::DistortionModel const& distortion_model, double depth_perception, QTransform const& distortion_model_to_output, QRect const& output_content_rect); /** * Similar to CylindricalSurfaceDewarper::mapToDewarpedSpace(), * except it maps to dewarped image coordinates rather than * to normalized dewarped coordinates. */ QPointF mapToDewarpedSpace(QPointF const& warped_pt) const; /** * Similar to CylindricalSurfaceDewarper::mapToWarpedSpace(), * except it maps from dewarped image coordinates rather than * from normalized dewarped coordinates. */ QPointF mapToWarpedSpace(QPointF const& 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; }; } // namespace dewarping #endif scantailor-RELEASE_0_9_12_2/dewarping/DistortionModel.cpp000066400000000000000000000113301271170121200232600ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "DistortionModel.h" #include "CylindricalSurfaceDewarper.h" #include "NumericTraits.h" #include "VecNT.h" #include #include #include #include #include #include #ifndef Q_MOC_RUN #include #endif #include namespace dewarping { DistortionModel::DistortionModel() { } DistortionModel::DistortionModel(QDomElement const& el) : m_topCurve(el.namedItem("top-curve").toElement()), m_bottomCurve(el.namedItem("bottom-curve").toElement()) { } QDomElement DistortionModel::toXml(QDomDocument& doc, QString const& name) const { 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")); return el; } bool DistortionModel::isValid() const { if (!m_topCurve.isValid() || !m_bottomCurve.isValid()) { return false; } Vec2d const 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(); for (int i = 0; i < 4; ++i) { Vec2d const cur(poly[i]); Vec2d const prev(poly[(i + 3) & 3]); Vec2d const next(poly[(i + 1) & 3]); Vec2d prev_normal(cur - prev); std::swap(prev_normal[0], prev_normal[1]); prev_normal[0] = -prev_normal[0]; double const dot = prev_normal.dot(next - cur); if (dot < min_dot) { min_dot = dot; } if (dot > max_dot) { max_dot = dot; } } if (min_dot * max_dot <= 0) { // Not convex. return false; } if (fabs(min_dot) < 0.01 || fabs(max_dot) < 0.01) { // Too close - possible problems with calculating homography. return false; } return true; } bool DistortionModel::matches(DistortionModel const& other) const { bool const this_valid = isValid(); bool const 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; } return true; } QRectF DistortionModel::modelDomain( CylindricalSurfaceDewarper const& dewarper, QTransform const& to_output, QRectF const& output_content_rect) const { 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. double const vert_scale = 1.0 / dewarper.directrixArcLength(); // When scaling model_domain, we want the following point to remain where it is. QPointF const scale_origin(output_content_rect.center()); double const new_upper_part = (scale_origin.y() - model_domain.top()) * vert_scale; double const new_height = model_domain.height() * vert_scale; model_domain.setTop(scale_origin.y() - new_upper_part); model_domain.setHeight(new_height); return model_domain; } QRectF DistortionModel::boundingBox(QTransform const& transform) const { double top = NumericTraits::max(); double left = top; double bottom = NumericTraits::min(); double right = bottom; BOOST_FOREACH(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()); } BOOST_FOREACH(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 scantailor-RELEASE_0_9_12_2/dewarping/DistortionModel.h000066400000000000000000000046471271170121200227420ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DEWARPING_DISTORTION_MODEL_H_ #define DEWARPING_DISTORTION_MODEL_H_ #include "Curve.h" class QDomDocument; class QDomElement; class QString; class QRectF; class QTransform; namespace dewarping { class CylindricalSurfaceDewarper; class DistortionModel { public: /** * \brief Constructs a null distortion model. */ DistortionModel(); explicit DistortionModel(QDomElement const& el); QDomElement toXml(QDomDocument& doc, QString const& 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(Curve const& curve) { m_topCurve = curve; } void setBottomCurve(Curve const& curve) { m_bottomCurve = curve; } Curve const& topCurve() const { return m_topCurve; } Curve const& bottomCurve() const { return m_bottomCurve; } bool matches(DistortionModel const& other) const; /** * Model domain is a rectangle in output image coordinates that * will be mapped to our curved quadrilateral. */ QRectF modelDomain( CylindricalSurfaceDewarper const& dewarper, QTransform const& to_output, QRectF const& 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(QTransform const& transform) const; Curve m_topCurve; Curve m_bottomCurve; }; } // namespace dewarping #endif scantailor-RELEASE_0_9_12_2/dewarping/DistortionModelBuilder.cpp000066400000000000000000000520541271170121200245770ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "DistortionModelBuilder.h" #include "DistortionModel.h" #include "CylindricalSurfaceDewarper.h" #include "LineBoundedByRect.h" #include "ToLineProjector.h" #include "SidesOfLine.h" #include "XSpline.h" #include "DebugImages.h" #include "VecNT.h" #include "MatMNT.h" #include "MatrixCalc.h" #include "spfit/FrenetFrame.h" #include "spfit/SqDistApproximant.h" #include "spfit/PolylineModelShape.h" #include "spfit/SplineFitter.h" #include "spfit/LinearForceBalancer.h" #include "spfit/ConstraintSet.h" #include #include #include #include #include #include #ifndef Q_MOC_RUN #include #endif #include #include #include #include #include #include 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(std::vector const& trimmed_polyline, XSpline const& extended_spline, double ord) : trimmedPolyline(trimmed_polyline), extendedPolyline(extended_spline.toPolyline()), extendedSpline(extended_spline), order(ord) {} bool operator<(TracedCurve const& rhs) const { return order < rhs.order; } }; struct DistortionModelBuilder::RansacModel { TracedCurve const* topCurve; TracedCurve const* bottomCurve; double totalError; RansacModel() : topCurve(0), bottomCurve(0), totalError(NumericTraits::max()) {} bool isValid() const { return topCurve && bottomCurve; } }; class DistortionModelBuilder::RansacAlgo { public: RansacAlgo(std::vector const& all_curves) : m_rAllCurves(all_curves) {} void buildAndAssessModel( TracedCurve const* top_curve, TracedCurve const* bottom_curve); RansacModel& bestModel() { return m_bestModel; } RansacModel const& bestModel() const { return m_bestModel; } private: double calcReferenceHeight( CylindricalSurfaceDewarper const& dewarper, QPointF const& loc); RansacModel m_bestModel; std::vector const& m_rAllCurves; }; class DistortionModelBuilder::BadCurve : public std::exception { public: virtual char const* what() const throw() { return "Bad curve"; } }; DistortionModelBuilder::DistortionModelBuilder(Vec2d const& down_direction) : m_downDirection(down_direction), m_rightDirection(down_direction[1], -down_direction[0]) { assert(down_direction.squaredNorm() > 0); } void DistortionModelBuilder::setVerticalBounds(QLineF const& bound1, QLineF const& bound2) { m_bound1 = bound1; m_bound2 = bound2; } std::pair DistortionModelBuilder::verticalBounds() const { return std::pair(m_bound1, m_bound2); } void DistortionModelBuilder::addHorizontalCurve(std::vector const& polyline) { if (polyline.size() < 2) { return; } if (Vec2d(polyline.back() - polyline.front()).dot(m_rightDirection) > 0) { m_ltrPolylines.push_back(polyline); } else { m_ltrPolylines.push_back(std::vector(polyline.rbegin(), polyline.rend())); } } void DistortionModelBuilder::transform(QTransform const& xform) { assert(xform.isAffine()); QLineF const down_line(xform.map(QLineF(QPointF(0, 0), m_downDirection))); QLineF const 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); BOOST_FOREACH(std::vector& polyline, m_ltrPolylines) { BOOST_FOREACH(QPointF& pt, polyline) { pt = xform.map(pt); } } } DistortionModel DistortionModelBuilder::tryBuildModel(DebugImages* dbg, QImage const* dbg_background) const { int num_curves = 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); BOOST_FOREACH(std::vector const& polyline, m_ltrPolylines) { try { ordered_curves.push_back(polylineToCurve(polyline)); } catch (BadCurve const&) { // Just skip it. } } num_curves = 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::TracedCurve DistortionModelBuilder::polylineToCurve(std::vector const& polyline) const { std::pair const bounds(frontBackBounds(polyline)); // Trim the polyline if necessary. std::vector const trimmed_polyline(maybeTrimPolyline(polyline, bounds)); Vec2d const centroid(this->centroid(polyline)); // Fit the polyline to a spline, extending it to bounds at the same time. XSpline const extended_spline(fitExtendedSpline(trimmed_polyline, centroid, bounds)); double const order = centroid.dot(m_downDirection); return TracedCurve(trimmed_polyline, extended_spline, order); } Vec2d DistortionModelBuilder::centroid(std::vector const& polyline) { int const num_points = 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) { QLineF const segment(polyline[i - 1], polyline[i]); Vec2d const center(0.5 * (segment.p1() + segment.p2())); double const weight = segment.length(); accum += center * weight; total_weight += weight; } if (total_weight < 1e-06) { return Vec2d(polyline.front()); } else { return accum / total_weight; } } /** * \brief Returns bounds ordered according to the direction of \p polyline. * * The first and second bounds will correspond to polyline.front() and polyline.back() * respectively. */ std::pair DistortionModelBuilder::frontBackBounds(std::vector const& polyline) const { assert(!polyline.empty()); ToLineProjector const proj1(m_bound1); ToLineProjector const 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( std::vector const& polyline, std::pair const& bounds) { 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()); } bool DistortionModelBuilder::maybeTrimFront(std::deque& polyline, QLineF const& bound) { 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(); } intersectFront(polyline, bound); return true; } bool DistortionModelBuilder::maybeTrimBack(std::deque& polyline, QLineF const& bound) { 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(); } intersectBack(polyline, bound); return true; } void DistortionModelBuilder::intersectFront( std::deque& polyline, QLineF const& bound) { assert(polyline.size() >= 2); QLineF const 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, QLineF const& bound) { assert(polyline.size() >= 2); QLineF const back_segment(polyline[polyline.size() - 2], polyline.back()); QPointF intersection; if (bound.intersect(back_segment, &intersection) != QLineF::NoIntersection) { polyline.back() = intersection; } } XSpline DistortionModelBuilder::fitExtendedSpline( std::vector const& polyline, Vec2d const& centroid, std::pair const& bounds) { using namespace spfit; QLineF const chord(polyline.front(), polyline.back()); XSpline spline; int const initial_spline_points = 5; spline.appendControlPoint(chord.pointAt(0), 1); for (int i = 1; i < initial_spline_points - 1; ++i) { double const 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: ModelShape(std::vector const& polyline) : PolylineModelShape(polyline) {} protected: virtual SqDistApproximant calcApproximant( QPointF const& pt, FittableSpline::SampleFlags sample_flags, Flags polyline_flags, FrenetFrame const& frenet_frame, double signed_curvature) const { 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); } } }; ModelShape const model_shape(polyline); SplineFitter fitter(&spline); 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); // 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()); // 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)); } 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() ); OptimizationResult const 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; } /*============================== RansacAlgo ============================*/ void DistortionModelBuilder::RansacAlgo::buildAndAssessModel( TracedCurve const* top_curve, TracedCurve const* bottom_curve) try { DistortionModel model; model.setTopCurve(Curve(top_curve->extendedPolyline)); model.setBottomCurve(Curve(bottom_curve->extendedPolyline)); if (!model.isValid()) { return; } double const depth_perception = 2.0; // Doesn't matter much here. CylindricalSurfaceDewarper const dewarper( top_curve->extendedPolyline, bottom_curve->extendedPolyline, depth_perception ); double error = 0; BOOST_FOREACH(TracedCurve const& curve, m_rAllCurves) { size_t const polyline_size = curve.trimmedPolyline.size(); double const 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); BOOST_FOREACH(QPointF const& warped_pt, curve.trimmedPolyline) { // TODO: add another signature with hint for efficiency. QPointF const 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, 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], polyline_size, 1) - mc(&A[0], polyline_size, 2) *((mc(&At[0], 2, polyline_size)*mc(&A[0], polyline_size, 2)).inv() *(mc(&At[0], 2, polyline_size)*mc(&B[0], 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 += fabs(errvec[i]) * r_reference_height; } // Penalty for not being straight. error += sum_abs_err / polyline_size; // TODO: penalty for not being horizontal. } catch (std::runtime_error const&) { // Strictly vertical line? error += 1000; } } if (error < m_bestModel.totalError) { m_bestModel.topCurve = top_curve; m_bestModel.bottomCurve = bottom_curve; m_bestModel.totalError = error; } } catch (std::runtime_error const&) { // Probably CylindricalSurfaceDewarper didn't like something. } #if 0 double DistortionModelBuilder::RansacAlgo::calcReferenceHeight( CylindricalSurfaceDewarper const& dewarper, QPointF const& loc) { // TODO: ideally, we would use the counterpart of CylindricalSurfaceDewarper::mapGeneratrix(), // that would map it the other way, and which doesn't currently exist. QPointF const pt1(dewarper.mapToDewarpedSpace(loc + QPointF(0.0, -10))); QPointF const pt2(dewarper.mapToDewarpedSpace(loc + QPointF(0.0, 10))); return fabs(pt1.y() - pt2.y()); } #endif QImage DistortionModelBuilder::visualizeTrimmedPolylines( QImage const& background, std::vector const& curves) const { QImage canvas(background.convertToFormat(QImage::Format_RGB32)); QPainter painter(&canvas); painter.setRenderHint(QPainter::Antialiasing); int const width = background.width(); int const height = background.height(); double const stroke_width = 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); BOOST_FOREACH(TracedCurve const& curve, curves) { if (!curve.trimmedPolyline.empty()) { painter.drawPolyline(&curve.trimmedPolyline[0], curve.trimmedPolyline.size()); } } // Draw polyline knots. QBrush knot_brush(Qt::magenta); painter.setBrush(knot_brush); painter.setPen(Qt::NoPen); BOOST_FOREACH(TracedCurve const& curve, curves) { QRectF rect(0, 0, stroke_width, stroke_width); BOOST_FOREACH(QPointF const& knot, curve.trimmedPolyline) { rect.moveCenter(knot); painter.drawEllipse(rect); } } return canvas; } QImage DistortionModelBuilder::visualizeModel( QImage const& background, std::vector const& curves, RansacModel const& model) const { QImage canvas(background.convertToFormat(QImage::Format_RGB32)); QPainter painter(&canvas); painter.setRenderHint(QPainter::Antialiasing); int const width = background.width(); int const height = background.height(); double const stroke_width = 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)); BOOST_FOREACH(TracedCurve const& 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); } size_t const size = curve.extendedPolyline.size(); painter.drawPolyline(&curve.extendedPolyline[0], curve.extendedPolyline.size()); Vec2d const main_direction(curve.extendedPolyline.back() - curve.extendedPolyline.front()); std::list > reverse_segments; for (size_t i = 1; i < size; ++i) { Vec2d const 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(i); } else { // Start a new sequence. reverse_segments.push_back(std::vector()); std::vector& sequence = reverse_segments.back(); sequence.push_back(i - 1); sequence.push_back(i); } } QVector polyline; if (!reverse_segments.empty()) { painter.setPen(reverse_segments_pen); BOOST_FOREACH(std::vector const& sequence, reverse_segments) { assert(!sequence.empty()); polyline.clear(); BOOST_FOREACH(int idx, sequence) { polyline << curve.extendedPolyline[idx]; } painter.drawPolyline(polyline); } } int const 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) { double const 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; } } // namespace dewarping scantailor-RELEASE_0_9_12_2/dewarping/DistortionModelBuilder.h000066400000000000000000000102351271170121200242370ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DEWARPING_DISTORTION_MODEL_BUILDER_H_ #define DEWARPING_DISTORTION_MODEL_BUILDER_H_ #include "VecNT.h" #include #include #include #include #include #include class QImage; class DebugImages; class XSpline; 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. */ DistortionModelBuilder(Vec2d const& 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(QLineF const& bound1, QLineF const& 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(std::vector const& polyline); /** * \brief Applies an affine transformation to the internal representation. */ void transform(QTransform const& 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 = 0, QImage const* dbg_background = 0) const; private: struct TracedCurve; struct RansacModel; class RansacAlgo; class BadCurve; TracedCurve polylineToCurve(std::vector const& polyline) const; static Vec2d centroid(std::vector const& polyline); std::pair frontBackBounds(std::vector const& polyline) const; static std::vector maybeTrimPolyline( std::vector const& polyline, std::pair const& bounds); static bool maybeTrimFront(std::deque& polyline, QLineF const& bound); static bool maybeTrimBack(std::deque& polyline, QLineF const& bound); static void intersectFront(std::deque& polyline, QLineF const& bound); static void intersectBack(std::deque& polyline, QLineF const& bound); static XSpline fitExtendedSpline( std::vector const& polyline, Vec2d const& centroid, std::pair const& bounds); QImage visualizeTrimmedPolylines( QImage const& background, std::vector const& curves) const; QImage visualizeModel(QImage const& background, std::vector const& curves, RansacModel const& 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 scantailor-RELEASE_0_9_12_2/dewarping/RasterDewarper.cpp000066400000000000000000000461351271170121200231060ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "RasterDewarper.h" #include "CylindricalSurfaceDewarper.h" #include "HomographicTransform.h" #include "VecNT.h" #include "imageproc/ColorMixer.h" #include "imageproc/GrayImage.h" #include #include #include #include #include #include #include #define INTERP_NONE 0 #define INTERP_BILLINEAR 1 #define INTERP_AREA_MAPPING 2 #define INTERPOLATION_METHOD INTERP_AREA_MAPPING using namespace imageproc; namespace dewarping { namespace { #if INTERPOLATION_METHOD == INTERP_NONE template void dewarpGeneric( PixelType const* const src_data, QSize const src_size, int const src_stride, PixelType* const dst_data, QSize const dst_size, int const dst_stride, CylindricalSurfaceDewarper const& distortion_model, QRectF const& model_domain, PixelType const bg_color) { int const src_width = src_size.width(); int const src_height = src_size.height(); int const dst_width = dst_size.width(); int const dst_height = dst_size.height(); CylindricalSurfaceDewarper::State state; double const model_domain_left = model_domain.left(); double const model_x_scale = 1.0 / (model_domain.right() - model_domain.left()); float const model_domain_top = model_domain.top(); float const model_y_scale = 1.0 / (model_domain.bottom() - model_domain.top()); for (int dst_x = 0; dst_x < dst_width; ++dst_x) { double const model_x = (dst_x - model_domain_left) * model_x_scale; CylindricalSurfaceDewarper::Generatrix const generatrix( distortion_model.mapGeneratrix(model_x, state) ); HomographicTransform<1, float> const homog(generatrix.pln2img.mat()); Vec2f const origin(generatrix.imgLine.p1()); Vec2f const vec(generatrix.imgLine.p2() - generatrix.imgLine.p1()); for (int dst_y = 0; dst_y < dst_height; ++dst_y) { float const model_y = (float(dst_y) - model_domain_top) * model_y_scale; Vec2f const src_pt(origin + vec * homog(model_y)); int const src_x = qRound(src_pt[0]); int const 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]; } } } #elif INTERPOLATION_METHOD == INTERP_BILLINEAR template void dewarpGeneric( PixelType const* const src_data, QSize const src_size, int const src_stride, PixelType* const dst_data, QSize const dst_size, int const dst_stride, CylindricalSurfaceDewarper const& distortion_model, QRectF const& model_domain, PixelType const bg_color) { int const src_width = src_size.width(); int const src_height = src_size.height(); int const dst_width = dst_size.width(); int const dst_height = dst_size.height(); CylindricalSurfaceDewarper::State state; double const model_domain_left = model_domain.left() - 0.5f; double const model_x_scale = 1.0 / (model_domain.right() - model_domain.left()); float const model_domain_top = model_domain.top() - 0.5f; float const model_y_scale = 1.0 / (model_domain.bottom() - model_domain.top()); for (int dst_x = 0; dst_x < dst_width; ++dst_x) { double const model_x = (dst_x - model_domain_left) * model_x_scale; CylindricalSurfaceDewarper::Generatrix const generatrix( distortion_model.mapGeneratrix(model_x, state) ); HomographicTransform<1, float> const homog(generatrix.pln2img.mat()); Vec2f const origin(generatrix.imgLine.p1()); Vec2f const vec(generatrix.imgLine.p2() - generatrix.imgLine.p1()); for (int dst_y = 0; dst_y < dst_height; ++dst_y) { float const model_y = ((float)dst_y - model_domain_top) * model_y_scale; Vec2f const src_pt(origin + vec * homog(model_y)); int const src_x0 = (int)floor(src_pt[0] - 0.5f); int const src_y0 = (int)floor(src_pt[1] - 0.5f); int const src_x1 = src_x0 + 1; int const src_y1 = src_y0 + 1; float const x = src_pt[0] - src_x0; float const 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); } } } #elif INTERPOLATION_METHOD == INTERP_AREA_MAPPING template void areaMapGeneratrix( PixelType const* const src_data, QSize const src_size, int const src_stride, PixelType* p_dst, QSize const dst_size, int const dst_stride, PixelType const bg_color, std::vector const& prev_grid_column, std::vector const& next_grid_column) { int const sw = src_size.width(); int const sh = src_size.height(); int const dst_height = dst_size.height(); Vec2f const* src_left_points = &prev_grid_column[0]; Vec2f const* 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) { Vec2f const 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]; } } 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; } // Note: the code below is more or less the same as in transformGeneric() // in imageproc/Transform.cpp // Note that without using floor() and ceil() // we can't guarantee that src_bottom >= src_top // and src_right >= src_left. int src32_left = (int)floor(f_src32_left); int src32_right = (int)ceil(f_src32_right); int src32_top = (int)floor(f_src32_top); int src32_bottom = (int)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; } /* * 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) { unsigned const top_fraction = 32 - (src32_top & 31); unsigned const hor_fraction = src32_right - src32_left; background_area += top_fraction * hor_fraction; unsigned const full_pixels_ver = -1 - src_top; background_area += hor_fraction * (full_pixels_ver << 5); src_top = 0; src32_top = 0; } if (src_bottom >= sh) { unsigned const bottom_fraction = src32_bottom - (src_bottom << 5); unsigned const hor_fraction = src32_right - src32_left; background_area += bottom_fraction * hor_fraction; unsigned const 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) { unsigned const left_fraction = 32 - (src32_left & 31); unsigned const vert_fraction = src32_bottom - src32_top; background_area += left_fraction * vert_fraction; unsigned const full_pixels_hor = -1 - src_left; background_area += vert_fraction * (full_pixels_hor << 5); src_left = 0; src32_left = 0; } if (src_right >= sw) { unsigned const right_fraction = src32_right - (src_right << 5); unsigned const vert_fraction = src32_bottom - src32_top; background_area += right_fraction * vert_fraction; unsigned const 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); //} unsigned const left_fraction = 32 - (src32_left & 31); unsigned const top_fraction = 32 - (src32_top & 31); unsigned const right_fraction = src32_right - (src_right << 5); unsigned const 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)); unsigned const src_area = (src32_bottom - src32_top) * (src32_right - src32_left); if (src_area == 0) { *p_dst = bg_color; p_dst += dst_stride; continue; } PixelType const* 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 PixelType const 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 unsigned const vert_fraction = src32_bottom - src32_top; unsigned const left_area = vert_fraction * left_fraction; unsigned const middle_area = vert_fraction << 5; unsigned const 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 unsigned const hor_fraction = src32_right - src32_left; unsigned const top_area = hor_fraction * top_fraction; unsigned const middle_area = hor_fraction << 5; unsigned const 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 unsigned const top_area = top_fraction << 5; unsigned const bottom_area = bottom_fraction << 5; unsigned const left_area = left_fraction << 5; unsigned const right_area = right_fraction << 5; unsigned const topleft_area = top_fraction * left_fraction; unsigned const topright_area = top_fraction * right_fraction; unsigned const bottomleft_area = bottom_fraction * left_fraction; unsigned const 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); } *p_dst = mixer.mix(src_area + background_area); p_dst += dst_stride; } } template void dewarpGeneric( PixelType const* const src_data, QSize const src_size, int const src_stride, PixelType* const dst_data, QSize const dst_size, int const dst_stride, CylindricalSurfaceDewarper const& distortion_model, QRectF const& model_domain, PixelType const bg_color) { int const src_width = src_size.width(); int const src_height = src_size.height(); int const dst_width = dst_size.width(); int const dst_height = dst_size.height(); CylindricalSurfaceDewarper::State state; double const model_domain_left = model_domain.left(); double const model_x_scale = 1.0 / (model_domain.right() - model_domain.left()); float const model_domain_top = model_domain.top(); float const model_y_scale = 1.0 / (model_domain.bottom() - model_domain.top()); 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) { double const model_x = (dst_x - model_domain_left) * model_x_scale; CylindricalSurfaceDewarper::Generatrix const generatrix( distortion_model.mapGeneratrix(model_x, state) ); HomographicTransform<1, float> const homog(generatrix.pln2img.mat()); Vec2f const origin(generatrix.imgLine.p1()); Vec2f const vec(generatrix.imgLine.p2() - generatrix.imgLine.p1()); for (int dst_y = 0; dst_y <= dst_height; ++dst_y) { float const model_y = (float(dst_y) - model_domain_top) * model_y_scale; next_grid_column[dst_y] = origin + vec * homog(model_y); } 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); } } #endif // INTERPOLATION_METHOD #if INTERPOLATION_METHOD == INTERP_BILLINEAR typedef float MixingWeight; #else typedef unsigned MixingWeight; #endif QImage dewarpGrayscale( QImage const& src, QSize const& dst_size, CylindricalSurfaceDewarper const& distortion_model, QRectF const& model_domain, QColor const& bg_color) { GrayImage dst(dst_size); uint8_t const bg_sample = 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( QImage const& src, QSize const& dst_size, CylindricalSurfaceDewarper const& distortion_model, QRectF const& model_domain, QColor const& bg_color) { QImage dst(dst_size, QImage::Format_RGB32); dst.fill(bg_color.rgb()); dewarpGeneric, uint32_t>( (uint32_t const*)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; } QImage dewarpArgb( QImage const& src, QSize const& dst_size, CylindricalSurfaceDewarper const& distortion_model, QRectF const& model_domain, QColor const& bg_color) { QImage dst(dst_size, QImage::Format_ARGB32); dst.fill(bg_color.rgba()); dewarpGeneric, uint32_t>( (uint32_t const*)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; } } // anonymous namespace QImage RasterDewarper::dewarp( QImage const& src, QSize const& dst_size, CylindricalSurfaceDewarper const& distortion_model, QRectF const& model_domain, QColor const& 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 ); } } } // namespace dewarping scantailor-RELEASE_0_9_12_2/dewarping/RasterDewarper.h000066400000000000000000000023321271170121200225420ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DEWARPING_RASTER_DEWARPER_H_ #define DEWARPING_RASTER_DEWARPER_H_ class QImage; class QSize; class QRectF; class QColor; namespace dewarping { class CylindricalSurfaceDewarper; class RasterDewarper { public: static QImage dewarp( QImage const& src, QSize const& dst_size, CylindricalSurfaceDewarper const& distortion_model, QRectF const& model_domain, QColor const& background_color ); }; } // namespace dewarping #endif scantailor-RELEASE_0_9_12_2/dewarping/TextLineRefiner.cpp000066400000000000000000000633321271170121200232210ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "TextLineRefiner.h" #include "Dpi.h" #include "VecNT.h" #include "NumericTraits.h" #include "DebugImages.h" #include "imageproc/GrayImage.h" #include "imageproc/GaussBlur.h" #include "imageproc/Sobel.h" #ifndef Q_MOC_RUN #include #include #include #include #endif #include #include #include #include #include #include #include #include #include #include #include using namespace imageproc; namespace dewarping { class TextLineRefiner::SnakeLength { public: explicit SnakeLength(Snake const& snake); float totalLength() const { return m_totalLength; } float avgSegmentLength() const { return m_avgSegmentLength; } 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 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; }; struct TextLineRefiner::FrenetFrame { Vec2f unitTangent; Vec2f unitDownNormal; }; class TextLineRefiner::Optimizer { public: Optimizer(Snake const& snake, Vec2f const& unit_down_vec, float factor); bool thicknessAdjustment(Snake& snake, Grid const& gradient); bool tangentMovement(Snake& snake, Grid const& gradient); bool normalMovement(Snake& snake, Grid const& gradient); private: static float calcExternalEnergy( Grid const& gradient, SnakeNode const& node, Vec2f const down_normal); static float calcElasticityEnergy( SnakeNode const& node1, SnakeNode const& node2, float avg_dist); static float calcBendingEnergy( SnakeNode const& node, SnakeNode const& prev_node, SnakeNode const& prev_prev_node); static float const m_elasticityWeight; static float const m_bendingWeight; static float const m_topExternalWeight; static float const m_bottomExternalWeight; float const m_factor; SnakeLength m_snakeLength; std::vector m_frenetFrames; }; TextLineRefiner::TextLineRefiner( GrayImage const& image, Dpi const& dpi, Vec2f const& unit_down_vector) : m_image(image) , m_dpi(dpi) , m_unitDownVec(unit_down_vector) { } void TextLineRefiner::refine( std::list >& polylines, int const iterations, DebugImages* dbg) const { if (polylines.empty()) { return; } std::vector snakes; snakes.reserve(polylines.size()); // Convert from polylines to snakes. BOOST_FOREACH(std::vector const& 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); BOOST_FOREACH(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); BOOST_FOREACH(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; BOOST_FOREACH(std::vector& polyline, polylines) { ++i; Snake const& snake = snakes[i]; polyline.clear(); BOOST_FOREACH(SnakeNode const& node, snake.nodes) { polyline.push_back(node.center); } } } void TextLineRefiner::calcBlurredGradient( Grid& gradient, float h_sigma, float v_sigma) const { using namespace boost::lambda; float const 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( Grid const& gradient, Vec2f const& pos, float penalty_if_outside) { float const x_base = floor(pos[0]); float const y_base = floor(pos[1]); int const x_base_i = (int)x_base; int const 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; } float const x = pos[0] - x_base; float const y = pos[1] - y_base; float const x1 = 1.0f - x; float const y1 = 1.0f - y; int const stride = gradient.stride(); float const* 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; } TextLineRefiner::Snake TextLineRefiner::makeSnake(std::vector const& polyline, int const iterations) { float total_length = 0; size_t const polyline_size = polyline.size(); for (size_t i = 1; i < polyline_size; ++i) { total_length += sqrt(Vec2f(polyline[i] - polyline[i - 1]).squaredNorm()); } int const points_in_snake = 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) { Vec2f const base(polyline[i - 1]); Vec2f const vec((polyline[i] - base)); float const next_t = base_t + sqrt(vec.squaredNorm()); while (next_t >= next_insert_t) { float const 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; } void TextLineRefiner::calcFrenetFrames( std::vector& frenet_frames, Snake const& snake, SnakeLength const& snake_length, Vec2f const& unit_down_vec) { size_t const 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); float const 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); float const 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)); float const len = 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); float const 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. BOOST_FOREACH(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; } } } void TextLineRefiner::evolveSnake(Snake& snake, Grid const& gradient, OnConvergence const 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; } } } } QImage TextLineRefiner::visualizeGradient(Grid const& gradient) const { int const width = gradient.width(); int const height = gradient.height(); int const gradient_stride = gradient.stride(); // First let's find the maximum and minimum values. float min_value = NumericTraits::max(); float max_value = NumericTraits::min(); float const* gradient_line = gradient.data(); for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { float const 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); uint32_t* overlay_line = (uint32_t*)overlay.bits(); int const overlay_stride = overlay.bytesPerLine() / 4; gradient_line = gradient.data(); for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { float const value = gradient_line[x] * scale; int const magnitude = qBound(0, (int)(fabs(value) + 0.5), 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; } QImage TextLineRefiner::visualizeSnakes(std::vector const& snakes, Grid const* gradient) const { QImage canvas; if (gradient) { canvas = visualizeGradient(*gradient); } else { canvas = m_image.toQImage().convertToFormat(QImage::Format_ARGB32_Premultiplied); } QPainter painter(&canvas); painter.setRenderHint(QPainter::Antialiasing); 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 middle_pen(QColor(255, 0, 255)); middle_pen.setWidth(1.5); QBrush knot_brush(QColor(255, 255, 0, 180)); painter.setBrush(knot_brush); QRectF knot_rect(0, 0, 7, 7); std::vector frenet_frames; BOOST_FOREACH(Snake const& snake, snakes) { SnakeLength const snake_length(snake); calcFrenetFrames(frenet_frames, snake, snake_length, m_unitDownVec); QVector top_polyline; QVector middle_polyline; QVector bottom_polyline; size_t const num_nodes = snake.nodes.size(); for (size_t i = 0; i < num_nodes; ++i) { QPointF const mid(snake.nodes[i].center + QPointF(0.5, 0.5)); QPointF const top(mid - snake.nodes[i].ribHalfLength * frenet_frames[i].unitDownNormal); QPointF const 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); painter.setPen(bottom_pen); painter.drawPolyline(bottom_polyline); painter.setPen(middle_pen); painter.drawPolyline(middle_polyline); // Draw knots. painter.setPen(Qt::NoPen); BOOST_FOREACH(QPointF const& pt, middle_polyline) { knot_rect.moveCenter(pt); painter.drawEllipse(knot_rect); } } return canvas; } /*============================ SnakeLength =============================*/ TextLineRefiner::SnakeLength::SnakeLength(Snake const& snake) : m_integralLength(snake.nodes.size()) , m_totalLength() , m_rTotalLength() , m_avgSegmentLength() { size_t const num_nodes = snake.nodes.size(); float arc_length_accum = 0; for (size_t i = 1; i < num_nodes; ++i) { Vec2f const vec(snake.nodes[i].center - snake.nodes[i - 1].center); arc_length_accum += 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); } } /*=========================== Optimizer =============================*/ float const TextLineRefiner::Optimizer::m_elasticityWeight = 0.2f; float const TextLineRefiner::Optimizer::m_bendingWeight = 1.8f; float const TextLineRefiner::Optimizer::m_topExternalWeight = 1.0f; float const TextLineRefiner::Optimizer::m_bottomExternalWeight = 1.0f; TextLineRefiner::Optimizer::Optimizer( Snake const& snake, Vec2f const& unit_down_vec, float factor) : m_factor(factor) , m_snakeLength(snake) { calcFrenetFrames(m_frenetFrames, snake, m_snakeLength, unit_down_vec); } bool TextLineRefiner::Optimizer::thicknessAdjustment(Snake& snake, Grid const& gradient) { size_t const num_nodes = snake.nodes.size(); float const 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) { float const head_rib = snake.nodes.front().ribHalfLength + rib_adjustments[i]; if (head_rib <= std::numeric_limits::epsilon()) { continue; } for (int j = 0; j < NUM_RIB_ADJUSTMENTS; ++j) { float const 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) { float const t = m_snakeLength.arcLengthFractionAt(node_idx); float const rib = head_rib + t * (tail_rib - head_rib); Vec2f const 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; } } } float const head_rib = snake.nodes.front().ribHalfLength + rib_adjustments[best_i]; float const tail_rib = snake.nodes.back().ribHalfLength + rib_adjustments[best_j]; for (size_t node_idx = 0; node_idx < num_nodes; ++node_idx) { float const 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; } bool TextLineRefiner::Optimizer::tangentMovement(Snake& snake, Grid const& gradient) { size_t const num_nodes = snake.nodes.size(); if (num_nodes < 3) { return false; } float const 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(step_storage.size()); step_storage.push_back(Step()); 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) { Vec2f const initial_pos(snake.nodes[node_idx].center); float const rib = snake.nodes[node_idx].ribHalfLength; Vec2f const unit_tangent(m_frenetFrames[node_idx].unitTangent); Vec2f const down_normal(m_frenetFrames[node_idx].unitDownNormal); for (int i = 0; i < NUM_TANGENT_MOVEMENTS; ++i) { Step step; step.prevStepIdx = ~uint32_t(0); step.node.center = initial_pos + tangent_movements[i] * 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. BOOST_FOREACH(uint32_t prev_step_idx, paths) { Step const& prev_step = step_storage[prev_step_idx]; float const 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(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(); BOOST_FOREACH(uint32_t last_step_idx, paths) { Step const& 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 (int node_idx = num_nodes - 2; node_idx > 0; --node_idx) { assert(step_idx != ~uint32_t(0)); Step const& step = step_storage[step_idx]; SnakeNode& node = snake.nodes[node_idx]; float const 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(); } bool TextLineRefiner::Optimizer::normalMovement(Snake& snake, Grid const& gradient) { size_t const num_nodes = snake.nodes.size(); if (num_nodes < 3) { return false; } float const 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 (int i = 0; i < NUM_NORMAL_MOVEMENTS; ++i) { uint32_t const prev_step_idx = step_storage.size(); { // Movements of the first node. Vec2f const down_normal(m_frenetFrames[0].unitDownNormal); Step step; step.node.center = snake.nodes[0].center + normal_movements[i] * 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 (int j = 0; j < NUM_NORMAL_MOVEMENTS; ++j) { // Movements of the second node. Vec2f const down_normal(m_frenetFrames[1].unitDownNormal); Step step; step.node.center = snake.nodes[1].center + normal_movements[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(step_storage.size()); step_storage.push_back(step); } } for (size_t node_idx = 2; node_idx < num_nodes; ++node_idx) { SnakeNode const& node = snake.nodes[node_idx]; Vec2f const down_normal(m_frenetFrames[node_idx].unitDownNormal); for (int i = 0; i < NUM_NORMAL_MOVEMENTS; ++i) { Step step; step.prevStepIdx = ~uint32_t(0); step.node.center = node.center + normal_movements[i] * down_normal; step.node.ribHalfLength = node.ribHalfLength; step.pathCost = NumericTraits::max(); float const base_cost = calcExternalEnergy(gradient, step.node, down_normal); // Now find the best step for the previous node to combine with. BOOST_FOREACH(uint32_t prev_step_idx, paths) { Step const& prev_step = step_storage[prev_step_idx]; Step const& prev_prev_step = step_storage[prev_step.prevStepIdx]; float const 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(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(); BOOST_FOREACH(uint32_t last_step_idx, paths) { Step const& 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 (int node_idx = num_nodes - 1; node_idx >= 0; --node_idx) { assert(step_idx != ~uint32_t(0)); Step const& step = step_storage[step_idx]; SnakeNode& node = snake.nodes[node_idx]; float const 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(); } float TextLineRefiner::Optimizer::calcExternalEnergy( Grid const& gradient, SnakeNode const& node, Vec2f const down_normal) { Vec2f const top(node.center - node.ribHalfLength * down_normal); Vec2f const bottom(node.center + node.ribHalfLength * down_normal); float const top_grad = externalEnergyAt(gradient, top, 0.0f); float const 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. float const top_energy = m_topExternalWeight * std::min(top_grad, 0.0f); float const 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( SnakeNode const& node1, SnakeNode const& node2, float avg_dist) { Vec2f const vec(node1.center - node2.center); float const vec_len = sqrt(vec.squaredNorm()); if (vec_len < 1.0f) { return 1000.0f; // Penalty for moving too close to another node. } float const dist_diff = fabs(avg_dist - vec_len); return m_elasticityWeight * (dist_diff / avg_dist); } float TextLineRefiner::Optimizer::calcBendingEnergy( SnakeNode const& node, SnakeNode const& prev_node, SnakeNode const& prev_prev_node) { Vec2f const vec(node.center - prev_node.center); float const vec_len = sqrt(vec.squaredNorm()); if (vec_len < 1.0f) { return 1000.0f; // Penalty for moving too close to another node. } Vec2f const prev_vec(prev_node.center - prev_prev_node.center); float const prev_vec_len = sqrt(prev_vec.squaredNorm()); if (prev_vec_len < 1.0f) { return 1000.0f; // Penalty for moving too close to another node. } Vec2f const bend_vec(vec / vec_len - prev_vec / prev_vec_len); return m_bendingWeight * bend_vec.squaredNorm(); } } // namespace dewarping scantailor-RELEASE_0_9_12_2/dewarping/TextLineRefiner.h000066400000000000000000000050061271170121200226600ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DEWARPING_TEXT_LINE_REFINER_H_ #define DEWARPING_TEXT_LINE_REFINER_H_ #include "Grid.h" #include "VecNT.h" #include "Dpi.h" #include "imageproc/GrayImage.h" #include #include #include #include #include class Dpi; class DebugImages; class QImage; namespace dewarping { class TextLineRefiner { public: TextLineRefiner( imageproc::GrayImage const& image, Dpi const& dpi, Vec2f const& unit_down_vector); void refine(std::list >& polylines, int iterations, DebugImages* dbg) const; private: enum OnConvergence { ON_CONVERGENCE_STOP, ON_CONVERGENCE_GO_FINER }; class SnakeLength; struct FrenetFrame; class Optimizer; struct SnakeNode { Vec2f center; float ribHalfLength; }; struct Snake { std::vector nodes; int iterationsRemaining; Snake() : iterationsRemaining(0) {} }; struct Step { SnakeNode node; uint32_t prevStepIdx; float pathCost; }; void calcBlurredGradient(Grid& gradient, float h_sigma, float v_sigma) const; static float externalEnergyAt( Grid const& gradient, Vec2f const& pos, float penalty_if_outside); static Snake makeSnake(std::vector const& polyline, int iterations); static void calcFrenetFrames( std::vector& frenet_frames, Snake const& snake, SnakeLength const& snake_length, Vec2f const& unit_down_vec); void evolveSnake(Snake& snake, Grid const& gradient, OnConvergence on_convergence) const; QImage visualizeGradient(Grid const& gradient) const; QImage visualizeSnakes(std::vector const& snakes, Grid const* gradient = 0) const; imageproc::GrayImage m_image; Dpi m_dpi; Vec2f m_unitDownVec; }; } // namespace dewarping #endif scantailor-RELEASE_0_9_12_2/dewarping/TextLineTracer.cpp000066400000000000000000000503711271170121200230460ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "TextLineTracer.h" #include "TextLineRefiner.h" #include "DetectVertContentBounds.h" #include "TowardsLineTracer.h" #include "GridLineTraverser.h" #include "Dpi.h" #include "TaskStatus.h" #include "DebugImages.h" #include "NumericTraits.h" #include "VecNT.h" #include "Grid.h" #include "SidesOfLine.h" #include "ToLineProjector.h" #include "LineBoundedByRect.h" #include "DistortionModelBuilder.h" #include "DistortionModel.h" #include "Curve.h" #include "imageproc/BinaryImage.h" #include "imageproc/BinaryThreshold.h" #include "imageproc/Binarize.h" #include "imageproc/Grayscale.h" #include "imageproc/GrayImage.h" #include "imageproc/Scale.h" #include "imageproc/Constants.h" #include "imageproc/GaussBlur.h" #include "imageproc/Sobel.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 #include #include #include #ifndef Q_MOC_RUN #include #include #include #include #include #endif #include #include #include #include #include #include #include #include #include using namespace imageproc; namespace dewarping { void TextLineTracer::trace( GrayImage const& input, Dpi const& dpi, QRect const& content_rect, DistortionModelBuilder& output, TaskStatus const& status, DebugImages* dbg) { using namespace boost::lambda; GrayImage downscaled(downscale(input, dpi)); if (dbg) { dbg->add(downscaled, "downscaled"); } int const downscaled_width = downscaled.width(); int const downscaled_height = downscaled.height(); double const downscale_x_factor = double(downscaled_width) / input.width(); double const downscale_y_factor = double(downscaled_height) / input.height(); QTransform to_orig; to_orig.scale(1.0 / downscale_x_factor, 1.0 / downscale_y_factor); QRect const downscaled_content_rect(to_orig.inverted().mapRect(content_rect)); Dpi const 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 /= 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); BOOST_FOREACH(std::vector& polyline, polylines) { BOOST_FOREACH(QPointF& pt, polyline) { pt = to_orig.map(pt); } output.addHorizontalCurve(polyline); } } GrayImage TextLineTracer::downscale(GrayImage const& input, Dpi const& 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); } void TextLineTracer::sanitizeBinaryImage(BinaryImage& image, QRect const& 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); } /** * Returns false if the curve contains both significant convexities and concavities. */ bool TextLineTracer::isCurvatureConsistent(std::vector const& polyline) { size_t const 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. float const cos_threshold = cos((90.0f - 6.0f) * constants::DEG2RAD); float const 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) { Vec2f const next_segment(polyline[i + 1] - polyline[i]); float const next_segment_sqlen = next_segment.squaredNorm(); float cos_sq = 0; float const sqlen_mult = prev_normal_sqlen * next_segment_sqlen; if (sqlen_mult > std::numeric_limits::epsilon()) { float const dot = prev_normal.dot(next_segment); cos_sq = fabs(dot) * dot / sqlen_mult; } if (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_positive); } bool TextLineTracer::isInsideBounds( QPointF const& pt, QLineF const& left_bound, QLineF const& 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; } QPointF const 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; } QPointF const 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, QLineF const& left_bound, QLineF const& right_bound) { ToLineProjector const proj1(left_bound); ToLineProjector const proj2(right_bound); std::list >::iterator it(polylines.begin()); std::list >::iterator const end(polylines.end()); while (it != end) { assert(!it->empty()); QPointF const front(it->front()); QPointF const back(it->back()); double const front_proj_len = proj1.projectionDist(front); double const back_proj_len = proj2.projectionDist(back); double const 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, QLineF const& left_bound, QLineF const& right_bound) { std::list >::iterator it(polylines.begin()); std::list >::iterator const 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) { std::list >::iterator it(polylines.begin()); std::list >::iterator const end(polylines.end()); while (it != end) { if (!isCurvatureConsistent(*it)) { polylines.erase(it++); } else { ++it; } } } void TextLineTracer::extractTextLines( std::list >& out, imageproc::GrayImage const& image, std::pair const& bounds, DebugImages* dbg) { using namespace boost::lambda; int const width = image.width(); int const height = image.height(); QSize const size(image.size()); Vec2f const direction(calcAvgUnitVector(bounds)); Grid main_grid(image.width(), image.height(), 0); Grid aux_grid(image.width(), image.height(), 0); float const 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) ); float const 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. SEDM const 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. BOOST_FOREACH(QPoint const seed, seeds) { std::vector polyline; { TowardsLineTracer tracer(&sedm, &main_grid, bounds.first, seed); while (QPoint const* pt = tracer.trace(10.0f)) { polyline.push_back(*pt); } std::reverse(polyline.begin(), polyline.end()); } polyline.push_back(seed); { TowardsLineTracer tracer(&sedm, &main_grid, bounds.second, seed); while (QPoint const* pt = tracer.trace(10.0f)) { polyline.push_back(*pt); } } out.push_back(std::vector()); out.back().swap(polyline); } } Vec2f TextLineTracer::calcAvgUnitVector(std::pair const& bounds) { Vec2f v1(bounds.first.p2() - bounds.first.p1()); v1 /= sqrt(v1.squaredNorm()); Vec2f v2(bounds.second.p2() - bounds.second.p1()); v2 /= sqrt(v2.squaredNorm()); Vec2f v3(v1 + v2); v3 /= sqrt(v3.squaredNorm()); return v3; } BinaryImage TextLineTracer::closeWithObstacles( BinaryImage const& image, BinaryImage const& obstacles, QSize const& brick) { BinaryImage mask(closeBrick(image, brick)); rasterOp >(mask, obstacles); return seedFill(image, mask, CONN4); } void TextLineTracer::findMidLineSeeds(SEDM const& sedm, QLineF mid_line, std::vector& seeds) { lineBoundedByRect(mid_line, QRect(QPoint(0, 0), sedm.size()).adjusted(0, 0, -1, -1)); uint32_t const* sedm_data = sedm.data(); int const sedm_stride = sedm.stride(); QPoint prev_pt; int32_t prev_level = 0; int dir = 1; // Distance growing. GridLineTraverser traverser(mid_line); while (traverser.hasNext()) { QPoint const pt(traverser.next()); int32_t const 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(QLineF const& line1, QLineF const& line2) { QPointF intersection; if (line1.intersect(line2, &intersection) == QLineF::NoIntersection) { // Lines are parallel. QPointF const p1(line2.p1()); QPointF const p2(ToLineProjector(line1).projectionPoint(p1)); QPointF const origin(0.5 * (p1 + p2)); QPointF const 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 /= sqrt(v1.squaredNorm()); v2 /= sqrt(v2.squaredNorm()); return QLineF(intersection, intersection + 0.5 * (v1 + v2)); } } QImage TextLineTracer::visualizeVerticalBounds( QImage const& background, std::pair const& bounds) { 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); painter.drawLine(bounds.first); painter.drawLine(bounds.second); return canvas; } QImage TextLineTracer::visualizeGradient(QImage const& background, Grid const& grad) { int const width = grad.width(); int const height = grad.height(); int const grad_stride = grad.stride(); // First let's find the maximum and minimum values. float min_value = NumericTraits::max(); float max_value = NumericTraits::min(); float const* grad_line = grad.data(); for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { float const 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); uint32_t* overlay_line = (uint32_t*)overlay.bits(); int const overlay_stride = overlay.bytesPerLine() / 4; grad_line = grad.data(); for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { float const value = grad_line[x] * scale; int const magnitude = qBound(0, (int)(fabs(value) + 0.5), 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; } QImage TextLineTracer::visualizeMidLineSeeds( QImage const& background, BinaryImage const& overlay, std::pair bounds, QLineF mid_line, std::vector const& 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); BOOST_FOREACH(QPoint const pt, seeds) { rect.moveCenter(pt + QPointF(0.5, 0.5)); painter.drawEllipse(rect); } return canvas; } QImage TextLineTracer::visualizePolylines( QImage const& background, std::list > const& polylines, std::pair const* 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); BOOST_FOREACH(std::vector const& polyline, polylines) { if (!polyline.empty()) { painter.drawPolyline(&polyline[0], polyline.size()); } } if (vert_bounds) { painter.drawLine(vert_bounds->first); painter.drawLine(vert_bounds->second); } return canvas; } } // namespace dewarping scantailor-RELEASE_0_9_12_2/dewarping/TextLineTracer.h000066400000000000000000000064511271170121200225130ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #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 class Dpi; class QImage; class QSize; class QRect; class TaskStatus; class DebugImages; namespace imageproc { class BinaryImage; class GrayImage; class SEDM; } namespace dewarping { class DistortionModelBuilder; class TextLineTracer { public: static void trace( imageproc::GrayImage const& input, Dpi const& dpi, QRect const& content_rect, DistortionModelBuilder& output, TaskStatus const& status, DebugImages* dbg = 0); private: static imageproc::GrayImage downscale(imageproc::GrayImage const& input, Dpi const& dpi); static void sanitizeBinaryImage(imageproc::BinaryImage& image, QRect const& content_rect); static void extractTextLines( std::list >& out, imageproc::GrayImage const& image, std::pair const& bounds, DebugImages* dbg); static Vec2f calcAvgUnitVector(std::pair const& bounds); static imageproc::BinaryImage closeWithObstacles( imageproc::BinaryImage const& image, imageproc::BinaryImage const& obstacles, QSize const& brick); static QLineF calcMidLine(QLineF const& line1, QLineF const& line2); static void findMidLineSeeds( imageproc::SEDM const& sedm, QLineF mid_line, std::vector& seeds); static bool isCurvatureConsistent(std::vector const& polyline); static bool isInsideBounds( QPointF const& pt, QLineF const& left_bound, QLineF const& right_bound); static void filterShortCurves(std::list >& polylines, QLineF const& left_bound, QLineF const& right_bound); static void filterOutOfBoundsCurves(std::list >& polylines, QLineF const& left_bound, QLineF const& right_bound); static void filterEdgyCurves(std::list >& polylines); static QImage visualizeVerticalBounds( QImage const& background, std::pair const& bounds); static QImage visualizeGradient(QImage const& background, Grid const& grad); static QImage visualizeMidLineSeeds(QImage const& background, imageproc::BinaryImage const& overlay, std::pair bounds, QLineF mid_line, std::vector const& seeds); static QImage visualizePolylines( QImage const& background, std::list > const& polylines, std::pair const* vert_bounds = 0); }; } // namespace dewarping #endif scantailor-RELEASE_0_9_12_2/dewarping/TopBottomEdgeTracer.cpp000066400000000000000000001120411271170121200240170ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "TopBottomEdgeTracer.h" #include "DistortionModelBuilder.h" #include "TaskStatus.h" #include "DebugImages.h" #include "NumericTraits.h" #include "PriorityQueue.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 #include #include #ifndef Q_MOC_RUN #include #include #include #endif #include #include #include #include #include using namespace imageproc; namespace dewarping { struct TopBottomEdgeTracer::GridNode { private: static uint32_t const HEAP_IDX_BITS = 28; static uint32_t const PREV_NEIGHBOUR_BITS = 3; static uint32_t const PATH_CONTINUATION_BITS = 1; static uint32_t const HEAP_IDX_SHIFT = 0; static uint32_t const PREV_NEIGHBOUR_SHIFT = HEAP_IDX_SHIFT + HEAP_IDX_BITS; static uint32_t const PATH_CONTINUATION_SHIFT = PREV_NEIGHBOUR_SHIFT + PREV_NEIGHBOUR_BITS; static uint32_t const HEAP_IDX_MASK = ((uint32_t(1) << HEAP_IDX_BITS) - uint32_t(1)) << HEAP_IDX_SHIFT; static uint32_t const PREV_NEIGHBOUR_MASK = ((uint32_t(1) << PREV_NEIGHBOUR_BITS) - uint32_t(1)) << PREV_NEIGHBOUR_SHIFT; static uint32_t const PATH_CONTINUATION_MASK = ((uint32_t(1) << PATH_CONTINUATION_BITS) - uint32_t(1)) << PATH_CONTINUATION_SHIFT; public: static uint32_t const 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 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 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: PrioQueue(Grid& grid) : m_pData(grid.data()) {} bool higherThan(uint32_t lhs, uint32_t rhs) const { return m_pData[lhs].pathCost < m_pData[rhs].pathCost; } void setIndex(uint32_t grid_idx, size_t heap_idx) { m_pData[grid_idx].setHeapIdx(static_cast(heap_idx)); } void reposition(GridNode* node) { PriorityQueue::reposition(node->heapIdx()); } private: GridNode* const m_pData; }; struct TopBottomEdgeTracer::Step { Vec2f pt; uint32_t prevStepIdx; float pathCost; }; template float TopBottomEdgeTracer::interpolatedGridValue(Grid const& grid, Extractor extractor, Vec2f const pos, float default_value) { float const x_base = floor(pos[0]); float const y_base = floor(pos[1]); int const x_base_i = (int)x_base; int const 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; } float const x = pos[0] - x_base; float const y = pos[1] - y_base; float const x1 = 1.0f - x; float const y1 = 1.0f - y; int const stride = grid.stride(); GridNode const* 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; } void TopBottomEdgeTracer::trace( imageproc::GrayImage const& image, std::pair bounds, DistortionModelBuilder& output, TaskStatus const& 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); Vec2f const 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); Vec2f const dir_1st_to_2nd(directionFromPointToLine(bounds.first.pointAt(0.5), bounds.second)); propagateShortestPaths(dir_1st_to_2nd, queue, grid); std::vector const 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()); BOOST_FOREACH(QPoint endpoint, endpoints1) { snakes.push_back(pathToSnake(grid, endpoint)); Vec2f const dir(downTheHillDirection(downscaled.rect(), snakes.back(), avg_bounds_dir)); downTheHillSnake(snakes.back(), grid, dir); } if (dbg) { QImage const background(visualizeBlurredGradient(grid)); dbg->add(visualizeSnakes(background, snakes, bounds), "down_the_hill_snakes"); } BOOST_FOREACH(std::vector& snake, snakes) { Vec2f const dir(-downTheHillDirection(downscaled.rect(), snake, avg_bounds_dir)); upTheHillSnake(snake, grid, dir); } if (dbg) { QImage const background(visualizeGradient(grid)); dbg->add(visualizeSnakes(background, snakes, bounds), "up_the_hill_snakes"); } // Convert snakes back to the original coordinate system. QTransform const upscaling_xform(downscaling_xform.inverted()); BOOST_FOREACH(std::vector& snake, snakes) { BOOST_FOREACH(QPointF& pt, snake) { pt = upscaling_xform.map(pt); } output.addHorizontalCurve(snake); } } bool TopBottomEdgeTracer::intersectWithRect( std::pair& bounds, QRectF const& rect) { return lineBoundedByRect(bounds.first, rect) && lineBoundedByRect(bounds.second, rect); } void TopBottomEdgeTracer::forceSameDirection(std::pair& bounds) { QPointF const v1(bounds.first.p2() - bounds.first.p1()); QPointF const 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, imageproc::GrayImage const& image, Vec2f const& direction) { assert(grid.padding() == 1); int const width = grid.width(); int const height = grid.height(); int const grid_stride = grid.stride(); int const image_stride = image.stride(); uint8_t const* image_line = image.data(); GridNode* grid_line = grid.data(); // This ensures that partial derivatives never go beyond the [-1, 1] range. float const scale = 1.0f / (255.0f * 8.0f); // 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; 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) { Vec2f const grad_vec(grid_line[x].xGrad, grid_line[x].yGrad); grid_line[x].dirDeriv = grad_vec.dot(direction); assert(fabs(grid_line[x].dirDeriv) <= 1.0); } grid_line += grid_stride; } } void TopBottomEdgeTracer::horizontalSobelInPlace(Grid& grid) { assert(grid.padding() == 1); int const width = grid.width(); int const height = grid.height(); int const 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 const 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); int const width = grid.width(); int const height = grid.height(); int const 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) { float const cur = p_grid->yGrad; p_grid->yGrad = p_grid[grid_stride].yGrad - prev; prev = cur; p_grid += grid_stride; } } } Vec2f TopBottomEdgeTracer::calcAvgUnitVector(std::pair const& bounds) { Vec2f v1(bounds.first.p2() - bounds.first.p1()); v1 /= sqrt(v1.squaredNorm()); Vec2f v2(bounds.second.p2() - bounds.second.p1()); v2 /= sqrt(v2.squaredNorm()); Vec2f v3(v1 + v2); v3 /= sqrt(v3.squaredNorm()); return v3; } Vec2f TopBottomEdgeTracer::directionFromPointToLine(QPointF const& pt, QLineF const& line) { Vec2f vec(ToLineProjector(line).projectionVector(pt)); float const sqlen = vec.squaredNorm(); if (sqlen > 1e-5) { vec /= sqrt(sqlen); } return vec; } void TopBottomEdgeTracer::prepareForShortestPathsFrom( PrioQueue& queue, Grid& grid, QLineF const& from) { GridNode padding_node; padding_node.setupForPadding(); grid.initPadding(padding_node); int const width = grid.width(); int const height = grid.height(); int const 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; } GridLineTraverser traverser(from); while (traverser.hasNext()) { QPoint const pt(traverser.next()); // intersectWithRect() ensures that. assert(pt.x() >= 0 && pt.y() >= 0 && pt.x() < width && pt.y() < height); int const offset = pt.y() * stride + pt.x(); data[offset].pathCost = 0; queue.push(offset); } } void TopBottomEdgeTracer::propagateShortestPaths( Vec2f const& direction, PrioQueue& queue, Grid& grid) { GridNode* const data = grid.data(); int next_nbh_offsets[8]; int prev_nbh_indexes[8]; int const num_neighbours = initNeighbours(next_nbh_offsets, prev_nbh_indexes, grid.stride(), direction); while (!queue.empty()) { int const 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) { int const nbh_grid_idx = grid_idx + next_nbh_offsets[i]; GridNode* nbh_node = data + nbh_grid_idx; assert(fabs(node->dirDeriv) <= 1.0); float const new_cost = std::max(node->pathCost, 1.0f - 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); } } } } } int TopBottomEdgeTracer::initNeighbours( int* next_nbh_offsets, int* prev_nbh_indexes, int stride, Vec2f const& direction) { int const candidate_offsets[] = { -stride - 1, -stride, -stride + 1, -1, 1, stride - 1, stride, stride + 1 }; float const 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 int const opposite_nbh_map[] = { 7, 6, 5, 4, 3, 2, 1, 0 }; int out_idx = 0; for (int i = 0; i < 8; ++i) { Vec2f const 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; } namespace { struct Path { QPoint pt; float cost; Path(QPoint pt, float cost) : pt(pt), cost(cost) {} }; } std::vector TopBottomEdgeTracer::locateBestPathEndpoints(Grid const& grid, QLineF const& line) { int const width = grid.width(); int const height = grid.height(); int const stride = grid.stride(); GridNode const* const data = grid.data(); size_t const num_best_paths = 2; // Take N best paths. int const min_sqdist = 100*100; std::vector best_paths; GridLineTraverser traverser(line); while (traverser.hasNext()) { QPoint const pt(traverser.next()); // intersectWithRect() ensures that. assert(pt.x() >= 0 && pt.y() >= 0 && pt.x() < width && pt.y() < height); uint32_t const offset = pt.y() * stride + pt.x(); GridNode const* node = data + offset; // Find the closest path. Path* closest_path = 0; int closest_sqdist = std::numeric_limits::max(); BOOST_FOREACH(Path& path, best_paths) { QPoint const delta(path.pt - pt); int const 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.push_back(Path(pt, node->pathCost)); } else { // Find the one to kick out (if any). BOOST_FOREACH(Path& path, best_paths) { if (node->pathCost < path.cost) { path = Path(pt, node->pathCost); break; } } } } std::vector best_endpoints; BOOST_FOREACH(Path const& path, best_paths) { if (path.cost < 0.95f) { best_endpoints.push_back(path.pt); } } return best_endpoints; } std::vector TopBottomEdgeTracer::tracePathFromEndpoint(Grid const& grid, QPoint const& endpoint) { static int const dx[8] = { -1, 0, 1, -1, 1, -1, 0, 1 }; static int const dy[8] = { -1, -1, -1, 0, 0, 1, 1, 1 }; int const stride = grid.stride(); int const grid_offsets[8] = { -stride - 1, -stride, -stride + 1, - 1, + 1, +stride - 1, +stride, +stride + 1 }; GridNode const* const data = grid.data(); std::vector path; QPoint pt(endpoint); int grid_offset = pt.x() + pt.y() * stride; for (;;) { path.push_back(pt); GridNode const* node = data + grid_offset; if (!node->hasPathContinuation()) { break; } int const nbh_idx = node->prevNeighbourIdx(); grid_offset += grid_offsets[nbh_idx]; pt += QPoint(dx[nbh_idx], dy[nbh_idx]); } return path; } std::vector TopBottomEdgeTracer::pathToSnake(Grid const& grid, QPoint const& endpoint) { int const max_dist = 15; // Maximum distance between two snake knots. int const max_dist_sq = max_dist * max_dist; int const half_max_dist = max_dist / 2; int const half_max_dist_sq = half_max_dist * half_max_dist; static int const dx[8] = { -1, 0, 1, -1, 1, -1, 0, 1 }; static int const dy[8] = { -1, -1, -1, 0, 0, 1, 1, 1 }; int const stride = grid.stride(); int const grid_offsets[8] = { -stride - 1, -stride, -stride + 1, - 1, + 1, +stride - 1, +stride, +stride + 1 }; GridNode const* const data = grid.data(); std::vector snake; snake.push_back(endpoint); QPoint snake_tail(endpoint); QPoint pt(endpoint); int grid_offset = pt.x() + pt.y() * stride; for (;;) { QPoint const delta(pt - snake_tail); int const sqdist = delta.x() * delta.x() + delta.y() * delta.y(); GridNode const* node = data + grid_offset; if (!node->hasPathContinuation()) { if (sqdist >= half_max_dist_sq) { snake.push_back(pt); snake_tail = pt; } break; } if (sqdist >= max_dist_sq) { snake.push_back(pt); snake_tail = pt; } int const nbh_idx = node->prevNeighbourIdx(); grid_offset += grid_offsets[nbh_idx]; pt += QPoint(dx[nbh_idx], dy[nbh_idx]); } return snake; } void TopBottomEdgeTracer::gaussBlurGradient(Grid& grid) { 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 ); } Vec2f TopBottomEdgeTracer::downTheHillDirection( QRectF const& page_rect, std::vector const& snake, Vec2f const& bounds_dir) { assert(!snake.empty()); // Take the centroid of a snake. QPointF centroid; BOOST_FOREACH(QPointF const& 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. Vec2d const v1(line.p1() - centroid); Vec2d const v2(line.p2() - centroid); if (v1.squaredNorm() > v2.squaredNorm()) { return v1; } else { return v2; } } void TopBottomEdgeTracer::downTheHillSnake( std::vector& snake, Grid const& grid, Vec2f const dir) { using namespace boost::lambda; size_t const num_nodes = snake.size(); if (num_nodes <= 1) { return; } float avg_dist = 0; for (size_t i = 1; i < num_nodes; ++i) { Vec2f const vec(snake[i] - snake[i - 1]); avg_dist += sqrt(vec.squaredNorm()); } avg_dist /= num_nodes - 1; std::vector step_storage; Vec2f displacements[9]; int const num_displacements = initDisplacementVectors(displacements, dir); float const elasticity_weight = 0.6f; float const bending_weight = 8.0f; float const external_weight = 0.4f; float const 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) { Vec2f const pt(snake[node_idx]); float const 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; float const 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; BOOST_FOREACH(uint32_t prev_step_idx, paths) { Step const& prev_step = step_storage[prev_step_idx]; float cost = prev_step.pathCost + step.pathCost; Vec2f const vec(step.pt - prev_step.pt); float const vec_len = sqrt(vec.squaredNorm()); if (vec_len < segment_dist_threshold) { cost += 1000; } // Elasticity. float const dist_diff = 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) { Step const& 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]; float const prev_normal_len = sqrt(prev_normal.squaredNorm()); if (prev_normal_len < segment_dist_threshold) { cost += 1000; } else { float const cos = vec.dot(prev_normal) / (vec_len * prev_normal_len); //cost += 0.7 * 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(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(); BOOST_FOREACH(uint32_t last_step_idx, paths) { Step const& 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)) { Step const& step = step_storage[step_idx]; snake.push_back(step.pt); step_idx = step.prevStepIdx; } assert(num_nodes == snake.size()); } } void TopBottomEdgeTracer::upTheHillSnake( std::vector& snake, Grid const& grid, Vec2f const dir) { using namespace boost::lambda; size_t const num_nodes = snake.size(); if (num_nodes <= 1) { return; } float avg_dist = 0; for (size_t i = 1; i < num_nodes; ++i) { Vec2f const vec(snake[i] - snake[i - 1]); avg_dist += sqrt(vec.squaredNorm()); } avg_dist /= num_nodes - 1; std::vector step_storage; Vec2f displacements[9]; int const num_displacements = initDisplacementVectors(displacements, dir); for (int i = 0; i < num_displacements; ++i) { // We need more accuracy here. displacements[i] *= 0.5f; } float const elasticity_weight = 0.6f; float const bending_weight = 3.0f; float const external_weight = 2.0f; float const 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) { Vec2f const pt(snake[node_idx]); float const 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; float const 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; BOOST_FOREACH(uint32_t prev_step_idx, paths) { Step const& prev_step = step_storage[prev_step_idx]; float cost = prev_step.pathCost + step.pathCost; Vec2f const vec(step.pt - prev_step.pt); float const vec_len = sqrt(vec.squaredNorm()); if (vec_len < segment_dist_threshold) { cost += 1000; } // Elasticity. float const dist_diff = 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) { Step const& 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]; float const prev_normal_len = sqrt(prev_normal.squaredNorm()); if (prev_normal_len < segment_dist_threshold) { cost += 1000; } else { float const cos = vec.dot(prev_normal) / (vec_len * prev_normal_len); //cost += 0.7 * 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(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(); BOOST_FOREACH(uint32_t last_step_idx, paths) { Step const& 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)) { Step const& step = step_storage[step_idx]; snake.push_back(step.pt); step_idx = step.prevStepIdx; } assert(num_nodes == snake.size()); } } 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); static float const dx[] = { -1, 0, 1, -1, 1, -1, 0, 1 }; static float const dy[] = { -1, -1, -1, 0, 0, 1, 1, 1 }; for (int i = 0; i < 8; ++i) { Vec2f const vec(dx[i], dy[i]); if (vec.dot(valid_direction) > 0) { vectors[out_idx++] = vec; } } return out_idx; } QImage TopBottomEdgeTracer::visualizeGradient(Grid const& grid, QImage const* background) { int const width = grid.width(); int const height = grid.height(); int const grid_stride = grid.stride(); // First let's find the maximum and minimum values. float min_value = NumericTraits::max(); float max_value = NumericTraits::min(); GridNode const* grid_line = grid.data(); for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { float const 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); uint32_t* overlay_line = (uint32_t*)overlay.bits(); int const overlay_stride = overlay.bytesPerLine() / 4; grid_line = grid.data(); for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { float const value = grid_line[x].dirDeriv * scale; int const magnitude = qBound(0, (int)(fabs(value) + 0.5), 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); return canvas; } QImage TopBottomEdgeTracer::visualizeBlurredGradient(Grid const& grid) { int const width = grid.width(); int const height = grid.height(); int const grid_stride = grid.stride(); // First let's find the maximum and minimum values. float min_value = NumericTraits::max(); float max_value = NumericTraits::min(); GridNode const* grid_line = grid.data(); for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { float const 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); uint32_t* overlay_line = (uint32_t*)overlay.bits(); int const overlay_stride = overlay.bytesPerLine() / 4; grid_line = grid.data(); for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { float const value = grid_line[x].blurred * scale; int const magnitude = qBound(0, (int)(fabs(value) + 0.5), 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); return canvas; } QImage TopBottomEdgeTracer::visualizePaths( QImage const& background, Grid const& grid, std::pair const& bounds, std::vector const& path_endpoints) { QImage canvas(background.convertToFormat(QImage::Format_RGB32)); uint32_t* const canvas_data = (uint32_t*)canvas.bits(); int const canvas_stride = canvas.bytesPerLine() / 4; int const width = grid.width(); int const height = grid.height(); int const grid_stride = grid.stride(); GridNode const* const grid_data = grid.data(); int const nbh_canvas_offsets[8] = { -canvas_stride - 1, -canvas_stride, -canvas_stride + 1, - 1, + 1, +canvas_stride - 1, +canvas_stride, +canvas_stride + 1 }; int const nbh_grid_offsets[8] = { -grid_stride - 1, -grid_stride, -grid_stride + 1, - 1, + 1, +grid_stride - 1, +grid_stride, +grid_stride + 1 }; BOOST_FOREACH(QPoint const 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 (;;) { GridNode const* node = grid_data + grid_offset; canvas_data[canvas_offset] = 0x00ff0000; if (!node->hasPathContinuation()) { break; } int const 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 TopBottomEdgeTracer::visualizePaths( QImage const& background, std::vector > const& paths, std::pair const& bounds) { QImage canvas(background.convertToFormat(QImage::Format_RGB32)); uint32_t* const canvas_data = (uint32_t*)canvas.bits(); int const canvas_stride = canvas.bytesPerLine() / 4; BOOST_FOREACH(std::vector const& path, paths) { BOOST_FOREACH(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); return canvas; } QImage TopBottomEdgeTracer::visualizeSnakes( QImage const& background, std::vector > const& snakes, std::pair const& bounds) { 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); QBrush knot_brush(QColor(255, 255, 0, 180)); painter.setBrush(knot_brush); QRectF knot_rect(0, 0, 7, 7); BOOST_FOREACH(std::vector const& snake, snakes) { if (snake.empty()) { continue; } painter.setPen(snake_pen); painter.drawPolyline(&snake[0], snake.size()); painter.setPen(Qt::NoPen); BOOST_FOREACH(QPointF const& 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); return canvas; } QImage TopBottomEdgeTracer::visualizePolylines( QImage const& background, std::list > const& polylines, std::pair const& 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); BOOST_FOREACH(std::vector const& polyline, polylines) { if (polyline.empty()) { continue; } painter.drawPolyline(&polyline[0], 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; } } // namespace dewarping scantailor-RELEASE_0_9_12_2/dewarping/TopBottomEdgeTracer.h000066400000000000000000000077661271170121200235050ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #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 class TaskStatus; class DebugImages; class QImage; class QPoint; namespace imageproc { class GrayImage; } namespace dewarping { class DistortionModelBuilder; class TopBottomEdgeTracer { public: static void trace( imageproc::GrayImage const& image, std::pair bounds, DistortionModelBuilder& output, TaskStatus const& status, DebugImages* dbg = 0); private: struct GridNode; class PrioQueue; struct Step; static bool intersectWithRect(std::pair& bounds, QRectF const& rect); static void forceSameDirection(std::pair& bounds); static void calcDirectionalDerivative( Grid& gradient, imageproc::GrayImage const& image, Vec2f const& direction); static void horizontalSobelInPlace(Grid& grid); static void verticalSobelInPlace(Grid& grid); static Vec2f calcAvgUnitVector(std::pair const& bounds); static Vec2f directionFromPointToLine(QPointF const& pt, QLineF const& line); static void prepareForShortestPathsFrom(PrioQueue& queue, Grid& grid, QLineF const& from); static void propagateShortestPaths(Vec2f const& direction, PrioQueue& queue, Grid& grid); static int initNeighbours(int* next_nbh_offsets, int* prev_nbh_indexes, int stride, Vec2f const& direction); static std::vector locateBestPathEndpoints(Grid const& grid, QLineF const& line); static std::vector tracePathFromEndpoint(Grid const& grid, QPoint const& endpoint); static std::vector pathToSnake(Grid const& grid, QPoint const& endpoint); static void gaussBlurGradient(Grid& grid); static Vec2f downTheHillDirection( QRectF const& page_rect, std::vector const& snake, Vec2f const& bounds_dir); static void downTheHillSnake(std::vector& snake, Grid const& grid, Vec2f dir); static void upTheHillSnake(std::vector& snake, Grid const& grid, Vec2f dir); static int initDisplacementVectors(Vec2f vectors[], Vec2f valid_direction); template static float interpolatedGridValue( Grid const& grid, Extractor extractor, Vec2f pos, float default_value); static QImage visualizeGradient(Grid const& grid, QImage const* background = 0); static QImage visualizeBlurredGradient(Grid const& grid); static QImage visualizePaths(QImage const& background, Grid const& grid, std::pair const& bounds, std::vector const& path_endpoints); static QImage visualizePaths( QImage const& background, std::vector > const& paths, std::pair const& bounds); static QImage visualizeSnakes( QImage const& background, std::vector > const& snakes, std::pair const& bounds); static QImage visualizePolylines( QImage const& background, std::list > const& snakes, std::pair const& bounds); }; } // namespace dewarping #endif scantailor-RELEASE_0_9_12_2/dewarping/TowardsLineTracer.cpp000066400000000000000000000117031271170121200235410ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "TowardsLineTracer.h" #include "SidesOfLine.h" #include "ToLineProjector.h" #include "NumericTraits.h" #include "imageproc/SEDM.h" #include #include #ifndef Q_MOC_RUN #include #include #include #endif #include #include #include using namespace imageproc; namespace dewarping { TowardsLineTracer::TowardsLineTracer( imageproc::SEDM const* dm, Grid const* pm, QLineF const& line, QPoint const& 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(); } QPoint const* TowardsLineTracer::trace(float const max_dist) { if (m_finished) { return 0; } int const max_sqdist = qRound(max_dist * max_dist); QPoint cur_pos(m_lastOutputPos); QPoint last_content_pos(-1, -1); uint32_t const* p_dm = m_pDmData + cur_pos.y() * m_dmStride + cur_pos.x(); float const* 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) { Step const& step = m_steps[i]; QPoint const new_pos(cur_pos + step.vec); if (!m_rect.contains(new_pos)) { continue; } uint32_t const sqd = p_dm[step.dmOffset]; if (sqd > best_squared_dist) { best_squared_dist = sqd; best_dm_idx = i; } float const 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; QPoint const 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; return &m_lastOutputPos; } else { return 0; } } void TowardsLineTracer::setupSteps() { QPoint all_directions[8]; // all_directions[0] is north-west, and then clockwise from there. static int const m0p[] = { -1, 0, 1 }; static int const 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; BOOST_FOREACH(QPoint const 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 /= 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)) ); } } // namespace dewarping scantailor-RELEASE_0_9_12_2/dewarping/TowardsLineTracer.h000066400000000000000000000032571271170121200232130ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DEWARPING_TOWARDS_LINE_TRACER_H_ #define DEWARPING_TOWARDS_LINE_TRACER_H_ #include "VecNT.h" #include "Grid.h" #include #include #include #include namespace imageproc { class SEDM; } namespace dewarping { /** * This class is used for tracing a path towards intersection with a given line. */ class TowardsLineTracer { public: TowardsLineTracer( imageproc::SEDM const* dm, Grid const* pm, QLineF const& line, QPoint const& initial_pos); QPoint const* trace(float max_dist); private: struct Step { Vec2d unitVec; QPoint vec; int dmOffset; int pmOffset; }; void setupSteps(); uint32_t const* m_pDmData; int m_dmStride; float const* 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; }; } // namespace dewarping #endif scantailor-RELEASE_0_9_12_2/filter_dc/000077500000000000000000000000001271170121200174125ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/filter_dc/AbstractFilterDataCollector.h000066400000000000000000000017161271170121200251420ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef ABSTRACTFILTERDATACOLLECTOR_H_ #define ABSTRACTFILTERDATACOLLECTOR_H_ class AbstractFilterDataCollector { public: virtual ~AbstractFilterDataCollector() {} }; #endif scantailor-RELEASE_0_9_12_2/filter_dc/ContentBoxCollector.h000066400000000000000000000021421271170121200235140ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef CONTENTBOXCOLLECTOR_H_ #define CONTENTBOXCOLLECTOR_H_ #include "AbstractFilterDataCollector.h" class ImageTransformation; class QRectF; class ContentBoxCollector : public AbstractFilterDataCollector { public: virtual void process( ImageTransformation const& xform, QRectF const& content_rect) = 0; }; #endif scantailor-RELEASE_0_9_12_2/filter_dc/PageOrientationCollector.h000066400000000000000000000021141271170121200245200ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_ORIENTATION_COLLECTOR_H_ #define PAGE_ORIENTATION_COLLECTOR_H_ #include "AbstractFilterDataCollector.h" class OrthogonalRotation; class PageOrientationCollector : public AbstractFilterDataCollector { public: virtual void process(OrthogonalRotation const& orientation) = 0; }; #endif scantailor-RELEASE_0_9_12_2/filter_dc/ThumbnailCollector.h000066400000000000000000000023441271170121200233600ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef THUMBNAILCOLLECTOR_H_ #define THUMBNAILCOLLECTOR_H_ #include "AbstractFilterDataCollector.h" #include class ThumbnailPixmapCache; class QGraphicsItem; class QSizeF; class ThumbnailCollector : public AbstractFilterDataCollector { public: virtual void processThumbnail(std::auto_ptr) = 0; virtual IntrusivePtr thumbnailCache() = 0; virtual QSizeF maxLogicalThumbSize() const = 0; }; #endif scantailor-RELEASE_0_9_12_2/filters/000077500000000000000000000000001271170121200171275ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/filters/deskew/000077500000000000000000000000001271170121200204115ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/filters/deskew/ApplyDialog.cpp000066400000000000000000000040241271170121200233220ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ApplyDialog.h" #include "ApplyDialog.h.moc" #include "PageSelectionAccessor.h" #include namespace deskew { ApplyDialog::ApplyDialog(QWidget* parent, PageId const& cur_page, PageSelectionAccessor const& 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(selectedPagesRB); if (m_selectedPages.size() <= 1) { selectedPagesWidget->setEnabled(false); } connect(buttonBox, SIGNAL(accepted()), this, SLOT(onSubmit())); } ApplyDialog::~ApplyDialog() { } 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); } accept(); } } // namespace deskew scantailor-RELEASE_0_9_12_2/filters/deskew/ApplyDialog.h000066400000000000000000000030641271170121200227720ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DESKEW_APPLYDIALOG_H_ #define DESKEW_APPLYDIALOG_H_ #include "ui_DeskewApplyDialog.h" #include "PageId.h" #include "PageSequence.h" #include "IntrusivePtr.h" #include #include class QButtonGroup; class PageSelectionAccessor; namespace deskew { class ApplyDialog : public QDialog, private Ui::DeskewApplyDialog { Q_OBJECT public: ApplyDialog(QWidget* parent, PageId const& cur_page, PageSelectionAccessor const& page_selection_accessor); virtual ~ApplyDialog(); signals: void appliedTo(std::set const& pages); void appliedToAllPages(std::set const& pages); private slots: void onSubmit(); private: PageSequence m_pages; PageId m_curPage; std::set m_selectedPages; QButtonGroup* m_pScopeGroup; }; } // namespace deskew #endif scantailor-RELEASE_0_9_12_2/filters/deskew/CMakeLists.txt000066400000000000000000000014101271170121200231450ustar00rootroot00000000000000PROJECT("Deskew Filter") INCLUDE_DIRECTORIES(BEFORE "${CMAKE_CURRENT_BINARY_DIR}") FILE(GLOB ui_files "ui/*.ui") QT4_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 ) SOURCE_GROUP("Sources" FILES ${sources}) QT4_AUTOMOC(${sources}) ADD_LIBRARY(deskew STATIC ${sources} ${ui_sources}) TRANSLATION_SOURCES(scantailor ${sources} ${ui_files}) scantailor-RELEASE_0_9_12_2/filters/deskew/CacheDrivenTask.cpp000066400000000000000000000047711271170121200241240ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "CacheDrivenTask.h" #include "Thumbnail.h" #include "IncompleteThumbnail.h" #include "Settings.h" #include "PageInfo.h" #include "ImageTransformation.h" #include "filter_dc/AbstractFilterDataCollector.h" #include "filter_dc/ThumbnailCollector.h" #include "filters/select_content/CacheDrivenTask.h" namespace deskew { CacheDrivenTask::CacheDrivenTask( IntrusivePtr const& settings, IntrusivePtr const& next_task) : m_ptrNextTask(next_task), m_ptrSettings(settings) { } CacheDrivenTask::~CacheDrivenTask() { } void CacheDrivenTask::process( PageInfo const& page_info, AbstractFilterDataCollector* collector, ImageTransformation const& xform) { Dependencies const deps(xform.preCropArea(), xform.preRotation()); std::auto_ptr params(m_ptrSettings->getPageParams(page_info.id())); if (!params.get() || !deps.matches(params->dependencies())) { if (ThumbnailCollector* thumb_col = dynamic_cast(collector)) { thumb_col->processThumbnail( std::auto_ptr( new IncompleteThumbnail( thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), page_info.imageId(), xform ) ) ); } return; } ImageTransformation new_xform(xform); new_xform.setPostRotation(params->deskewAngle()); if (m_ptrNextTask) { m_ptrNextTask->process(page_info, collector, new_xform); return; } if (ThumbnailCollector* thumb_col = dynamic_cast(collector)) { thumb_col->processThumbnail( std::auto_ptr( new Thumbnail( thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), page_info.imageId(), new_xform ) ) ); } } } // namespace deskew scantailor-RELEASE_0_9_12_2/filters/deskew/CacheDrivenTask.h000066400000000000000000000031601271170121200235600ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DESKEW_CACHEDRIVENTASK_H_ #define DESKEW_CACHEDRIVENTASK_H_ #include "NonCopyable.h" #include "RefCountable.h" #include "IntrusivePtr.h" class QSizeF; class PageInfo; class AbstractFilterDataCollector; class ImageTransformation; namespace select_content { class CacheDrivenTask; } namespace deskew { class Settings; class CacheDrivenTask : public RefCountable { DECLARE_NON_COPYABLE(CacheDrivenTask) public: CacheDrivenTask( IntrusivePtr const& settings, IntrusivePtr const& next_task); virtual ~CacheDrivenTask(); void process( PageInfo const& page_info, AbstractFilterDataCollector* collector, ImageTransformation const& xform); private: IntrusivePtr m_ptrNextTask; IntrusivePtr m_ptrSettings; }; } // namespace deskew #endif scantailor-RELEASE_0_9_12_2/filters/deskew/Dependencies.cpp000066400000000000000000000037741271170121200235160ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Dependencies.h" #include "XmlMarshaller.h" #include "XmlUnmarshaller.h" #include "imageproc/PolygonUtils.h" #include #include using namespace imageproc; namespace deskew { Dependencies::Dependencies() { } Dependencies::Dependencies( QPolygonF const& page_outline, OrthogonalRotation const rotation) : m_pageOutline(page_outline), m_rotation(rotation) { } Dependencies::Dependencies(QDomElement const& deps_el) : m_pageOutline( XmlUnmarshaller::polygonF( deps_el.namedItem("page-outline").toElement() ) ), m_rotation( XmlUnmarshaller::rotation( deps_el.namedItem("rotation").toElement() ) ) { } Dependencies::~Dependencies() { } bool Dependencies::matches(Dependencies const& other) const { if (m_rotation != other.m_rotation) { return false; } if (!PolygonUtils::fuzzyCompare(m_pageOutline, other.m_pageOutline)) { return false; } return true; } QDomElement Dependencies::toXml(QDomDocument& doc, QString const& name) const { XmlMarshaller marshaller(doc); QDomElement el(doc.createElement(name)); el.appendChild(marshaller.rotation(m_rotation, "rotation")); el.appendChild(marshaller.polygonF(m_pageOutline, "page-outline")); return el; } } // namespace deskew scantailor-RELEASE_0_9_12_2/filters/deskew/Dependencies.h000066400000000000000000000030211271170121200231440ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DESKEW_DEPENDENCIES_H_ #define DESKEW_DEPENDENCIES_H_ #include #include "OrthogonalRotation.h" class QDomDocument; class QDomElement; class QString; namespace deskew { /** * \brief Dependencies of deskew parameters. * * Once dependencies change, deskew parameters are no longer valid. */ class Dependencies { public: // Member-wise copying is OK. Dependencies(); Dependencies(QPolygonF const& page_outline, OrthogonalRotation rotation); Dependencies(QDomElement const& deps_el); ~Dependencies(); bool matches(Dependencies const& other) const; QDomElement toXml(QDomDocument& doc, QString const& name) const; private: QPolygonF m_pageOutline; OrthogonalRotation m_rotation; }; } // namespace deskew #endif scantailor-RELEASE_0_9_12_2/filters/deskew/Filter.cpp000066400000000000000000000102301271170121200223360ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Filter.h" #include "FilterUiInterface.h" #include "OptionsWidget.h" #include "Task.h" #include "CacheDrivenTask.h" #include "Settings.h" #include "ProjectReader.h" #include "ProjectWriter.h" #include "PageId.h" #include "RelinkablePath.h" #include "AbstractRelinker.h" #ifndef Q_MOC_RUN #include #include #endif #include #include #include #include #include #include "CommandLine.h" namespace deskew { Filter::Filter(PageSelectionAccessor const& page_selection_accessor) : m_ptrSettings(new Settings) { if (CommandLine::get().isGui()) { m_ptrOptionsWidget.reset(new OptionsWidget(m_ptrSettings, page_selection_accessor)); } } Filter::~Filter() { } QString Filter::getName() const { return QCoreApplication::translate("deskew::Filter", "Deskew"); } PageView Filter::getView() const { return PAGE_VIEW; } void Filter::performRelinking(AbstractRelinker const& relinker) { m_ptrSettings->performRelinking(relinker); } void Filter::preUpdateUI(FilterUiInterface* const ui, PageId const& page_id) { m_ptrOptionsWidget->preUpdateUI(page_id); ui->setOptionsWidget(m_ptrOptionsWidget.get(), ui->KEEP_OWNERSHIP); } QDomElement Filter::saveSettings(ProjectWriter const& writer, QDomDocument& doc) const { using namespace boost::lambda; QDomElement filter_el(doc.createElement("deskew")); writer.enumPages( boost::lambda::bind( &Filter::writePageSettings, this, boost::ref(doc), var(filter_el), boost::lambda::_1, boost::lambda::_2 ) ); return filter_el; } void Filter::loadSettings(ProjectReader const& reader, QDomElement const& filters_el) { m_ptrSettings->clear(); QDomElement const filter_el(filters_el.namedItem("deskew").toElement()); QString const 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; } QDomElement const el(node.toElement()); bool ok = true; int const id = el.attribute("id").toInt(&ok); if (!ok) { continue; } PageId const page_id(reader.pageId(id)); if (page_id.isNull()) { continue; } QDomElement const params_el(el.namedItem("params").toElement()); if (params_el.isNull()) { continue; } Params const params(params_el); m_ptrSettings->setPageParams(page_id, params); } } void Filter::writePageSettings( QDomDocument& doc, QDomElement& filter_el, PageId const& page_id, int const numeric_id) const { std::auto_ptr const params(m_ptrSettings->getPageParams(page_id)); if (!params.get()) { return; } QDomElement page_el(doc.createElement("page")); page_el.setAttribute("id", numeric_id); page_el.appendChild(params->toXml(doc, "params")); filter_el.appendChild(page_el); } IntrusivePtr Filter::createTask( PageId const& page_id, IntrusivePtr const& next_task, bool const batch_processing, bool const debug) { return IntrusivePtr( new Task( IntrusivePtr(this), m_ptrSettings, next_task, page_id, batch_processing, debug ) ); } IntrusivePtr Filter::createCacheDrivenTask( IntrusivePtr const& next_task) { return IntrusivePtr( new CacheDrivenTask(m_ptrSettings, next_task) ); } } // namespace deskew scantailor-RELEASE_0_9_12_2/filters/deskew/Filter.h000066400000000000000000000046351271170121200220170ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DESKEW_FILTER_H_ #define DESKEW_FILTER_H_ #include "NonCopyable.h" #include "AbstractFilter.h" #include "PageView.h" #include "IntrusivePtr.h" #include "FilterResult.h" #include "SafeDeletingQObjectPtr.h" class PageId; class QString; class PageSelectionAccessor; namespace select_content { class Task; class CacheDrivenTask; } namespace deskew { class OptionsWidget; class Task; class CacheDrivenTask; class Settings; class Filter : public AbstractFilter { DECLARE_NON_COPYABLE(Filter) public: Filter(PageSelectionAccessor const& page_selection_accessor); virtual ~Filter(); virtual QString getName() const; virtual PageView getView() const; virtual void performRelinking(AbstractRelinker const& relinker); virtual void preUpdateUI(FilterUiInterface* ui, PageId const& page_id); virtual QDomElement saveSettings( ProjectWriter const& writer, QDomDocument& doc) const; virtual void loadSettings( ProjectReader const& reader, QDomElement const& filters_el); IntrusivePtr createTask( PageId const& page_id, IntrusivePtr const& next_task, bool batch_processing, bool debug); IntrusivePtr createCacheDrivenTask( IntrusivePtr const& next_task); OptionsWidget* optionsWidget() { return m_ptrOptionsWidget.get(); } Settings* getSettings() { return m_ptrSettings.get(); }; private: void writePageSettings( QDomDocument& doc, QDomElement& filter_el, PageId const& page_id, int numeric_id) const; IntrusivePtr m_ptrSettings; SafeDeletingQObjectPtr m_ptrOptionsWidget; }; } // namespace deskew #endif scantailor-RELEASE_0_9_12_2/filters/deskew/ImageView.cpp000066400000000000000000000210131271170121200227670ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ImageView.h" #include "ImageView.h.moc" #include "ImageTransformation.h" #include "ImagePresentation.h" #include "InteractionState.h" #include "imageproc/Constants.h" #include #include #include #include #include #include #include #include #include #ifndef Q_MOC_RUN #include #endif #include #include namespace deskew { double const ImageView::m_maxRotationDeg = 45.0; double const ImageView::m_maxRotationSin = sin( m_maxRotationDeg * imageproc::constants::DEG2RAD ); int const ImageView::m_cellSize = 20; ImageView::ImageView( QImage const& image, QImage const& downscaled_image, ImageTransformation const& 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.") ); QString const tip(tr("Drag this handle to rotate the image.")); double const 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); } ImageView::~ImageView() { } void ImageView::manualDeskewAngleSetExternally(double const degrees) { if (m_xform.postRotation() == degrees) { return; } m_xform.setPostRotation(degrees); updateTransform(ImagePresentation(m_xform.transform(), m_xform.resultingPreCropArea())); } void ImageView::onPaint(QPainter& painter, InteractionState const& interaction) { painter.setWorldMatrixEnabled(false); painter.setRenderHints(QPainter::Antialiasing, false); double const w = maxViewportRect().width(); double const h = maxViewportRect().height(); QPointF const center(getImageRotationOrigin()); // Draw the semi-transparent grid. QPen pen(QColor(0, 0, 255, 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, 255)); 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 ( ) QRectF const 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) ); std::pair const 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); } 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(); double const 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()); } QPointF ImageView::handlePosition(int idx) const { std::pair const handles(getRotationHandles(getRotationArcSquare())); if (idx == 0) { return handles.first; } else { return handles.second; } } void ImageView::handleMoveRequest(int idx, QPointF const& pos) { QRectF const arc_square(getRotationArcSquare()); double const arc_radius = 0.5 * arc_square.width(); double const 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 = 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()); } /** * Get the point at the center of the widget, in widget coordinates. * The point may be adjusted to to ensure it's at the center of a pixel. */ QPointF ImageView::getImageRotationOrigin() const { QRectF const viewport_rect(maxViewportRect()); return QPointF( floor(0.5 * viewport_rect.width()) + 0.5, 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 { double const h_margin = 0.5 * m_handlePixmap.width() + verticalScrollBar()->style()->pixelMetric(QStyle::PM_ScrollBarExtent, 0, verticalScrollBar()); double const v_margin = 0.5 * m_handlePixmap.height() + horizontalScrollBar()->style()->pixelMetric(QStyle::PM_ScrollBarExtent, 0, horizontalScrollBar()); 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()); QRectF arc_square(QPointF(0, 0), arc_size); arc_square.moveCenter(reduced_screen_rect.center()); return arc_square; } std::pair ImageView::getRotationHandles(QRectF const& arc_square) const { double const rot_sin = m_xform.postRotationSin(); double const rot_cos = m_xform.postRotationCos(); double const arc_radius = 0.5 * arc_square.width(); QPointF const 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 scantailor-RELEASE_0_9_12_2/filters/deskew/ImageView.h000066400000000000000000000044261271170121200224450ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #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 class QRect; namespace deskew { class ImageView : public ImageViewBase, private InteractionHandler { Q_OBJECT public: ImageView( QImage const& image, QImage const& downscaled_image, ImageTransformation const& xform); virtual ~ImageView(); signals: void manualDeskewAngleSet(double degrees); public slots: void manualDeskewAngleSetExternally(double degrees); protected: virtual void onPaint(QPainter& painter, InteractionState const& interaction); virtual void onWheelEvent(QWheelEvent* event, InteractionState& interaction); private: QPointF handlePosition(int idx) const; void handleMoveRequest(int idx, QPointF const& pos); virtual void dragFinished(); QPointF getImageRotationOrigin() const; QRectF getRotationArcSquare() const; std::pair getRotationHandles(QRectF const& arc_square) const; static int const m_cellSize; static double const m_maxRotationDeg; static double const m_maxRotationSin; QPixmap m_handlePixmap; DraggablePoint m_handles[2]; ObjectDragHandler m_handleInteractors[2]; DragHandler m_dragHandler; ZoomHandler m_zoomHandler; ImageTransformation m_xform; }; } // namespace deskew #endif scantailor-RELEASE_0_9_12_2/filters/deskew/OptionsWidget.cpp000066400000000000000000000150241271170121200237160ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "OptionsWidget.h" #include "OptionsWidget.h.moc" #include "Settings.h" #include "ScopedIncDec.h" #include #include #include #include "ApplyDialog.h" #ifndef Q_MOC_RUN #include #endif namespace deskew { double const OptionsWidget::MAX_ANGLE = 45.0; OptionsWidget::OptionsWidget(IntrusivePtr const& settings, PageSelectionAccessor const& page_selection_accessor) : m_ptrSettings(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(); 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()) ); } OptionsWidget::~OptionsWidget() { } void OptionsWidget::showDeskewDialog() { ApplyDialog* dialog = new ApplyDialog( this, m_pageId, m_pageSelectionAccessor ); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setWindowTitle(tr("Apply Deskew")); connect( dialog, SIGNAL(appliedTo(std::set const&)), this, SLOT(appliedTo(std::set const&)) ); connect( dialog, SIGNAL(appliedToAllPages(std::set const&)), this, SLOT(appliedToAllPages(std::set const&)) ); dialog->show(); } void OptionsWidget::appliedTo(std::set const& pages) { if (pages.empty()) { return; } Params const params( m_uiData.effectiveDeskewAngle(), m_uiData.dependencies(), m_uiData.mode() ); m_ptrSettings->setDegress(pages, params); BOOST_FOREACH(PageId const& page_id, pages) { emit invalidateThumbnail(page_id); } } void OptionsWidget::appliedToAllPages(std::set const& pages) { if (pages.empty()) { return; } Params const params( m_uiData.effectiveDeskewAngle(), m_uiData.dependencies(), m_uiData.mode() ); m_ptrSettings->setDegress(pages, params); emit invalidateAllThumbnails(); } void OptionsWidget::manualDeskewAngleSetExternally(double const degrees) { m_uiData.setEffectiveDeskewAngle(degrees); m_uiData.setMode(MODE_MANUAL); updateModeIndication(MODE_MANUAL); setSpinBoxKnownState(degreesToSpinBox(degrees)); commitCurrentParams(); emit invalidateThumbnail(m_pageId); } void OptionsWidget::preUpdateUI(PageId const& page_id) { ScopedIncDec guard(m_ignoreAutoManualToggle); m_pageId = page_id; setSpinBoxUnknownState(); autoBtn->setChecked(true); autoBtn->setEnabled(false); manualBtn->setEnabled(false); } void OptionsWidget::postUpdateUI(UiData const& ui_data) { m_uiData = ui_data; autoBtn->setEnabled(true); manualBtn->setEnabled(true); updateModeIndication(ui_data.mode()); setSpinBoxKnownState(degreesToSpinBox(ui_data.effectiveDeskewAngle())); } void OptionsWidget::spinBoxValueChanged(double const value) { if (m_ignoreSpinBoxChanges) { return; } double const degrees = spinBoxToDegrees(value); m_uiData.setEffectiveDeskewAngle(degrees); m_uiData.setMode(MODE_MANUAL); updateModeIndication(MODE_MANUAL); commitCurrentParams(); emit manualDeskewAngleSet(degrees); emit invalidateThumbnail(m_pageId); } void OptionsWidget::modeChanged(bool const 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(); } } void OptionsWidget::updateModeIndication(AutoManualMode const mode) { ScopedIncDec guard(m_ignoreAutoManualToggle); if (mode == MODE_AUTO) { autoBtn->setChecked(true); } else { manualBtn->setChecked(true); } } void OptionsWidget::setSpinBoxUnknownState() { ScopedIncDec guard(m_ignoreSpinBoxChanges); angleSpinBox->setSpecialValueText("?"); angleSpinBox->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter); angleSpinBox->setValue(angleSpinBox->minimum()); angleSpinBox->setEnabled(false); } void OptionsWidget::setSpinBoxKnownState(double const angle) { ScopedIncDec guard(m_ignoreSpinBoxChanges); 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); } void OptionsWidget::commitCurrentParams() { Params const params( m_uiData.effectiveDeskewAngle(), m_uiData.dependencies(), m_uiData.mode() ); m_ptrSettings->setPageParams(m_pageId, params); } double OptionsWidget::spinBoxToDegrees(double const 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(double const degrees) { // See above. return -degrees; } /*========================== OptionsWidget::UiData =========================*/ OptionsWidget::UiData::UiData() : m_effDeskewAngle(0.0), m_mode(MODE_AUTO) { } OptionsWidget::UiData::~UiData() { } void OptionsWidget::UiData::setEffectiveDeskewAngle(double const degrees) { m_effDeskewAngle = degrees; } double OptionsWidget::UiData::effectiveDeskewAngle() const { return m_effDeskewAngle; } void OptionsWidget::UiData::setDependencies(Dependencies const& deps) { m_deps = deps; } Dependencies const& OptionsWidget::UiData::dependencies() const { return m_deps; } void OptionsWidget::UiData::setMode(AutoManualMode const mode) { m_mode = mode; } AutoManualMode OptionsWidget::UiData::mode() const { return m_mode; } } // namespace deskew scantailor-RELEASE_0_9_12_2/filters/deskew/OptionsWidget.h000066400000000000000000000053321271170121200233640ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DESKEW_OPTIONSWIDGET_H_ #define DESKEW_OPTIONSWIDGET_H_ #include "ui_DeskewOptionsWidget.h" #include "FilterOptionsWidget.h" #include "IntrusivePtr.h" #include "PageId.h" #include "Dependencies.h" #include "AutoManualMode.h" #include "PageSelectionAccessor.h" #include namespace deskew { class Settings; class OptionsWidget : public FilterOptionsWidget, private Ui::DeskewOptionsWidget { Q_OBJECT public: class UiData { // Member-wise copying is OK. public: UiData(); ~UiData(); void setEffectiveDeskewAngle(double degrees); double effectiveDeskewAngle() const; void setDependencies(Dependencies const& deps); Dependencies const& dependencies() const; void setMode(AutoManualMode mode); AutoManualMode mode() const; private: double m_effDeskewAngle; Dependencies m_deps; AutoManualMode m_mode; }; OptionsWidget(IntrusivePtr const& settings, PageSelectionAccessor const& page_selection_accessor); virtual ~OptionsWidget(); signals: void manualDeskewAngleSet(double degrees); public slots: void manualDeskewAngleSetExternally(double degrees); public: void preUpdateUI(PageId const& page_id); void postUpdateUI(UiData const& ui_data); private slots: void spinBoxValueChanged(double skew_degrees); void modeChanged(bool auto_mode); void showDeskewDialog(); void appliedTo(std::set const& pages); void appliedToAllPages(std::set const& pages); private: void updateModeIndication(AutoManualMode mode); void setSpinBoxUnknownState(); void setSpinBoxKnownState(double angle); void commitCurrentParams(); static double spinBoxToDegrees(double sb_value); static double degreesToSpinBox(double degrees); static double const MAX_ANGLE; IntrusivePtr m_ptrSettings; PageId m_pageId; UiData m_uiData; int m_ignoreAutoManualToggle; int m_ignoreSpinBoxChanges; PageSelectionAccessor m_pageSelectionAccessor; }; } // namespace deskew #endif scantailor-RELEASE_0_9_12_2/filters/deskew/Params.cpp000066400000000000000000000032311271170121200223370ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Params.h" #include "../../Utils.h" #include #include namespace deskew { Params::Params(double const deskew_angle_deg, Dependencies const& deps, AutoManualMode const mode) : m_deskewAngleDeg(deskew_angle_deg), m_deps(deps), m_mode(mode) { } Params::Params(QDomElement const& 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) { } Params::~Params() { } QDomElement Params::toXml(QDomDocument& doc, QString const& 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")); return el; } } // namespace deskew scantailor-RELEASE_0_9_12_2/filters/deskew/Params.h000066400000000000000000000027731271170121200220160ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DESKEW_PARAMS_H_ #define DESKEW_PARAMS_H_ #include "Dependencies.h" #include "AutoManualMode.h" #include class QDomDocument; class QDomElement; namespace deskew { class Params { public: // Member-wise copying is OK. Params(double deskew_angle_deg, Dependencies const& deps, AutoManualMode mode); Params(QDomElement const& deskew_el); ~Params(); double deskewAngle() const { return m_deskewAngleDeg; } Dependencies const& dependencies() const { return m_deps; } AutoManualMode mode() const { return m_mode; } QDomElement toXml(QDomDocument& doc, QString const& name) const; private: double m_deskewAngleDeg; Dependencies m_deps; AutoManualMode m_mode; }; } // namespace deskew #endif scantailor-RELEASE_0_9_12_2/filters/deskew/Settings.cpp000066400000000000000000000047011271170121200227170ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Settings.h" #include "Utils.h" #include "RelinkablePath.h" #include "AbstractRelinker.h" #include #ifndef Q_MOC_RUN #include #endif namespace deskew { Settings::Settings() { } Settings::~Settings() { } void Settings::clear() { QMutexLocker locker(&m_mutex); m_perPageParams.clear(); } void Settings::performRelinking(AbstractRelinker const& relinker) { QMutexLocker locker(&m_mutex); PerPageParams new_params; BOOST_FOREACH(PerPageParams::value_type const& kv, m_perPageParams) { RelinkablePath const 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); } void Settings::setPageParams(PageId const& page_id, Params const& params) { QMutexLocker locker(&m_mutex); Utils::mapSetValue(m_perPageParams, page_id, params); } void Settings::clearPageParams(PageId const& page_id) { QMutexLocker locker(&m_mutex); m_perPageParams.erase(page_id); } std::auto_ptr Settings::getPageParams(PageId const& page_id) const { QMutexLocker locker(&m_mutex); PerPageParams::const_iterator it(m_perPageParams.find(page_id)); if (it != m_perPageParams.end()) { return std::auto_ptr(new Params(it->second)); } else { return std::auto_ptr(); } } void Settings::setDegress(std::set const& pages, Params const& params) { QMutexLocker const locker(&m_mutex); BOOST_FOREACH(PageId const& page, pages) { Utils::mapSetValue(m_perPageParams, page, params); } } } // namespace deskew scantailor-RELEASE_0_9_12_2/filters/deskew/Settings.h000066400000000000000000000031721271170121200223650ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DESKEW_SETTINGS_H_ #define DESKEW_SETTINGS_H_ #include "RefCountable.h" #include "NonCopyable.h" #include "PageId.h" #include "Params.h" #include #include #include #include class AbstractRelinker; namespace deskew { class Settings : public RefCountable { DECLARE_NON_COPYABLE(Settings) public: Settings(); virtual ~Settings(); void clear(); void performRelinking(AbstractRelinker const& relinker); void setPageParams(PageId const& page_id, Params const& params); void clearPageParams(PageId const& page_id); std::auto_ptr getPageParams(PageId const& page_id) const; void setDegress(std::set const& pages, Params const& params); private: typedef std::map PerPageParams; mutable QMutex m_mutex; PerPageParams m_perPageParams; }; } // namespace deskew #endif scantailor-RELEASE_0_9_12_2/filters/deskew/Task.cpp000066400000000000000000000202351271170121200220210ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Task.h" #include "Filter.h" #include "OptionsWidget.h" #include "Settings.h" #include "Params.h" #include "Dependencies.h" #include "TaskStatus.h" #include "DebugImages.h" #include "filters/select_content/Task.h" #include "FilterUiInterface.h" #include "ImageView.h" #include "FilterData.h" #include "Dpi.h" #include "Dpm.h" #include "ImageTransformation.h" #include "imageproc/BinaryImage.h" #include "imageproc/BWColor.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/Connectivity.h" #include "imageproc/Morphology.h" #include #include #include #include #include #include #include #include #include #include #include #include "CommandLine.h" namespace deskew { using namespace imageproc; class Task::UiUpdater : public FilterResult { public: UiUpdater(IntrusivePtr const& filter, std::auto_ptr dbg_img, QImage const& image, PageId const& page_id, ImageTransformation const& xform, OptionsWidget::UiData const& ui_data, bool batch_processing); virtual void updateUI(FilterUiInterface* ui); virtual IntrusivePtr filter() { return m_ptrFilter; } private: IntrusivePtr m_ptrFilter; std::auto_ptr m_ptrDbg; QImage m_image; QImage m_downscaledImage; PageId m_pageId; ImageTransformation m_xform; OptionsWidget::UiData m_uiData; bool m_batchProcessing; }; Task::Task(IntrusivePtr const& filter, IntrusivePtr const& settings, IntrusivePtr const& next_task, PageId const& page_id, bool const batch_processing, bool const debug) : m_ptrFilter(filter), m_ptrSettings(settings), m_ptrNextTask(next_task), m_pageId(page_id), m_batchProcessing(batch_processing) { if (debug) { m_ptrDbg.reset(new DebugImages); } } Task::~Task() { } FilterResultPtr Task::process(TaskStatus const& status, FilterData const& data) { status.throwIfCancelled(); Dependencies const deps(data.xform().preCropArea(), data.xform().preRotation()); OptionsWidget::UiData ui_data; ui_data.setDependencies(deps); CommandLine const& cli = CommandLine::get(); std::auto_ptr params(m_ptrSettings->getPageParams(m_pageId)); if (params.get()) { if ((!deps.matches(params->dependencies()) || params->deskewAngle() != ui_data.effectiveDeskewAngle()) && params->mode() == MODE_AUTO && !cli.hasDeskewAngle() && !cli.hasDeskew()) { params.reset(); } else { ui_data.setEffectiveDeskewAngle(params->deskewAngle()); ui_data.setMode(params->mode()); Params const new_params( ui_data.effectiveDeskewAngle(), deps, ui_data.mode() ); m_ptrSettings->setPageParams(m_pageId, new_params); } } if (!params.get()) { QRectF const image_area( data.xform().transformBack().mapRect(data.xform().resultingRect()) ); QRect const 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.get()) { m_ptrDbg->add(rotated_image, "bw_rotated"); } QSize const unrotated_dpm(Dpm(data.origImage()).toSize()); Dpm const rotated_dpm( data.xform().preRotation().rotate(unrotated_dpm) ); cleanup(status, rotated_image, Dpi(rotated_dpm)); if (m_ptrDbg.get()) { m_ptrDbg->add(rotated_image, "after_cleanup"); } status.throwIfCancelled(); SkewFinder skew_finder; skew_finder.setResolutionRatio( (double)rotated_dpm.horizontal() / rotated_dpm.vertical() ); Skew const 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 const new_params( ui_data.effectiveDeskewAngle(), deps, ui_data.mode() ); m_ptrSettings->setPageParams(m_pageId, new_params); status.throwIfCancelled(); } } ImageTransformation new_xform(data.xform()); new_xform.setPostRotation(ui_data.effectiveDeskewAngle()); if (m_ptrNextTask) { return m_ptrNextTask->process(status, FilterData(data, new_xform)); } else { return FilterResultPtr( new UiUpdater( m_ptrFilter, m_ptrDbg, data.origImage(), m_pageId, new_xform, ui_data, m_batchProcessing ) ); } } void Task::cleanup(TaskStatus const& status, BinaryImage& image, Dpi const& 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(); } status.throwIfCancelled(); QSize const brick(from150dpi(QSize(200, 14), reduced_dpi)); BinaryImage opened(openBrick(reduced_image, brick, BLACK)); reduced_image.release(); status.throwIfCancelled(); BinaryImage seed(upscaleIntegerTimes(opened, image.size(), WHITE)); opened.release(); status.throwIfCancelled(); BinaryImage garbage(seedFill(seed, image, CONN8)); seed.release(); status.throwIfCancelled(); rasterOp >(image, garbage); } int Task::from150dpi(int size, int target_dpi) { int const new_size = (size * target_dpi + 75) / 150; if (new_size < 1) { return 1; } return new_size; } QSize Task::from150dpi(QSize const& size, Dpi const& target_dpi) { int const width = from150dpi(size.width(), target_dpi.horizontal()); int const height = from150dpi(size.height(), target_dpi.vertical()); return QSize(width, height); } /*============================ Task::UiUpdater ==========================*/ Task::UiUpdater::UiUpdater( IntrusivePtr const& filter, std::auto_ptr dbg_img, QImage const& image, PageId const& page_id, ImageTransformation const& xform, OptionsWidget::UiData const& ui_data, bool const batch_processing) : m_ptrFilter(filter), m_ptrDbg(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); ui->invalidateThumbnail(m_pageId); if (m_batchProcessing) { return; } ImageView* view = new ImageView(m_image, m_downscaledImage, m_xform); ui->setImageWidget(view, ui->TRANSFER_OWNERSHIP, m_ptrDbg.get()); QObject::connect( view, SIGNAL(manualDeskewAngleSet(double)), opt_widget, SLOT(manualDeskewAngleSetExternally(double)) ); QObject::connect( opt_widget, SIGNAL(manualDeskewAngleSet(double)), view, SLOT(manualDeskewAngleSetExternally(double)) ); } } // namespace deskew scantailor-RELEASE_0_9_12_2/filters/deskew/Task.h000066400000000000000000000040141271170121200214630ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DESKEW_TASK_H_ #define DESKEW_TASK_H_ #include "NonCopyable.h" #include "RefCountable.h" #include "FilterResult.h" #include "PageId.h" #include class TaskStatus; class FilterData; class QImage; class QSize; class Dpi; class DebugImages; namespace imageproc { class BinaryImage; }; namespace select_content { class Task; } namespace deskew { class Filter; class Settings; class Task : public RefCountable { DECLARE_NON_COPYABLE(Task) public: Task(IntrusivePtr const& filter, IntrusivePtr const& settings, IntrusivePtr const& next_task, PageId const& page_id, bool batch_processing, bool debug); virtual ~Task(); FilterResultPtr process( TaskStatus const& status, FilterData const& data); private: class UiUpdater; static void cleanup( TaskStatus const& status, imageproc::BinaryImage& img, Dpi const& dpi); static int from150dpi(int size, int target_dpi); static QSize from150dpi(QSize const& size, Dpi const& target_dpi); IntrusivePtr m_ptrFilter; IntrusivePtr m_ptrSettings; IntrusivePtr m_ptrNextTask; std::auto_ptr m_ptrDbg; PageId m_pageId; bool m_batchProcessing; }; } // namespace deskew #endif scantailor-RELEASE_0_9_12_2/filters/deskew/Thumbnail.cpp000066400000000000000000000045421271170121200230450ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Thumbnail.h" #include #include #include #include #include #include #include #include #include namespace deskew { Thumbnail::Thumbnail( IntrusivePtr const& thumbnail_cache, QSizeF const& max_size, ImageId const& image_id, ImageTransformation const& xform) : ThumbnailBase(thumbnail_cache, max_size, image_id, xform) { } void Thumbnail::paintOverImage( QPainter& painter, QTransform const& image_to_display, QTransform const& thumb_to_display) { painter.setRenderHint(QPainter::Antialiasing, false); QPen pen(QColor(0, 0, 255, 70)); pen.setWidth(1); pen.setCosmetic(true); painter.setPen(pen); QRectF const bounding_rect(boundingRect()); double const cell_size = 8; double const left = bounding_rect.left(); double const right = bounding_rect.right(); double const top = bounding_rect.top(); double const bottom = bounding_rect.bottom(); double const w = bounding_rect.width(); double const h = bounding_rect.height(); QPointF const 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); } } // namespace deskew scantailor-RELEASE_0_9_12_2/filters/deskew/Thumbnail.h000066400000000000000000000025521271170121200225110ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DESKEW_THUMBNAIL_H_ #define DESKEW_THUMBNAIL_H_ #include "ThumbnailBase.h" #include "IntrusivePtr.h" class QSizeF; class ThumbnailPixmapCache; class ImageId; class ImageTransformation; namespace deskew { class Thumbnail : public ThumbnailBase { public: Thumbnail(IntrusivePtr const& thumbnail_cache, QSizeF const& max_size, ImageId const& image_id, ImageTransformation const& xform); virtual void paintOverImage( QPainter& painter, QTransform const& image_to_display, QTransform const& thumb_to_display); }; } // namespace deskew #endif scantailor-RELEASE_0_9_12_2/filters/deskew/ui/000077500000000000000000000000001271170121200210265ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/filters/deskew/ui/DeskewApplyDialog.ui000066400000000000000000000077321271170121200247460ustar00rootroot00000000000000 DeskewApplyDialog Qt::WindowModal 0 0 320 195 Apply to This page only (already applied) true All pages This page and the following ones 0 0 Selected pages 0 Qt::Horizontal QSizePolicy::Fixed 30 0 7 Use Ctrl+Click / Shift+Click to select multiple pages. Qt::Vertical 20 0 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox rejected() DeskewApplyDialog reject() 316 260 286 274 scantailor-RELEASE_0_9_12_2/filters/deskew/ui/DeskewOptionsWidget.ui000066400000000000000000000111121271170121200253230ustar00rootroot00000000000000 DeskewOptionsWidget 0 0 220 238 Form Deskew Qt::Horizontal 1 20 Auto true true true Manual true true Qt::Horizontal 1 20 Qt::Horizontal 40 20 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0.010000000000000 Qt::Horizontal 40 20 Qt::Horizontal 38 18 Apply To ... Qt::Horizontal 28 18 Qt::Vertical 20 40 scantailor-RELEASE_0_9_12_2/filters/fix_orientation/000077500000000000000000000000001271170121200223305ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/filters/fix_orientation/ApplyDialog.cpp000066400000000000000000000056261271170121200252520ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ApplyDialog.h" #include "ApplyDialog.h.moc" #include "PageSelectionAccessor.h" #include #include #include namespace fix_orientation { ApplyDialog::ApplyDialog( QWidget* parent, PageId const& cur_page, PageSelectionAccessor const& 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(everyOtherSelectedRB); if (m_selectedPages.size() <= 1) { selectedPagesWidget->setEnabled(false); everyOtherSelectedWidget->setEnabled(false); everyOtherSelectedHint->setText(selectedPagesHint->text()); } else if (m_selectedRanges.size() > 1) { everyOtherSelectedWidget->setEnabled(false); everyOtherSelectedHint->setText(tr("Can't do: more than one group is selected.")); } connect(buttonBox, SIGNAL(accepted()), this, SLOT(onSubmit())); } ApplyDialog::~ApplyDialog() { } void ApplyDialog::onSubmit() { std::set pages; // 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 (everyOtherRB->isChecked()) { m_pages.selectEveryOther(m_curPage).swap(pages); } else if (everyOtherSelectedRB->isChecked()) { assert(m_selectedRanges.size() == 1); PageRange const& range = m_selectedRanges.front(); range.selectEveryOther(m_curPage).swap(pages); } emit appliedTo(pages); // We assume the default connection from accept() to accepted() was removed. accept(); } } // namespace fix_orientation scantailor-RELEASE_0_9_12_2/filters/fix_orientation/ApplyDialog.h000066400000000000000000000033031271170121200247050ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef FIX_ORIENTATION_APPLYDIALOG_H_ #define FIX_ORIENTATION_APPLYDIALOG_H_ #include "ui_OrientationApplyDialog.h" #include "PageId.h" #include "PageRange.h" #include "PageSequence.h" #include "IntrusivePtr.h" #include #include #include class PageSelectionAccessor; class QButtonGroup; namespace fix_orientation { class Scope; class ApplyDialog : public QDialog, private Ui::OrientationApplyDialog { Q_OBJECT public: ApplyDialog(QWidget* parent, PageId const& cur_page, PageSelectionAccessor const& page_selection_accessor); virtual ~ApplyDialog(); signals: void appliedTo(std::set const& pages); void appliedToAllPages(std::set const& pages); private slots: void onSubmit(); private: PageSequence m_pages; std::set m_selectedPages; std::vector m_selectedRanges; PageId m_curPage; QButtonGroup* m_pBtnGroup; }; } // namespace fix_orientation #endif scantailor-RELEASE_0_9_12_2/filters/fix_orientation/CMakeLists.txt000066400000000000000000000013101271170121200250630ustar00rootroot00000000000000PROJECT("Fix Orientation Filter") INCLUDE_DIRECTORIES(BEFORE "${CMAKE_CURRENT_BINARY_DIR}") FILE(GLOB ui_files "ui/*.ui") QT4_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 ) SOURCE_GROUP("Sources" FILES ${sources}) QT4_AUTOMOC(${sources}) ADD_LIBRARY(fix_orientation STATIC ${sources} ${ui_sources}) TRANSLATION_SOURCES(scantailor ${sources} ${ui_files})scantailor-RELEASE_0_9_12_2/filters/fix_orientation/CacheDrivenTask.cpp000066400000000000000000000043771271170121200260450ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "CacheDrivenTask.h" #include "Settings.h" #include "PageInfo.h" #include "PageId.h" #include "ImageId.h" #include "ImageTransformation.h" #include "ThumbnailBase.h" #include "filter_dc/AbstractFilterDataCollector.h" #include "filter_dc/ThumbnailCollector.h" #include "filter_dc/PageOrientationCollector.h" #include "filters/page_split/CacheDrivenTask.h" namespace fix_orientation { CacheDrivenTask::CacheDrivenTask( IntrusivePtr const& settings, IntrusivePtr const& next_task) : m_ptrNextTask(next_task), m_ptrSettings(settings) { } CacheDrivenTask::~CacheDrivenTask() { } void CacheDrivenTask::process( PageInfo const& page_info, AbstractFilterDataCollector* collector) { QRectF const 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())); if (PageOrientationCollector* col = dynamic_cast(collector)) { col->process(xform.preRotation()); } if (m_ptrNextTask) { m_ptrNextTask->process(page_info, collector, xform); return; } if (ThumbnailCollector* thumb_col = dynamic_cast(collector)) { thumb_col->processThumbnail( std::auto_ptr( new ThumbnailBase( thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), page_info.imageId(), xform ) ) ); } } } // namespace fix_orientation scantailor-RELEASE_0_9_12_2/filters/fix_orientation/CacheDrivenTask.h000066400000000000000000000031311271170121200254750ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef FIX_ORIENTATION_CACHEDRIVENTASK_H_ #define FIX_ORIENTATION_CACHEDRIVENTASK_H_ #include "NonCopyable.h" #include "CompositeCacheDrivenTask.h" #include "IntrusivePtr.h" class PageInfo; class AbstractFilterDataCollector; namespace page_split { class CacheDrivenTask; } namespace fix_orientation { class Settings; class CacheDrivenTask : public CompositeCacheDrivenTask { DECLARE_NON_COPYABLE(CacheDrivenTask) public: CacheDrivenTask( IntrusivePtr const& settings, IntrusivePtr const& next_task); virtual ~CacheDrivenTask(); virtual void process( PageInfo const& page_info, AbstractFilterDataCollector* collector); private: IntrusivePtr m_ptrNextTask; IntrusivePtr m_ptrSettings; }; } // namespace fix_orientation #endif scantailor-RELEASE_0_9_12_2/filters/fix_orientation/Filter.cpp000066400000000000000000000107211271170121200242620ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Filter.h" #include "FilterUiInterface.h" #include "OptionsWidget.h" #include "Settings.h" #include "Task.h" #include "CacheDrivenTask.h" #include "OrthogonalRotation.h" #include "PageId.h" #include "ImageId.h" #include "ProjectReader.h" #include "ProjectWriter.h" #include "XmlMarshaller.h" #include "XmlUnmarshaller.h" #ifndef Q_MOC_RUN #include #include #endif #include #include #include #include #include #include #include #include "CommandLine.h" namespace fix_orientation { Filter::Filter( PageSelectionAccessor const& page_selection_accessor) : m_ptrSettings(new Settings) { if (CommandLine::get().isGui()) { m_ptrOptionsWidget.reset( new OptionsWidget(m_ptrSettings, page_selection_accessor) ); } } Filter::~Filter() { } QString Filter::getName() const { return QCoreApplication::translate( "fix_orientation::Filter", "Fix Orientation" ); } PageView Filter::getView() const { return IMAGE_VIEW; } void Filter::performRelinking(AbstractRelinker const& relinker) { m_ptrSettings->performRelinking(relinker); } void Filter::preUpdateUI(FilterUiInterface* ui, PageId const& page_id) { if (m_ptrOptionsWidget.get()) { OrthogonalRotation const rotation( m_ptrSettings->getRotationFor(page_id.imageId()) ); m_ptrOptionsWidget->preUpdateUI(page_id, rotation); ui->setOptionsWidget(m_ptrOptionsWidget.get(), ui->KEEP_OWNERSHIP); } } QDomElement Filter::saveSettings( ProjectWriter const& writer, QDomDocument& doc) const { using namespace boost::lambda; QDomElement filter_el(doc.createElement("fix-orientation")); writer.enumImages( boost::lambda::bind( &Filter::writeImageSettings, this, boost::ref(doc), var(filter_el), boost::lambda::_1, boost::lambda::_2 ) ); return filter_el; } void Filter::loadSettings(ProjectReader const& reader, QDomElement const& filters_el) { m_ptrSettings->clear(); QDomElement filter_el(filters_el.namedItem("fix-orientation").toElement()); QString const 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; int const id = el.attribute("id").toInt(&ok); if (!ok) { continue; } ImageId const image_id(reader.imageId(id)); if (image_id.isNull()) { continue; } OrthogonalRotation const rotation( XmlUnmarshaller::rotation( el.namedItem("rotation").toElement() ) ); m_ptrSettings->applyRotation(image_id, rotation); } } IntrusivePtr Filter::createTask( PageId const& page_id, IntrusivePtr const& next_task, bool const batch_processing) { return IntrusivePtr( new Task( page_id.imageId(), IntrusivePtr(this), m_ptrSettings, next_task, batch_processing ) ); } IntrusivePtr Filter::createCacheDrivenTask( IntrusivePtr const& next_task) { return IntrusivePtr( new CacheDrivenTask(m_ptrSettings, next_task) ); } void Filter::writeImageSettings( QDomDocument& doc, QDomElement& filter_el, ImageId const& image_id, int const numeric_id) const { OrthogonalRotation const rotation(m_ptrSettings->getRotationFor(image_id)); if (rotation.toDegrees() == 0) { return; } 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); } } // namespace fix_orientation scantailor-RELEASE_0_9_12_2/filters/fix_orientation/Filter.h000066400000000000000000000051061271170121200237300ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef FIX_ORIENTATION_FILTER_H_ #define FIX_ORIENTATION_FILTER_H_ #include "NonCopyable.h" #include "AbstractFilter.h" #include "PageView.h" #include "FilterResult.h" #include "IntrusivePtr.h" #include "SafeDeletingQObjectPtr.h" class PageId; class ImageId; class PageSelectionAccessor; class QString; class QDomDocument; class QDomElement; namespace page_split { class Task; class CacheDrivenTask; } namespace fix_orientation { class OptionsWidget; class Task; class CacheDrivenTask; class Settings; /** * \note All methods of this class except the destructor * must be called from the GUI thread only. */ class Filter : public AbstractFilter { DECLARE_NON_COPYABLE(Filter) public: Filter(PageSelectionAccessor const& page_selection_accessor); virtual ~Filter(); virtual QString getName() const; virtual PageView getView() const; virtual void performRelinking(AbstractRelinker const& relinker); virtual void preUpdateUI(FilterUiInterface* ui, PageId const&); virtual QDomElement saveSettings( ProjectWriter const& writer, QDomDocument& doc) const; virtual void loadSettings( ProjectReader const& reader, QDomElement const& filters_el); IntrusivePtr createTask( PageId const& page_id, IntrusivePtr const& next_task, bool batch_processing); IntrusivePtr createCacheDrivenTask( IntrusivePtr const& next_task); OptionsWidget* optionsWidget() { return m_ptrOptionsWidget.get(); }; Settings* getSettings() { return m_ptrSettings.get(); }; private: void writeImageSettings( QDomDocument& doc, QDomElement& filter_el, ImageId const& image_id, int numeric_id) const; IntrusivePtr m_ptrSettings; SafeDeletingQObjectPtr m_ptrOptionsWidget; }; } // fix_orientation #endif scantailor-RELEASE_0_9_12_2/filters/fix_orientation/ImageView.cpp000066400000000000000000000032571271170121200247200ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ImageView.h" #include "ImageView.h.moc" #include "ImageTransformation.h" #include "ImagePresentation.h" namespace fix_orientation { ImageView::ImageView( QImage const& image, QImage const& downscaled_image, ImageTransformation const& 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); } ImageView::~ImageView() { } void ImageView::setPreRotation(OrthogonalRotation const rotation) { if (m_xform.preRotation() == rotation) { return; } m_xform.setPreRotation(rotation); // This should call update() by itself. updateTransform(ImagePresentation(m_xform.transform(), m_xform.resultingPreCropArea())); } } // namespace fix_orientation scantailor-RELEASE_0_9_12_2/filters/fix_orientation/ImageView.h000066400000000000000000000026331271170121200243620ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef FIX_ORIENTATION_IMAGEVIEW_H_ #define FIX_ORIENTATION_IMAGEVIEW_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( QImage const& image, QImage const& downscaled_image, ImageTransformation const& xform); virtual ~ImageView(); public slots: void setPreRotation(OrthogonalRotation rotation); private: DragHandler m_dragHandler; ZoomHandler m_zoomHandler; ImageTransformation m_xform; }; } // namespace fix_orientation #endif scantailor-RELEASE_0_9_12_2/filters/fix_orientation/OptionsWidget.cpp000066400000000000000000000074501271170121200256410ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "OptionsWidget.h" #include "OptionsWidget.h.moc" #include "Filter.h" #include "ApplyDialog.h" #include "Settings.h" #include "ProjectPages.h" #include "ImageId.h" #include "PageId.h" #ifndef Q_MOC_RUN #include #endif #include #include namespace fix_orientation { OptionsWidget::OptionsWidget( IntrusivePtr const& settings, PageSelectionAccessor const& page_selection_accessor) : m_ptrSettings(settings), m_pageSelectionAccessor(page_selection_accessor) { setupUi(this); 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())); } OptionsWidget::~OptionsWidget() { } void OptionsWidget::preUpdateUI( PageId const& page_id, OrthogonalRotation const rotation) { m_pageId = page_id; m_rotation = rotation; setRotationPixmap(); } void OptionsWidget::postUpdateUI(OrthogonalRotation const rotation) { setRotation(rotation); } void OptionsWidget::rotateLeft() { OrthogonalRotation rotation(m_rotation); rotation.prevClockwiseDirection(); setRotation(rotation); } void OptionsWidget::rotateRight() { OrthogonalRotation rotation(m_rotation); rotation.nextClockwiseDirection(); setRotation(rotation); } void OptionsWidget::resetRotation() { setRotation(OrthogonalRotation()); } void OptionsWidget::showApplyToDialog() { ApplyDialog* dialog = new ApplyDialog( this, m_pageId, m_pageSelectionAccessor ); dialog->setAttribute(Qt::WA_DeleteOnClose); connect( dialog, SIGNAL(appliedTo(std::set const&)), this, SLOT(appliedTo(std::set const&)) ); connect( dialog, SIGNAL(appliedToAllPages(std::set const&)), this, SLOT(appliedToAllPages(std::set const&)) ); dialog->show(); } void OptionsWidget::appliedTo(std::set const& pages) { if (pages.empty()) { return; } m_ptrSettings->applyRotation(pages, m_rotation); BOOST_FOREACH(PageId const& page_id, pages) { emit invalidateThumbnail(page_id); } } void OptionsWidget::appliedToAllPages(std::set const& pages) { m_ptrSettings->applyRotation(pages, m_rotation); emit invalidateAllThumbnails(); } void OptionsWidget::setRotation(OrthogonalRotation const rotation) { if (rotation == m_rotation) { return; } m_rotation = rotation; setRotationPixmap(); m_ptrSettings->applyRotation(m_pageId.imageId(), rotation); emit rotated(rotation); emit invalidateThumbnail(m_pageId); } void OptionsWidget::setRotationPixmap() { char const* path = 0; 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)); } } // namespace fix_orientation scantailor-RELEASE_0_9_12_2/filters/fix_orientation/OptionsWidget.h000066400000000000000000000037551271170121200253120ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef FIX_ORIENTATION_OPTIONSWIDGET_H_ #define FIX_ORIENTATION_OPTIONSWIDGET_H_ #include "ui_OrientationOptionsWidget.h" #include "FilterOptionsWidget.h" #include "OrthogonalRotation.h" #include "PageId.h" #include "IntrusivePtr.h" #include "PageSelectionAccessor.h" namespace fix_orientation { class Settings; class OptionsWidget : public FilterOptionsWidget, private Ui::OrientationOptionsWidget { Q_OBJECT public: OptionsWidget(IntrusivePtr const& settings, PageSelectionAccessor const& page_selection_accessor); virtual ~OptionsWidget(); void preUpdateUI(PageId const& page_id, OrthogonalRotation rotation); void postUpdateUI(OrthogonalRotation rotation); signals: void rotated(OrthogonalRotation rotation); private slots: void rotateLeft(); void rotateRight(); void resetRotation(); void showApplyToDialog(); void appliedTo(std::set const& pages); void appliedToAllPages(std::set const& pages); private: void setRotation(OrthogonalRotation rotation); void setRotationPixmap(); IntrusivePtr m_ptrSettings; PageSelectionAccessor m_pageSelectionAccessor; PageId m_pageId; OrthogonalRotation m_rotation; }; } // namespace fix_orientation #endif scantailor-RELEASE_0_9_12_2/filters/fix_orientation/Settings.cpp000066400000000000000000000047511271170121200246430ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Settings.h" #include "Utils.h" #include "RelinkablePath.h" #include "AbstractRelinker.h" #ifndef Q_MOC_RUN #include #endif namespace fix_orientation { Settings::Settings() { } Settings::~Settings() { } void Settings::clear() { QMutexLocker locker(&m_mutex); m_perImageRotation.clear(); } void Settings::performRelinking(AbstractRelinker const& relinker) { QMutexLocker locker(&m_mutex); PerImageRotation new_rotations; BOOST_FOREACH(PerImageRotation::value_type const& kv, m_perImageRotation) { RelinkablePath const 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); } void Settings::applyRotation( ImageId const& image_id, OrthogonalRotation const rotation) { QMutexLocker locker(&m_mutex); setImageRotationLocked(image_id, rotation); } void Settings::applyRotation( std::set const& pages, OrthogonalRotation const rotation) { QMutexLocker locker(&m_mutex); BOOST_FOREACH(PageId const& page, pages) { setImageRotationLocked(page.imageId(), rotation); } } OrthogonalRotation Settings::getRotationFor(ImageId const& image_id) const { QMutexLocker locker(&m_mutex); PerImageRotation::const_iterator it(m_perImageRotation.find(image_id)); if (it != m_perImageRotation.end()) { return it->second; } else { return OrthogonalRotation(); } } void Settings::setImageRotationLocked( ImageId const& image_id, OrthogonalRotation const& rotation) { Utils::mapSetValue(m_perImageRotation, image_id, rotation); } } // namespace fix_orientation scantailor-RELEASE_0_9_12_2/filters/fix_orientation/Settings.h000066400000000000000000000034061271170121200243040ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef FIX_ORIENTATION_SETTINGS_H_ #define FIX_ORIENTATION_SETTINGS_H_ #include "RefCountable.h" #include "NonCopyable.h" #include "OrthogonalRotation.h" #include "ImageId.h" #include "PageId.h" #include #include #include class AbstractRelinker; namespace fix_orientation { class Settings : public RefCountable { DECLARE_NON_COPYABLE(Settings) public: Settings(); virtual ~Settings(); void clear(); void performRelinking(AbstractRelinker const& relinker); void applyRotation(ImageId const& image_id, OrthogonalRotation rotation); void applyRotation(std::set const& pages, OrthogonalRotation rotation); OrthogonalRotation getRotationFor(ImageId const& image_id) const; private: typedef std::map PerImageRotation; void setImageRotationLocked( ImageId const& image_id, OrthogonalRotation const& rotation); mutable QMutex m_mutex; PerImageRotation m_perImageRotation; }; } // namespace fix_orientation #endif scantailor-RELEASE_0_9_12_2/filters/fix_orientation/Task.cpp000066400000000000000000000070271271170121200237440ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Task.h" #include "Filter.h" #include "OptionsWidget.h" #include "Settings.h" #include "FilterData.h" #include "ImageTransformation.h" #include "filters/page_split/Task.h" #include "TaskStatus.h" #include "ImageView.h" #include "FilterUiInterface.h" #include #include namespace fix_orientation { using imageproc::BinaryThreshold; class Task::UiUpdater : public FilterResult { public: UiUpdater(IntrusivePtr const& filter, QImage const& image, ImageId const& image_id, ImageTransformation const& xform, bool batch_processing); virtual void updateUI(FilterUiInterface* wnd); virtual IntrusivePtr filter() { return m_ptrFilter; } private: IntrusivePtr m_ptrFilter; QImage m_image; QImage m_downscaledImage; ImageId m_imageId; ImageTransformation m_xform; bool m_batchProcessing; }; Task::Task( ImageId const& image_id, IntrusivePtr const& filter, IntrusivePtr const& settings, IntrusivePtr const& next_task, bool const batch_processing) : m_ptrFilter(filter), m_ptrNextTask(next_task), m_ptrSettings(settings), m_imageId(image_id), m_batchProcessing(batch_processing) { } Task::~Task() { } FilterResultPtr Task::process(TaskStatus const& status, FilterData const& data) { // This function is executed from the worker thread. status.throwIfCancelled(); ImageTransformation xform(data.xform()); xform.setPreRotation(m_ptrSettings->getRotationFor(m_imageId)); if (m_ptrNextTask) { return m_ptrNextTask->process(status, FilterData(data, xform)); } else { return FilterResultPtr( new UiUpdater( m_ptrFilter, data.origImage(), m_imageId, xform, m_batchProcessing ) ); } } /*============================ Task::UiUpdater ========================*/ Task::UiUpdater::UiUpdater( IntrusivePtr const& filter, QImage const& image, ImageId const& image_id, ImageTransformation const& xform, bool const batch_processing) : m_ptrFilter(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); ui->invalidateThumbnail(PageId(m_imageId)); if (m_batchProcessing) { return; } ImageView* 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 scantailor-RELEASE_0_9_12_2/filters/fix_orientation/Task.h000066400000000000000000000033561271170121200234120ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef FIX_ORIENTATION_TASK_H_ #define FIX_ORIENTATION_TASK_H_ #include "NonCopyable.h" #include "RefCountable.h" #include "FilterResult.h" #include "IntrusivePtr.h" #include "ImageId.h" class TaskStatus; class FilterData; class QImage; namespace page_split { class Task; } namespace fix_orientation { class Filter; class Settings; class Task : public RefCountable { DECLARE_NON_COPYABLE(Task) public: Task( ImageId const& image_id, IntrusivePtr const& filter, IntrusivePtr const& settings, IntrusivePtr const& next_task, bool batch_processing); virtual ~Task(); FilterResultPtr process(TaskStatus const& status, FilterData const& data); private: class UiUpdater; IntrusivePtr m_ptrFilter; IntrusivePtr m_ptrNextTask; // if null, this task is the final one IntrusivePtr m_ptrSettings; ImageId m_imageId; bool m_batchProcessing; }; } // namespace fix_orientation #endif scantailor-RELEASE_0_9_12_2/filters/fix_orientation/ui/000077500000000000000000000000001271170121200227455ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/filters/fix_orientation/ui/OrientationApplyDialog.ui000066400000000000000000000172461271170121200277370ustar00rootroot00000000000000 OrientationApplyDialog Qt::WindowModal 0 0 364 316 Fix Orientation Apply to This page only (already applied) true All pages This page and the following ones 0 0 0 0 Every other page Qt::Horizontal QSizePolicy::Fixed 30 0 7 The current page will be included. 0 0 Selected pages Qt::Horizontal QSizePolicy::Fixed 30 0 7 Use Ctrl+Click / Shift+Click to select multiple pages. 0 0 Every other selected page Qt::Horizontal QSizePolicy::Fixed 30 0 7 The current page will be included. Qt::Vertical 17 0 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox rejected() OrientationApplyDialog reject() 319 252 286 256 scantailor-RELEASE_0_9_12_2/filters/fix_orientation/ui/OrientationOptionsWidget.ui000066400000000000000000000134271271170121200303260ustar00rootroot00000000000000 OrientationOptionsWidget 0 0 224 272 Form Rotate Qt::Horizontal 40 20 ... :/icons/object-rotate-left.png 24 24 ... :/icons/object-rotate-right.png 24 24 Qt::Horizontal 40 20 Qt::Horizontal 40 20 :/icons/big-up-arrow.png Qt::Horizontal 40 20 Qt::Horizontal 40 20 Reset Qt::Horizontal 40 20 Scope Qt::Horizontal 40 20 Apply to ... Qt::Horizontal 40 20 Qt::Vertical 20 40 scantailor-RELEASE_0_9_12_2/filters/output/000077500000000000000000000000001271170121200204675ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/filters/output/ApplyColorsDialog.cpp000066400000000000000000000042121271170121200245610ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ApplyColorsDialog.h" #include "ApplyColorsDialog.h.moc" #include "PageSelectionAccessor.h" #include namespace output { ApplyColorsDialog::ApplyColorsDialog( QWidget* parent, PageId const& cur_page, PageSelectionAccessor const& 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); } connect(buttonBox, SIGNAL(accepted()), this, SLOT(onSubmit())); } ApplyColorsDialog::~ApplyColorsDialog() { } void ApplyColorsDialog::onSubmit() { 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(); return; } emit accepted(pages); // We assume the default connection from accepted() to accept() // was removed. accept(); } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/ApplyColorsDialog.h000066400000000000000000000030451271170121200242310ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_APPLYCOLORSDIALOG_H_ #define OUTPUT_APPLYCOLORSDIALOG_H_ #include "ui_OutputApplyColorsDialog.h" #include "PageId.h" #include "PageSequence.h" #include "IntrusivePtr.h" #include #include class PageSelectionAccessor; class QButtonGroup; namespace output { class ApplyColorsDialog : public QDialog, private Ui::OutputApplyColorsDialog { Q_OBJECT public: ApplyColorsDialog(QWidget* parent, PageId const& page_id, PageSelectionAccessor const& page_selection_accessor); virtual ~ApplyColorsDialog(); signals: void accepted(std::set const& pages); private slots: void onSubmit(); private: PageSequence m_pages; std::set m_selectedPages; PageId m_curPage; QButtonGroup* m_pScopeGroup; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/BlackWhiteOptions.cpp000066400000000000000000000031171271170121200245660ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "BlackWhiteOptions.h" #include #include #include namespace output { BlackWhiteOptions::BlackWhiteOptions() : m_thresholdAdjustment(0) { } BlackWhiteOptions::BlackWhiteOptions(QDomElement const& el) : m_thresholdAdjustment(el.attribute("thresholdAdj").toInt()) { } QDomElement BlackWhiteOptions::toXml(QDomDocument& doc, QString const& name) const { QDomElement el(doc.createElement(name)); el.setAttribute("thresholdAdj", m_thresholdAdjustment); return el; } bool BlackWhiteOptions::operator==(BlackWhiteOptions const& other) const { if (m_thresholdAdjustment != other.m_thresholdAdjustment) { return false; } return true; } bool BlackWhiteOptions::operator!=(BlackWhiteOptions const& other) const { return !(*this == other); } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/BlackWhiteOptions.h000066400000000000000000000026521271170121200242360ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_BLACK_WHITE_OPTIONS_H_ #define OUTPUT_BLACK_WHITE_OPTIONS_H_ class QString; class QDomDocument; class QDomElement; namespace output { class BlackWhiteOptions { public: BlackWhiteOptions(); BlackWhiteOptions(QDomElement const& el); QDomElement toXml(QDomDocument& doc, QString const& name) const; int thresholdAdjustment() const { return m_thresholdAdjustment; } void setThresholdAdjustment(int val) { m_thresholdAdjustment = val; } bool operator==(BlackWhiteOptions const& other) const; bool operator!=(BlackWhiteOptions const& other) const; private: int m_thresholdAdjustment; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/CMakeLists.txt000066400000000000000000000037651271170121200232420ustar00rootroot00000000000000PROJECT("Output Filter") INCLUDE_DIRECTORIES(BEFORE "${CMAKE_CURRENT_BINARY_DIR}") FILE(GLOB ui_files "ui/*.ui") QT4_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 ColorGrayscaleOptions.cpp ColorGrayscaleOptions.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 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 DewarpingMode.cpp DewarpingMode.h ChangeDewarpingDialog.cpp ChangeDewarpingDialog.h DepthPerception.cpp DepthPerception.h ) SOURCE_GROUP("Sources" FILES ${sources}) QT4_AUTOMOC(${sources}) ADD_LIBRARY(output STATIC ${sources} ${ui_sources}) ADD_DEPENDENCIES(output toplevel_ui_sources) TRANSLATION_SOURCES(scantailor ${sources} ${ui_files})scantailor-RELEASE_0_9_12_2/filters/output/CacheDrivenTask.cpp000066400000000000000000000102571271170121200241760ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "CacheDrivenTask.h" #include "OutputGenerator.h" #include "PictureZoneComparator.h" #include "FillZoneComparator.h" #include "Settings.h" #include "Params.h" #include "Thumbnail.h" #include "IncompleteThumbnail.h" #include "ImageTransformation.h" #include "PageInfo.h" #include "PageId.h" #include "ImageId.h" #include "Dpi.h" #include "Utils.h" #include "filter_dc/AbstractFilterDataCollector.h" #include "filter_dc/ThumbnailCollector.h" #include #include #include #include #include namespace output { CacheDrivenTask::CacheDrivenTask( IntrusivePtr const& settings, OutputFileNameGenerator const& out_file_name_gen) : m_ptrSettings(settings), m_outFileNameGen(out_file_name_gen) { } CacheDrivenTask::~CacheDrivenTask() { } void CacheDrivenTask::process( PageInfo const& page_info, AbstractFilterDataCollector* collector, ImageTransformation const& xform, QPolygonF const& content_rect_phys) { if (ThumbnailCollector* thumb_col = dynamic_cast(collector)) { QString const out_file_path(m_outFileNameGen.filePathFor(page_info.id())); Params const params(m_ptrSettings->getParams(page_info.id())); ImageTransformation new_xform(xform); new_xform.postScaleToDpi(params.outputDpi()); bool need_reprocess = false; do { // Just to be able to break from it. std::auto_ptr stored_output_params( m_ptrSettings->getOutputParams(page_info.id()) ); if (!stored_output_params.get()) { need_reprocess = true; break; } OutputGenerator const generator( params.outputDpi(), params.colorParams(), params.despeckleLevel(), new_xform, content_rect_phys ); OutputImageParams const new_output_image_params( generator.outputImageSize(), generator.outputContentRect(), new_xform, params.outputDpi(), params.colorParams(), params.dewarpingMode(), params.distortionModel(), params.depthPerception(), params.despeckleLevel() ); if (!stored_output_params->outputImageParams().matches(new_output_image_params)) { need_reprocess = true; break; } ZoneSet const new_picture_zones(m_ptrSettings->pictureZonesForPage(page_info.id())); if (!PictureZoneComparator::equal(stored_output_params->pictureZones(), new_picture_zones)) { need_reprocess = true; break; } ZoneSet const new_fill_zones(m_ptrSettings->fillZonesForPage(page_info.id())); if (!FillZoneComparator::equal(stored_output_params->fillZones(), new_fill_zones)) { need_reprocess = true; break; } QFileInfo const out_file_info(out_file_path); if (!out_file_info.exists()) { need_reprocess = true; break; } if (!stored_output_params->outputFileParams().matches(OutputFileParams(out_file_info))) { need_reprocess = true; break; } } while (false); if (need_reprocess) { thumb_col->processThumbnail( std::auto_ptr( new IncompleteThumbnail( thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), page_info.imageId(), new_xform ) ) ); } else { ImageTransformation const out_xform( new_xform.resultingRect(), params.outputDpi() ); thumb_col->processThumbnail( std::auto_ptr( new Thumbnail( thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), ImageId(out_file_path), out_xform ) ) ); } } } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/CacheDrivenTask.h000066400000000000000000000031451271170121200236410ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_CACHEDRIVENTASK_H_ #define OUTPUT_CACHEDRIVENTASK_H_ #include "NonCopyable.h" #include "RefCountable.h" #include "IntrusivePtr.h" #include "OutputFileNameGenerator.h" class QPolygonF; class PageInfo; class AbstractFilterDataCollector; class ImageTransformation; namespace output { class Settings; class CacheDrivenTask : public RefCountable { DECLARE_NON_COPYABLE(CacheDrivenTask) public: CacheDrivenTask( IntrusivePtr const& settings, OutputFileNameGenerator const& out_file_name_gen); virtual ~CacheDrivenTask(); void process( PageInfo const& page_info, AbstractFilterDataCollector* collector, ImageTransformation const& xform, QPolygonF const& content_rect_phys); private: IntrusivePtr m_ptrSettings; OutputFileNameGenerator m_outFileNameGen; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/ChangeDewarpingDialog.cpp000066400000000000000000000056751271170121200253560ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ChangeDewarpingDialog.h" #include "ChangeDewarpingDialog.h.moc" #include "PageSelectionAccessor.h" #include "QtSignalForwarder.h" #ifndef Q_MOC_RUN #include #include #endif namespace output { ChangeDewarpingDialog::ChangeDewarpingDialog( QWidget* parent, PageId const& cur_page, DewarpingMode const& mode, PageSelectionAccessor const& page_selection_accessor) : QDialog(parent), m_pages(page_selection_accessor.allPages()), m_selectedPages(page_selection_accessor.selectedPages()), m_curPage(cur_page), m_mode(mode), 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 (mode) { case DewarpingMode::OFF: ui.offRB->setChecked(true); break; case DewarpingMode::AUTO: ui.autoRB->setChecked(true); break; case DewarpingMode::MANUAL: ui.manualRB->setChecked(true); break; } // No, we don't leak memory here. new QtSignalForwarder(ui.offRB, SIGNAL(clicked(bool)), var(m_mode) = DewarpingMode::OFF); new QtSignalForwarder(ui.autoRB, SIGNAL(clicked(bool)), var(m_mode) = DewarpingMode::AUTO); new QtSignalForwarder(ui.manualRB, SIGNAL(clicked(bool)), var(m_mode) = DewarpingMode::MANUAL); connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(onSubmit())); } ChangeDewarpingDialog::~ChangeDewarpingDialog() { } void ChangeDewarpingDialog::onSubmit() { std::set pages; 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_mode); accept(); return; } emit accepted(pages, m_mode); // We assume the default connection from accepted() to accept() // was removed. accept(); } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/ChangeDewarpingDialog.h000066400000000000000000000033021271170121200250040ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_CHANGE_DEWARPING_DIALOG_H_ #define OUTPUT_CHANGE_DEWARPING_DIALOG_H_ #include "ui_OutputChangeDewarpingDialog.h" #include "DewarpingMode.h" #include "PageId.h" #include "PageSequence.h" #include "IntrusivePtr.h" #include #include #include class PageSelectionAccessor; class QButtonGroup; namespace output { class ChangeDewarpingDialog : public QDialog { Q_OBJECT public: ChangeDewarpingDialog( QWidget* parent, PageId const& cur_page, DewarpingMode const& mode, PageSelectionAccessor const& page_selection_accessor); virtual ~ChangeDewarpingDialog(); signals: void accepted(std::set const& pages, DewarpingMode const& mode); private slots: void onSubmit(); private: Ui::OutputChangeDewarpingDialog ui; PageSequence m_pages; std::set m_selectedPages; PageId m_curPage; DewarpingMode m_mode; QButtonGroup* m_pScopeGroup; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/ChangeDpiDialog.cpp000066400000000000000000000110331271170121200241330ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ChangeDpiDialog.h" #include "ChangeDpiDialog.h.moc" #include "PageSelectionAccessor.h" #include "Dpi.h" #include #include #include #include #include #include #ifndef Q_MOC_RUN #include #endif #include namespace output { ChangeDpiDialog::ChangeDpiDialog( QWidget* parent, Dpi const& dpi, PageId const& cur_page, PageSelectionAccessor const& 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 int const common_dpis[] = { 300, 400, 600 }; int const requested_dpi = std::max(dpi.horizontal(), dpi.vertical()); m_customDpiString = QString::number(requested_dpi); int selected_index = -1; BOOST_FOREACH(int const cdpi, common_dpis) { if (cdpi == requested_dpi) { selected_index = dpiSelector->count(); } QString const 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(QString const&)), this, SLOT(dpiEditTextChanged(QString const&)) ); connect(buttonBox, SIGNAL(accepted()), this, SLOT(onSubmit())); } ChangeDpiDialog::~ChangeDpiDialog() { } void ChangeDpiDialog::dpiSelectionChanged(int const 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) ); } } void ChangeDpiDialog::dpiEditTextChanged(QString const& text) { if (dpiSelector->currentIndex() == m_customItemIdx) { m_customDpiString = text; } } void ChangeDpiDialog::onSubmit() { QString const dpi_str(dpiSelector->currentText()); if (dpi_str.isEmpty()) { QMessageBox::warning( this, tr("Error"), tr("DPI is not set.") ); return; } int const dpi = dpi_str.toInt(); if (dpi < 72) { QMessageBox::warning( this, tr("Error"), tr("DPI is too low!") ); return; } if (dpi > 1200) { QMessageBox::warning( this, tr("Error"), tr("DPI is too high!") ); return; } 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(); return; } emit accepted(pages, Dpi(dpi, dpi)); // We assume the default connection from accepted() to accept() // was removed. accept(); } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/ChangeDpiDialog.h000066400000000000000000000033321271170121200236030ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_CHANGEDPIDIALOG_H_ #define OUTPUT_CHANGEDPIDIALOG_H_ #include "ui_OutputChangeDpiDialog.h" #include "PageId.h" #include "PageSequence.h" #include "IntrusivePtr.h" #include #include #include class PageSelectionAccessor; class QButtonGroup; class Dpi; namespace output { class ChangeDpiDialog : public QDialog, private Ui::OutputChangeDpiDialog { Q_OBJECT public: ChangeDpiDialog( QWidget* parent, Dpi const& dpi, PageId const& cur_page, PageSelectionAccessor const& page_selection_accessor); virtual ~ChangeDpiDialog(); signals: void accepted(std::set const& pages, Dpi const& dpi); private slots: void dpiSelectionChanged(int index); void dpiEditTextChanged(QString const& text); void onSubmit(); private: PageSequence m_pages; std::set m_selectedPages; PageId m_curPage; QButtonGroup* m_pScopeGroup; int m_customItemIdx; QString m_customDpiString; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/ColorGrayscaleOptions.cpp000066400000000000000000000034031271170121200254600ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ColorGrayscaleOptions.h" #include #include #include namespace output { ColorGrayscaleOptions::ColorGrayscaleOptions(QDomElement const& el) : m_whiteMargins(el.attribute("whiteMargins") == "1"), m_normalizeIllumination(el.attribute("normalizeIllumination") == "1") { } QDomElement ColorGrayscaleOptions::toXml(QDomDocument& doc, QString const& name) const { QDomElement el(doc.createElement(name)); el.setAttribute("whiteMargins", m_whiteMargins ? "1" : "0"); el.setAttribute("normalizeIllumination", m_normalizeIllumination ? "1" : "0"); return el; } bool ColorGrayscaleOptions::operator==(ColorGrayscaleOptions const& other) const { if (m_whiteMargins != other.m_whiteMargins) { return false; } if (m_normalizeIllumination != other.m_normalizeIllumination) { return false; } return true; } bool ColorGrayscaleOptions::operator!=(ColorGrayscaleOptions const& other) const { return !(*this == other); } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/ColorGrayscaleOptions.h000066400000000000000000000032251271170121200251270ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_COLOR_GRAYSCALE_OPTIONS_H_ #define OUTPUT_COLOR_GRAYSCALE_OPTIONS_H_ class QString; class QDomDocument; class QDomElement; namespace output { class ColorGrayscaleOptions { public: ColorGrayscaleOptions() : m_whiteMargins(false), m_normalizeIllumination(false) {} ColorGrayscaleOptions(QDomElement const& el); QDomElement toXml(QDomDocument& doc, QString const& name) const; bool whiteMargins() const { return m_whiteMargins; } void setWhiteMargins(bool val) { m_whiteMargins = val; } bool normalizeIllumination() const { return m_normalizeIllumination; } void setNormalizeIllumination(bool val) { m_normalizeIllumination = val; } bool operator==(ColorGrayscaleOptions const& other) const; bool operator!=(ColorGrayscaleOptions const& other) const; private: bool m_whiteMargins; bool m_normalizeIllumination; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/ColorParams.cpp000066400000000000000000000041331271170121200234160ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ColorParams.h" #include #include #include namespace output { ColorParams::ColorParams(QDomElement const& el) : m_colorMode(parseColorMode(el.attribute("colorMode"))), m_colorGrayscaleOptions(el.namedItem("color-or-grayscale").toElement()), m_bwOptions(el.namedItem("bw").toElement()) { } QDomElement ColorParams::toXml(QDomDocument& doc, QString const& name) const { QDomElement el(doc.createElement(name)); el.setAttribute("colorMode", formatColorMode(m_colorMode)); el.appendChild(m_colorGrayscaleOptions.toXml(doc, "color-or-grayscale")); el.appendChild(m_bwOptions.toXml(doc, "bw")); return el; } ColorParams::ColorMode ColorParams::parseColorMode(QString const& str) { if (str == "bw") { return BLACK_AND_WHITE; } else if (str == "bitonal") { // Backwards compatibility. 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(ColorParams::ColorMode const mode) { char const* str = ""; switch (mode) { case BLACK_AND_WHITE: str = "bw"; break; case COLOR_GRAYSCALE: str = "colorOrGray"; break; case MIXED: str = "mixed"; break; } return QString::fromAscii(str); } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/ColorParams.h000066400000000000000000000036671271170121200230760ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_COLORPARAMS_H_ #define OUTPUT_COLORPARAMS_H_ #include "ColorGrayscaleOptions.h" #include "BlackWhiteOptions.h" class QDomDocument; class QDomElement; namespace output { class ColorParams { public: enum ColorMode { BLACK_AND_WHITE, COLOR_GRAYSCALE, MIXED }; ColorParams(): m_colorMode(BLACK_AND_WHITE) {} ColorParams(QDomElement const& el); QDomElement toXml(QDomDocument& doc, QString const& name) const; ColorMode colorMode() const { return m_colorMode; } void setColorMode(ColorMode mode) { m_colorMode = mode; } ColorGrayscaleOptions const& colorGrayscaleOptions() const { return m_colorGrayscaleOptions; } void setColorGrayscaleOptions(ColorGrayscaleOptions const& opt) { m_colorGrayscaleOptions = opt; } BlackWhiteOptions const& blackWhiteOptions() const { return m_bwOptions; } void setBlackWhiteOptions(BlackWhiteOptions const& opt) { m_bwOptions = opt; } private: static ColorMode parseColorMode(QString const& str); static QString formatColorMode(ColorMode mode); ColorMode m_colorMode; ColorGrayscaleOptions m_colorGrayscaleOptions; BlackWhiteOptions m_bwOptions; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/ColorPickupInteraction.cpp000066400000000000000000000616251271170121200256370ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ColorPickupInteraction.h" #include "ZoneInteractionContext.h" #include "InteractionState.h" #include "ImageViewBase.h" #include "PropertySet.h" #include "ScopedIncDec.h" #include #include #include #include #include #include #include #include #include #include #include #include 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.")); } void ColorPickupInteraction::startInteraction( EditableZoneSet::Zone const& zone, InteractionState& interaction) { typedef FillColorProperty FCP; m_ptrFillColorProp = zone.properties()->locateOrCreate(); interaction.capture(m_interaction); } bool ColorPickupInteraction::isActive(InteractionState const& interaction) const { return interaction.capturedBy(m_interaction); } void ColorPickupInteraction::onPaint( QPainter& painter, InteractionState const& interaction) { if (m_dontDrawCircle) { return; } 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()); } void ColorPickupInteraction::onMousePressEvent( QMouseEvent* event, InteractionState& interaction) { 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(); } void ColorPickupInteraction::onKeyPressEvent( QKeyEvent* event, InteractionState& interaction) { if (event->key() == Qt::Key_Escape) { event->accept(); switchToDefaultInteraction(); } } void ColorPickupInteraction::takeColor() { ScopedIncDec const guard(m_dontDrawCircle); QRect const rect(targetBoundingRect()); QPixmap const pixmap(QPixmap::grabWidget(&m_rContext.imageView(), rect)); if (pixmap.isNull()) { return; } QImage const image(pixmap.toImage().convertToFormat(QImage::Format_RGB32)); int const width = rect.width(); int const height = rect.height(); int const x_center = width / 2; int const y_center = height / 2; int const sqdist_threshold = x_center * y_center; uint32_t const* line = (uint32_t*)image.bits(); int const 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) { int const dy = y - y_center; int const dy_sq = dy * dy; for (int x = 0; x < width; ++x) { int const dx = x - x_center; int const dx_sq = dx * dx; int const sqdist = dy_sq + dx_sq; if (sqdist <= sqdist_threshold) { uint32_t const color = line[x]; bitmixed_colors.push_back(bitMixColor(color)); } } } if (bitmixed_colors.empty()) { return; } std::vector::iterator half_pos(bitmixed_colors.begin() + bitmixed_colors.size()/2); std::nth_element(bitmixed_colors.begin(), half_pos, bitmixed_colors.end()); QColor const color(bitUnmixColor(*half_pos)); m_ptrFillColorProp->setColor(color); // Update default properties. PropertySet default_props(m_rZones.defaultProperties()); default_props.locateOrCreate()->setColor(color); m_rZones.setDefaultProperties(default_props); m_rZones.commit(); } QRect ColorPickupInteraction::targetBoundingRect() const { QPoint const 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); return rect; } void ColorPickupInteraction::switchToDefaultInteraction() { m_ptrFillColorProp.reset(); m_interaction.release(); makePeerPreceeder(*m_rContext.createDefaultInteraction()); unlink(); m_rContext.imageView().update(); } uint32_t ColorPickupInteraction::bitMixColor(uint32_t const color) { return ( m_sBitMixingLUT[0][(color >> 16) & 0xff] | m_sBitMixingLUT[1][(color >> 8) & 0xff] | m_sBitMixingLUT[2][color & 0xff] ); } uint32_t ColorPickupInteraction::bitUnmixColor(uint32_t const mixed) { return ( m_sBitUnmixingLUT[0][(mixed >> 16) & 0xff] | m_sBitUnmixingLUT[1][(mixed >> 8) & 0xff] | m_sBitUnmixingLUT[2][mixed & 0xff] ); } /** * Generated like this: * \code * uint32_t const bit23 = 1 << 23; * int const bit7 = 1 << 7; * for (int partition = 0; partition < 3; ++partition) { * for (int sample = 0; sample < 256; ++sample) { * uint32_t accum = 0; * for (int bit = 0; bit < 8; ++bit) { * if (sample & (bit7 >> bit)) { * accum |= bit23 >> (bit * 3 + partition); * } * } * m_sBitMixingLUT[partition][sample] = accum; * } * } * \endcode */ uint32_t const 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 } }; /** * Generated like this: * \code * uint32_t const bit23 = 1 << 23; * int const bit7 = 1 << 7; * for (int partition = 0; partition < 3; ++partition) { * for (int sample = 0; sample < 256; ++sample) { * uint32_t accum = 0; * for (int bit = 0; bit < 8; ++bit) { * if (sample & (bit7 >> bit)) { * int const src_offset = bit + partition * 8; * int const channel = src_offset % 3; * int const dst_offset = src_offset / 3; * accum |= bit23 >> (channel * 8 + dst_offset); * } * } * m_sBitUnmixingLUT[partition][sample] = accum; * } * } * \endcode */ uint32_t const 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 } }; } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/ColorPickupInteraction.h000066400000000000000000000044171271170121200253000ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #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 "IntrusivePtr.h" #include #include #include class ZoneInteractionContext; namespace output { class ColorPickupInteraction : public InteractionHandler { Q_DECLARE_TR_FUNCTIONS(ColorPickupInteraction) public: ColorPickupInteraction(EditableZoneSet& zones, ZoneInteractionContext& context); void startInteraction( EditableZoneSet::Zone const& zone, InteractionState& interaction); bool isActive(InteractionState const& interaction) const; protected: virtual void onPaint(QPainter& painter, InteractionState const& interaction); virtual void onMousePressEvent(QMouseEvent* event, InteractionState& interaction); virtual void onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction); virtual void onKeyPressEvent(QKeyEvent* event, InteractionState& interaction); private: void takeColor(); QRect targetBoundingRect() const; void switchToDefaultInteraction(); static uint32_t bitMixColor(uint32_t color); static uint32_t bitUnmixColor(uint32_t mixed); EditableZoneSet& m_rZones; ZoneInteractionContext& m_rContext; InteractionState::Captor m_interaction; IntrusivePtr m_ptrFillColorProp; int m_dontDrawCircle; static uint32_t const m_sBitMixingLUT[3][256]; static uint32_t const m_sBitUnmixingLUT[3][256]; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/DepthPerception.cpp000066400000000000000000000027331271170121200242750ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "DepthPerception.h" #include "../../Utils.h" #include namespace output { DepthPerception::DepthPerception() : m_value(defaultValue()) { } DepthPerception::DepthPerception(double value) : m_value(qBound(minValue(), value, maxValue())) { } DepthPerception::DepthPerception(QString const& from_string) { 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); } void DepthPerception::setValue(double value) { m_value = qBound(minValue(), value, maxValue()); } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/DepthPerception.h000066400000000000000000000025711271170121200237420ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_DEPTH_PERCEPTION_H_ #define OUTPUT_DEPTH_PERCEPTION_H_ #include namespace output { /** * \see imageproc::CylindricalSurfaceDewarper */ class DepthPerception { public: DepthPerception(); DepthPerception(double value); explicit DepthPerception(QString const& from_string); QString toString() const; void setValue(double value); double value() const { return m_value; } static double minValue() { return 1.0; } static double defaultValue() { return 2.0; } static double maxValue() { return 3.0; } private: double m_value; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/DespeckleLevel.cpp000066400000000000000000000026621271170121200240700ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "DespeckleLevel.h" #include namespace output { QString despeckleLevelToString(DespeckleLevel const level) { switch (level) { case DESPECKLE_OFF: return "off"; case DESPECKLE_CAUTIOUS: return "cautious"; case DESPECKLE_NORMAL: return "normal"; case DESPECKLE_AGGRESSIVE: return "aggressive"; } return QString(); } DespeckleLevel despeckleLevelFromString(QString const& 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; } } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/DespeckleLevel.h000066400000000000000000000021651271170121200235330ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DESPECKLE_LEVEL_H_ #define DESPECKLE_LEVEL_H_ class QString; namespace output { enum DespeckleLevel { DESPECKLE_OFF, DESPECKLE_CAUTIOUS, DESPECKLE_NORMAL, DESPECKLE_AGGRESSIVE }; QString despeckleLevelToString(DespeckleLevel level); DespeckleLevel despeckleLevelFromString(QString const& str); } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/DespeckleState.cpp000066400000000000000000000100461271170121200240740ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "DespeckleState.h" #include "DespeckleVisualization.h" #include "Despeckle.h" #include "TaskStatus.h" #include "DebugImages.h" #include "imageproc/RasterOp.h" #include #include using namespace imageproc; namespace output { DespeckleState::DespeckleState( QImage const& output, imageproc::BinaryImage const& speckles, DespeckleLevel level, Dpi const& dpi) : 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); } DespeckleState DespeckleState::redespeckle( DespeckleLevel const level, TaskStatus const& status, DebugImages* dbg) const { DespeckleState new_state(*this); 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_speckles = Despeckle::despeckle( m_everythingBW, m_dpi, level2, status, dbg ); status.throwIfCancelled(); rasterOp >(new_state.m_speckles, m_everythingBW); return new_state; } QImage DespeckleState::overlaySpeckles( QImage const& mixed, imageproc::BinaryImage const& speckles) { QImage result(mixed.convertToFormat(QImage::Format_RGB32)); if (result.isNull() && !mixed.isNull()) { throw std::bad_alloc(); } if (speckles.isNull()) { return result; } uint32_t* result_line = (uint32_t*)result.bits(); int const result_stride = result.bytesPerLine() / 4; uint32_t const* speckles_line = speckles.data(); int const speckles_stride = speckles.wordsPerLine(); uint32_t const msb = uint32_t(1) << 31; int const width = result.width(); int const 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; } return result; } /** * Here we assume that B/W content have all their color components * set to either 0x00 or 0xff. We enforce this convention when * generating output files. */ BinaryImage DespeckleState::extractBW(QImage const& mixed) { BinaryImage result(mixed.size(), WHITE); uint32_t const* mixed_line = (uint32_t const*)mixed.bits(); int const mixed_stride = mixed.bytesPerLine() / 4; uint32_t* result_line = result.data(); int const result_stride = result.wordsPerLine(); uint32_t const msb = uint32_t(1) << 31; int const width = result.width(); int const 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; } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/DespeckleState.h000066400000000000000000000046751271170121200235540ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_DESPECKLE_STATE_H_ #define OUTPUT_DESPECKLE_STATE_H_ #include "DespeckleLevel.h" #include "Dpi.h" #include "imageproc/BinaryImage.h" #include class TaskStatus; class DebugImages; namespace output { class DespeckleVisualization; /** * Holds enough information to build a DespeckleVisualization * or to re-despeckle with different DespeckleLevel. */ class DespeckleState { // Member-wise copying is OK. public: DespeckleState(QImage const& output, imageproc::BinaryImage const& speckles, DespeckleLevel level, Dpi const& dpi); DespeckleLevel level() const { return m_despeckleLevel; } DespeckleVisualization visualize() const; DespeckleState redespeckle(DespeckleLevel level, TaskStatus const& status, DebugImages* dbg = 0) const; private: static QImage overlaySpeckles( QImage const& mixed, imageproc::BinaryImage const& speckles); static imageproc::BinaryImage extractBW(QImage const& 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; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/DespeckleView.cpp000066400000000000000000000204741271170121200237340ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "DespeckleView.h" #include "DespeckleView.h.moc" #include "DespeckleVisualization.h" #include "Despeckle.h" #include "AbstractCommand.h" #include "BackgroundExecutor.h" #include "BackgroundTask.h" #include "ImageViewBase.h" #include "BasicImageView.h" #include "OutputMargins.h" #include "ProcessingIndicationWidget.h" #include "DebugImages.h" #include "TabbedDebugImages.h" #include "AutoRemovingFile.h" #include "TaskStatus.h" #include "Dpi.h" #include "imageproc/BinaryImage.h" #include "imageproc/RasterOp.h" #include #include #include using namespace imageproc; namespace output { class DespeckleView::TaskCancelException : public std::exception { public: virtual char const* what() const throw() { return "Task cancelled"; } }; class DespeckleView::TaskCancelHandle : public TaskStatus, public RefCountable { public: virtual void cancel(); virtual bool isCancelled() const; virtual void throwIfCancelled() const; private: mutable QAtomicInt m_cancelFlag; }; class DespeckleView::DespeckleTask : public AbstractCommand0 { public: DespeckleTask( DespeckleView* owner, DespeckleState const& despeckle_state, IntrusivePtr const& cancel_handle, DespeckleLevel new_level, bool debug); virtual BackgroundExecutor::TaskResultPtr operator()(); private: QPointer m_ptrOwner; DespeckleState m_despeckleState; IntrusivePtr m_ptrCancelHandle; std::auto_ptr m_ptrDbg; DespeckleLevel m_despeckleLevel; }; class DespeckleView::DespeckleResult : public AbstractCommand0 { public: DespeckleResult( QPointer const& owner, IntrusivePtr const& cancel_handle, DespeckleState const& despeckle_state, DespeckleVisualization const& visualization, std::auto_ptr debug_images); // This method is called from the main thread. virtual void operator()(); private: QPointer m_ptrOwner; IntrusivePtr m_ptrCancelHandle; std::auto_ptr m_ptrDbg; DespeckleState m_despeckleState; DespeckleVisualization m_visualization; }; /*============================ DespeckleView ==============================*/ DespeckleView::DespeckleView( DespeckleState const& despeckle_state, DespeckleVisualization const& 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::auto_ptr widget( new BasicImageView(visualization.image(), visualization.downscaledImage()) ); setCurrentIndex(addWidget(widget.release())); } } DespeckleView::~DespeckleView() { cancelBackgroundTask(); } void DespeckleView::despeckleLevelChanged(DespeckleLevel const new_level, bool* handled) { if (new_level == m_despeckleLevel) { return; } m_despeckleLevel = new_level; if (isVisible()) { *handled = true; if (currentWidget() == m_pProcessingIndicator) { 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(); } void DespeckleView::showEvent(QShowEvent* evt) { QStackedWidget::showEvent(evt); if (currentWidget() == m_pProcessingIndicator) { initiateDespeckling(RESET_ANIMATION); } } void DespeckleView::initiateDespeckling(AnimationAction const anim_action) { removeImageViewWidget(); if (anim_action == RESET_ANIMATION) { m_pProcessingIndicator->resetAnimation(); } else { m_pProcessingIndicator->processingRestartedEffect(); } cancelBackgroundTask(); m_ptrCancelHandle.reset(new TaskCancelHandle); // Note that we are getting rid of m_initialSpeckles, // as we wouldn't need it any more. BackgroundExecutor::TaskPtr const task( new DespeckleTask( this, m_despeckleState, m_ptrCancelHandle, m_despeckleLevel, m_debug ) ); ImageViewBase::backgroundExecutor().enqueueTask(task); } void DespeckleView::despeckleDone( DespeckleState const& despeckle_state, DespeckleVisualization const& visualization, DebugImages* dbg) { assert(!visualization.isNull()); m_despeckleState = despeckle_state; removeImageViewWidget(); std::auto_ptr widget( new BasicImageView( visualization.image(), visualization.downscaledImage(), OutputMargins() ) ); if (dbg && !dbg->empty()) { std::auto_ptr tab_widget(new TabbedDebugImages); 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 = tab_widget; } setCurrentIndex(addWidget(widget.release())); } void DespeckleView::cancelBackgroundTask() { if (m_ptrCancelHandle) { m_ptrCancelHandle->cancel(); m_ptrCancelHandle.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; } } /*============================= DespeckleTask ==========================*/ DespeckleView::DespeckleTask::DespeckleTask( DespeckleView* owner, DespeckleState const& despeckle_state, IntrusivePtr const& cancel_handle, DespeckleLevel const level, bool const debug) : m_ptrOwner(owner), m_despeckleState(despeckle_state), m_ptrCancelHandle(cancel_handle), m_despeckleLevel(level) { if (debug) { m_ptrDbg.reset(new DebugImages); } } BackgroundExecutor::TaskResultPtr DespeckleView::DespeckleTask::operator()() { try { m_ptrCancelHandle->throwIfCancelled(); m_despeckleState = m_despeckleState.redespeckle( m_despeckleLevel, *m_ptrCancelHandle, m_ptrDbg.get() ); m_ptrCancelHandle->throwIfCancelled(); DespeckleVisualization visualization(m_despeckleState.visualize()); m_ptrCancelHandle->throwIfCancelled(); return BackgroundExecutor::TaskResultPtr( new DespeckleResult( m_ptrOwner, m_ptrCancelHandle, m_despeckleState, visualization, m_ptrDbg ) ); } catch (TaskCancelException const&) { return BackgroundExecutor::TaskResultPtr(); } } /*======================== DespeckleResult ===========================*/ DespeckleView::DespeckleResult::DespeckleResult( QPointer const& owner, IntrusivePtr const& cancel_handle, DespeckleState const& despeckle_state, DespeckleVisualization const& visualization, std::auto_ptr debug_images) : m_ptrOwner(owner), m_ptrCancelHandle(cancel_handle), m_ptrDbg(debug_images), m_despeckleState(despeckle_state), m_visualization(visualization) { } void DespeckleView::DespeckleResult::operator()() { if (m_ptrCancelHandle->isCancelled()) { return; } if (DespeckleView* owner = m_ptrOwner) { owner->despeckleDone(m_despeckleState, m_visualization, m_ptrDbg.get()); } } /*========================= TaskCancelHandle ============================*/ void DespeckleView::TaskCancelHandle::cancel() { m_cancelFlag.fetchAndStoreRelaxed(1); } bool DespeckleView::TaskCancelHandle::isCancelled() const { return m_cancelFlag.fetchAndAddRelaxed(0) != 0; } void DespeckleView::TaskCancelHandle::throwIfCancelled() const { if (isCancelled()) { throw TaskCancelException(); } } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/DespeckleView.h000066400000000000000000000046541271170121200234030ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_DESPECKLE_VIEW_H_ #define OUTPUT_DESPECKLE_VIEW_H_ #include "DespeckleLevel.h" #include "DespeckleState.h" #include "IntrusivePtr.h" #include "Dpi.h" #include "imageproc/BinaryImage.h" #include #include class DebugImages; class ProcessingIndicationWidget; 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(DespeckleState const& despeckle_state, DespeckleVisualization const& visualization, bool debug); virtual ~DespeckleView(); public slots: void despeckleLevelChanged(DespeckleLevel level, bool* handled); protected: virtual void hideEvent(QHideEvent* evt); virtual void showEvent(QShowEvent* evt); private: class TaskCancelException; class TaskCancelHandle; class DespeckleTask; class DespeckleResult; enum AnimationAction { RESET_ANIMATION, RESUME_ANIMATION }; void initiateDespeckling(AnimationAction anim_action); void despeckleDone(DespeckleState const& despeckle_state, DespeckleVisualization const& visualization, DebugImages* dbg); void cancelBackgroundTask(); void removeImageViewWidget(); DespeckleState m_despeckleState; IntrusivePtr m_ptrCancelHandle; ProcessingIndicationWidget* m_pProcessingIndicator; DespeckleLevel m_despeckleLevel; bool m_debug; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/DespeckleVisualization.cpp000066400000000000000000000056631271170121200256660ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "DespeckleVisualization.h" #include "ImageViewBase.h" #include "Dpi.h" #include "imageproc/BinaryImage.h" #include "imageproc/SEDM.h" #include #include using namespace imageproc; namespace output { DespeckleVisualization::DespeckleVisualization( QImage const& output, imageproc::BinaryImage const& speckles, Dpi const& dpi) { if (output.isNull()) { // This can happen in batch processing mode. return; } m_image = output.convertToFormat(QImage::Format_RGB32); if (!speckles.isNull()) { colorizeSpeckles(m_image, speckles, dpi); } m_downscaledImage = ImageViewBase::createDownscaledImage(m_image); } void DespeckleVisualization::colorizeSpeckles( QImage& image, imageproc::BinaryImage const& speckles, Dpi const& dpi) { int const w = image.width(); int const h = image.height(); uint32_t* image_line = (uint32_t*)image.bits(); int const image_stride = image.bytesPerLine() / 4; SEDM const sedm(speckles, SEDM::DIST_TO_BLACK, SEDM::DIST_TO_NO_BORDERS); uint32_t const* sedm_line = sedm.data(); int const sedm_stride = sedm.stride(); float const radius = 15.0 * std::max(dpi.horizontal(), dpi.vertical()) / 600; float const sq_radius = radius * radius; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { uint32_t const 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; } float const alpha_upper_bound = 0.8f; float const scale = alpha_upper_bound / sq_radius; float const alpha = alpha_upper_bound - scale * sq_dist; if (alpha > 0) { float const alpha2 = 1.0f - alpha; float const overlay_r = 255; float const overlay_g = 0; float const overlay_b = 0; float const r = overlay_r * alpha + qRed(image_line[x]) * alpha2; float const g = overlay_g * alpha + qGreen(image_line[x]) * alpha2; float const 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; } } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/DespeckleVisualization.h000066400000000000000000000035531271170121200253270ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_DESPECKLE_VISUALIZATION_H_ #define OUTPUT_DESPECKLE_VISUALIZATION_H_ #include class Dpi; namespace imageproc { class BinaryImage; } namespace output { class DespeckleVisualization { public: /* * Constructs a null visualization. */ DespeckleVisualization() {} /** * \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( QImage const& output, imageproc::BinaryImage const& speckles, Dpi const& dpi); bool isNull() const { return m_image.isNull(); } QImage const& image() const { return m_image; } QImage const& downscaledImage() const { return m_downscaledImage; } private: static void colorizeSpeckles( QImage& image, imageproc::BinaryImage const& speckles, Dpi const& dpi); QImage m_image; QImage m_downscaledImage; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/DewarpingMode.cpp000066400000000000000000000023651271170121200237260ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "DewarpingMode.h" #include namespace output { DewarpingMode::DewarpingMode(QString const& str) { if (str == "auto") { m_mode = AUTO; } else if (str == "manual") { m_mode = MANUAL; } else { m_mode = OFF; } } QString DewarpingMode::toString() const { switch (m_mode) { case OFF: return "off"; case AUTO: return "auto"; case MANUAL: return "manual"; } assert(!"Unreachable"); return QString(); } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/DewarpingMode.h000066400000000000000000000022541271170121200233700ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_DEWARPING_MODE_H_ #define OUTPUT_DEWARPING_MODE_H_ #include namespace output { class DewarpingMode { public: enum Mode { OFF, AUTO, MANUAL }; DewarpingMode(Mode mode = OFF) : m_mode(mode) {} explicit DewarpingMode(QString const& str); QString toString() const; operator Mode() const { return m_mode; } private: Mode m_mode; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/DewarpingView.cpp000066400000000000000000000305771271170121200237620ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "DewarpingView.h" #include "DewarpingView.h.moc" #include "ImagePresentation.h" #include "dewarping/Curve.h" #include "VecNT.h" #include "MatrixCalc.h" #include "NumericTraits.h" #include "ToLineProjector.h" #include "XSpline.h" #include "dewarping/CylindricalSurfaceDewarper.h" #include "dewarping/Curve.h" #include "spfit/SplineFitter.h" #include "spfit/ConstraintSet.h" #include "spfit/PolylineModelShape.h" #include "spfit/LinearForceBalancer.h" #include "spfit/OptimizationResult.h" #include "imageproc/Constants.h" #include #include #include #include #include #include #include #include #include #include #ifndef Q_MOC_RUN #include #include #include #endif #include #include namespace output { using namespace imageproc; DewarpingView::DewarpingView( QImage const& image, ImagePixmapUnion const& downscaled_image, QTransform const& image_to_virt, QPolygonF const& virt_display_area, QRectF const& virt_content_rect, PageId const& page_id, DewarpingMode dewarping_mode, dewarping::DistortionModel const& distortion_model, DepthPerception const& depth_perception) : ImageViewBase( image, downscaled_image, ImagePresentation(image_to_virt, virt_display_area) ), m_pageId(page_id), m_virtDisplayArea(virt_display_area), m_dewarpingMode(dewarping_mode), m_distortionModel(distortion_model), m_depthPerception(depth_perception), m_dragHandler(*this), m_zoomHandler(*this) { setMouseTracking(true); QPolygonF const 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) { std::vector const& 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]); } else { initNewSpline(new_top_spline, polyline.front(), polyline.back()); fitSpline(new_top_spline, polyline); } top_spline.swap(new_top_spline); } if (bottom_spline.numControlPoints() < 2) { std::vector const& 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]); } else { initNewSpline(new_bottom_spline, polyline.front(), polyline.back()); fitSpline(new_bottom_spline, polyline); } 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; BOOST_FOREACH(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() { } void DewarpingView::initNewSpline(XSpline& spline, QPointF const& p1, QPointF const& p2) { QLineF const line(p1, p2); spline.appendControlPoint(line.p1(), 0); 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, std::vector const& polyline) { using namespace spfit; SplineFitter fitter(&spline); PolylineModelShape const 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() ); OptimizationResult const res(fitter.optimize(internal_force_weight)); if (dewarping::Curve::splineHasLoops(spline)) { fitter.undoLastStep(); break; } if (res.improvementPercentage() < 0.5) { break; } } } void DewarpingView::depthPerceptionChanged(double val) { m_depthPerception.setValue(val); update(); } void DewarpingView::onPaint(QPainter& painter, InteractionState const& 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); int const num_vert_grid_lines = 30; int const 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) { double const x = j / (num_vert_grid_lines - 1.0); dewarping::CylindricalSurfaceDewarper::Generatrix const gtx(dewarper.mapGeneratrix(x, state)); QPointF const gtx_p0(gtx.imgLine.pointAt(gtx.pln2img(0))); QPointF const gtx_p1(gtx.imgLine.pointAt(gtx.pln2img(1))); painter.drawLine(gtx_p0, gtx_p1); for (int i = 0; i < num_hor_grid_lines; ++i) { double const y = i / (num_hor_grid_lines - 1.0); curves[i].push_back(gtx.imgLine.pointAt(gtx.pln2img(y))); } } BOOST_FOREACH(QVector const& curve, curves) { painter.drawPolyline(curve); } } catch (std::runtime_error const&) { // Still probably a bad model, even though DistortionModel::isValid() was true. valid_model = false; } } // valid_model if (!valid_model) { // Just draw the frame. dewarping::Curve const& top_curve = m_distortionModel.topCurve(); dewarping::Curve const& 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); } void DewarpingView::paintXSpline( QPainter& painter, InteractionState const& interaction, InteractiveXSpline const& ispline) { XSpline const& spline = ispline.spline(); painter.save(); painter.setBrush(Qt::NoBrush); #if 0 // No point in drawing the curve itself - we already draw the grid. painter.setWorldTransform(imageToVirtual() * virtualToWidget()); QPen curve_pen(Qt::blue); curve_pen.setWidthF(1.5); curve_pen.setCosmetic(true); painter.setPen(curve_pen); std::vector const 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); int const 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(); } 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(); } void DewarpingView::dragFinished() { if (m_dewarpingMode == DewarpingMode::AUTO) { m_dewarpingMode = DewarpingMode::MANUAL; } emit distortionModelChanged(m_distortionModel); } /** Source image coordinates to widget coordinates. */ QPointF DewarpingView::sourceToWidget(QPointF const& pt) const { return virtualToWidget().map(imageToVirtual().map(pt)); } /** Widget coordinates to source image coordinates. */ QPointF DewarpingView::widgetToSource(QPointF const& pt) const { return virtualToImage().map(widgetToVirtual().map(pt)); } QPolygonF DewarpingView::virtMarginArea(int margin_idx) const { dewarping::Curve const& top_curve = m_distortionModel.topCurve(); dewarping::Curve const& 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; ToLineProjector const vert_line_projector(vert_boundary); ToLineProjector const normal_projector(normal); BOOST_FOREACH(QPointF const& pt, m_virtDisplayArea) { double const p1 = vert_line_projector.projectionScalar(pt); if (p1 < min) { min = p1; } if (p1 > max) { max = p1; } double const 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; 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); } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/DewarpingView.h000066400000000000000000000053671271170121200234260ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_DEWARPING_VIEW_H_ #define OUTPUT_DEWARPING_VIEW_H_ #include "ImageViewBase.h" #include "ImagePixmapUnion.h" #include "InteractionHandler.h" #include "InteractiveXSpline.h" #include "DragHandler.h" #include "ZoomHandler.h" #include "DewarpingMode.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( QImage const& image, ImagePixmapUnion const& downscaled_image, QTransform const& source_to_virt, QPolygonF const& virt_display_area, QRectF const& virt_content_rect, PageId const& page_id, DewarpingMode dewarping_mode, dewarping::DistortionModel const& distortion_model, DepthPerception const& depth_perception); virtual ~DewarpingView(); signals: void distortionModelChanged(dewarping::DistortionModel const& model); public slots: void depthPerceptionChanged(double val); protected: virtual void onPaint(QPainter& painter, InteractionState const& interaction); private: static void initNewSpline(XSpline& spline, QPointF const& p1, QPointF const& p2); static void fitSpline(XSpline& spline, std::vector const& polyline); void paintXSpline( QPainter& painter, InteractionState const& interaction, InteractiveXSpline const& ispline); void curveModified(int curve_idx); void dragFinished(); QPointF sourceToWidget(QPointF const& pt) const; QPointF widgetToSource(QPointF const& pt) const; QPolygonF virtMarginArea(int margin_idx) const; PageId m_pageId; QPolygonF m_virtDisplayArea; DewarpingMode m_dewarpingMode; dewarping::DistortionModel m_distortionModel; DepthPerception m_depthPerception; InteractiveXSpline m_topSpline; InteractiveXSpline m_bottomSpline; DragHandler m_dragHandler; ZoomHandler m_zoomHandler; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/FillColorProperty.cpp000066400000000000000000000036001271170121200246240ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "FillColorProperty.h" #include "PropertyFactory.h" #include #include #include namespace output { char const FillColorProperty::m_propertyName[] = "FillColorProperty"; FillColorProperty::FillColorProperty(QDomElement const& el) : m_rgb(rgbFromString(el.attribute("color"))) { } void FillColorProperty::registerIn(PropertyFactory& factory) { factory.registerProperty(m_propertyName, &FillColorProperty::construct); } IntrusivePtr FillColorProperty::clone() const { return IntrusivePtr(new FillColorProperty(*this)); } QDomElement FillColorProperty::toXml(QDomDocument& doc, QString const& name) const { QDomElement el(doc.createElement(name)); el.setAttribute("type", m_propertyName); el.setAttribute("color", rgbToString(m_rgb)); return el; } IntrusivePtr FillColorProperty::construct(QDomElement const& el) { return IntrusivePtr(new FillColorProperty(el)); } QRgb FillColorProperty::rgbFromString(QString const& str) { return QColor(str).rgb(); } QString FillColorProperty::rgbToString(QRgb rgb) { return QColor(rgb).name(); } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/FillColorProperty.h000066400000000000000000000032751271170121200243010ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_FILL_COLOR_PROPERTY_H_ #define OUTPUT_FILL_COLOR_PROPERTY_H_ #include "Property.h" #include "IntrusivePtr.h" #include #include class PropertyFactory; class QDomDocument; class QDomElement; class QString; namespace output { class FillColorProperty : public Property { public: FillColorProperty(QColor const& color = Qt::white) : m_rgb(color.rgb()) {} FillColorProperty(QDomElement const& el); static void registerIn(PropertyFactory& factory); virtual IntrusivePtr clone() const; virtual QDomElement toXml(QDomDocument& doc, QString const& name) const; QColor color() const { return QColor(m_rgb); } void setColor(QColor const& color) { m_rgb = color.rgb(); } private: static IntrusivePtr construct(QDomElement const& el); static QRgb rgbFromString(QString const& str); static QString rgbToString(QRgb rgb); static char const m_propertyName[]; QRgb m_rgb; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/FillZoneComparator.cpp000066400000000000000000000035501271170121200247500ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "FillZoneComparator.h" #include "ZoneSet.h" #include "Zone.h" #include "PropertySet.h" #include "FillColorProperty.h" #include "imageproc/PolygonUtils.h" #include using namespace imageproc; namespace output { bool FillZoneComparator::equal(ZoneSet const& lhs, ZoneSet const& rhs) { ZoneSet::const_iterator lhs_it(lhs.begin()); ZoneSet::const_iterator rhs_it(rhs.begin()); ZoneSet::const_iterator const lhs_end(lhs.end()); ZoneSet::const_iterator const 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); } bool FillZoneComparator::equal(Zone const& lhs, Zone const& rhs) { if (!PolygonUtils::fuzzyCompare(lhs.spline().toPolygon(), rhs.spline().toPolygon())) { return false; } return equal(lhs.properties(), rhs.properties()); } bool FillZoneComparator::equal(PropertySet const& lhs, PropertySet const& rhs) { typedef FillColorProperty FCP; return lhs.locateOrDefault()->color() == rhs.locateOrDefault()->color(); } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/FillZoneComparator.h000066400000000000000000000021741271170121200244160ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef FILL_ZONE_COMPARATOR_H_ #define FILL_ZONE_COMPARATOR_H_ class ZoneSet; class Zone; class PropertySet; namespace output { class FillZoneComparator { public: static bool equal(ZoneSet const& lhs, ZoneSet const& rhs); static bool equal(Zone const& lhs, Zone const& rhs); static bool equal(PropertySet const& lhs, PropertySet const& rhs); }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/FillZoneEditor.cpp000066400000000000000000000143211271170121200240650ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "FillZoneEditor.h" #include "FillZoneEditor.h.moc" #include "ZoneContextMenuInteraction.h" #include "ZoneContextMenuItem.h" #include "ColorPickupInteraction.h" #include "NonCopyable.h" #include "Zone.h" #include "ZoneSet.h" #include "SerializableSpline.h" #include "PropertySet.h" #include "FillColorProperty.h" #include "Settings.h" #include "ImageTransformation.h" #include "ImagePresentation.h" #include "OutputMargins.h" #include #include #include #include #include #include #include #include #ifndef Q_MOC_RUN #include #include #endif #include #include namespace output { class FillZoneEditor::MenuCustomizer { private: typedef ZoneContextMenuInteraction::StandardMenuItems StdMenuItems; public: MenuCustomizer(FillZoneEditor* editor) : m_pEditor(editor) {} std::vector operator()( EditableZoneSet::Zone const& zone, StdMenuItems const& std_items); private: FillZoneEditor* m_pEditor; }; FillZoneEditor::FillZoneEditor( QImage const& image, ImagePixmapUnion const& downscaled_version, boost::function const& orig_to_image, boost::function const& image_to_orig, PageId const& page_id, IntrusivePtr const& 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(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); BOOST_FOREACH(Zone const& zone, m_ptrSettings->fillZonesForPage(page_id)) { EditableSpline::Ptr spline( new EditableSpline(zone.spline().transformed(m_origToImage)) ); m_zones.addZone(spline, zone.properties()); } } FillZoneEditor::~FillZoneEditor() { m_ptrSettings->setDefaultFillZoneProperties(m_zones.defaultProperties()); } void FillZoneEditor::onPaint(QPainter& painter, InteractionState const& interaction) { if (m_colorPickupInteraction.isActive(interaction)) { return; } painter.setRenderHint(QPainter::Antialiasing, false); painter.setPen(Qt::NoPen); BOOST_FOREACH(EditableZoneSet::Zone const& zone, m_zones) { typedef FillColorProperty FCP; QColor const 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) ); } InteractionHandler* FillZoneEditor::createColorPickupInteraction( EditableZoneSet::Zone const& zone, InteractionState& interaction) { m_colorPickupInteraction.startInteraction(zone, interaction); return &m_colorPickupInteraction; } void FillZoneEditor::commitZones() { ZoneSet zones; BOOST_FOREACH(EditableZoneSet::Zone const& zone, m_zones) { SerializableSpline const spline( SerializableSpline(*zone.spline()).transformed(m_imageToOrig) ); zones.add(Zone(spline, *zone.properties())); } m_ptrSettings->setFillZones(m_pageId, zones); emit invalidateThumbnail(m_pageId); } void FillZoneEditor::updateRequested() { update(); } QColor FillZoneEditor::toOpaque(QColor const& color) { QColor adapted(color); adapted.setAlpha(0xff); return adapted; } QColor FillZoneEditor::toGrayscale(QColor const& color) { int const gray = qGray(color.rgb()); return QColor(gray, gray, gray); } QColor FillZoneEditor::toBlackWhite(QColor const& color) { int const gray = qGray(color.rgb()); return gray < 128 ? Qt::black : Qt::white; } FillZoneEditor::ColorAdapter FillZoneEditor::colorAdapterFor(QImage const& 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; } } /*=========================== MenuCustomizer =========================*/ std::vector FillZoneEditor::MenuCustomizer::operator()( EditableZoneSet::Zone const& zone, StdMenuItems const& std_items) { std::vector items; items.reserve(2); items.push_back( ZoneContextMenuItem( tr("Pick color"), boost::bind( &FillZoneEditor::createColorPickupInteraction, m_pEditor, zone, _1 ) ) ); items.push_back(std_items.deleteItem); return items; } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/FillZoneEditor.h000066400000000000000000000056231271170121200235370ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_FILL_ZONE_EDITOR_H_ #define OUTPUT_FILL_ZONE_EDITOR_H_ #include "ImageViewBase.h" #include "ImagePixmapUnion.h" #include "NonCopyable.h" #include "RefCountable.h" #include "IntrusivePtr.h" #include "PageId.h" #include "ZoneInteractionContext.h" #include "ColorPickupInteraction.h" #include "EditableSpline.h" #include "EditableZoneSet.h" #include "ZoomHandler.h" #include "DragHandler.h" #ifndef Q_MOC_RUN #include #endif #include #include #include class InteractionState; class QPainter; namespace output { class Settings; class FillZoneEditor : public ImageViewBase, private InteractionHandler { Q_OBJECT public: FillZoneEditor( QImage const& image, ImagePixmapUnion const& downscaled_version, boost::function const& orig_to_image, boost::function const& image_to_orig, PageId const& page_id, IntrusivePtr const& settings); virtual ~FillZoneEditor(); signals: void invalidateThumbnail(PageId const& page_id); protected: virtual void onPaint(QPainter& painter, InteractionState const& interaction); private slots: void commitZones(); void updateRequested(); private: class MenuCustomizer; typedef QColor (*ColorAdapter)(QColor const&); InteractionHandler* createContextMenuInteraction(InteractionState& interaction); InteractionHandler* createColorPickupInteraction( EditableZoneSet::Zone const& zone, InteractionState& interaction); static QColor toOpaque(QColor const& color); static QColor toGrayscale(QColor const& color); static QColor toBlackWhite(QColor const& color); static ColorAdapter colorAdapterFor(QImage const& image); 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; boost::function m_origToImage; boost::function m_imageToOrig; PageId m_pageId; IntrusivePtr m_ptrSettings; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/FillZonePropFactory.cpp000066400000000000000000000016751271170121200251170ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "FillZonePropFactory.h" #include "FillColorProperty.h" namespace output { FillZonePropFactory::FillZonePropFactory() { FillColorProperty::registerIn(*this); } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/FillZonePropFactory.h000066400000000000000000000017671271170121200245660ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_FILL_ZONE_PROP_FACTORY_H_ #define OUTPUT_FILL_ZONE_PROP_FACTORY_H_ #include "PropertyFactory.h" namespace output { class FillZonePropFactory : public PropertyFactory { public: FillZonePropFactory(); }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/Filter.cpp000066400000000000000000000123261271170121200224240ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Filter.h" #include "FilterUiInterface.h" #include "OptionsWidget.h" #include "Task.h" #include "PageId.h" #include "Settings.h" #include "Params.h" #include "OutputParams.h" #include "ProjectReader.h" #include "ProjectWriter.h" #include "CacheDrivenTask.h" #include #include #include #include #include #include #include #include #include "CommandLine.h" #include "ImageViewTab.h" namespace output { Filter::Filter( PageSelectionAccessor const& page_selection_accessor) : m_ptrSettings(new Settings) { if (CommandLine::get().isGui()) { m_ptrOptionsWidget.reset( new OptionsWidget(m_ptrSettings, page_selection_accessor) ); } } Filter::~Filter() { } QString Filter::getName() const { return QCoreApplication::translate("output::Filter", "Output"); } PageView Filter::getView() const { return PAGE_VIEW; } void Filter::performRelinking(AbstractRelinker const& relinker) { m_ptrSettings->performRelinking(relinker); } void Filter::preUpdateUI(FilterUiInterface* ui, PageId const& page_id) { m_ptrOptionsWidget->preUpdateUI(page_id); ui->setOptionsWidget(m_ptrOptionsWidget.get(), ui->KEEP_OWNERSHIP); } QDomElement Filter::saveSettings( ProjectWriter const& writer, QDomDocument& doc) const { using namespace boost::lambda; QDomElement filter_el(doc.createElement("output")); writer.enumPages( boost::lambda::bind( &Filter::writePageSettings, this, boost::ref(doc), var(filter_el), boost::lambda::_1, boost::lambda::_2 ) ); return filter_el; } void Filter::writePageSettings( QDomDocument& doc, QDomElement& filter_el, PageId const& page_id, int numeric_id) const { Params const params(m_ptrSettings->getParams(page_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")); std::auto_ptr output_params(m_ptrSettings->getOutputParams(page_id)); if (output_params.get()) { page_el.appendChild(output_params->toXml(doc, "output-params")); } filter_el.appendChild(page_el); } void Filter::loadSettings(ProjectReader const& reader, QDomElement const& filters_el) { m_ptrSettings->clear(); QDomElement const filter_el( filters_el.namedItem("output").toElement() ); QString const 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; } QDomElement const el(node.toElement()); bool ok = true; int const id = el.attribute("id").toInt(&ok); if (!ok) { continue; } PageId const page_id(reader.pageId(id)); if (page_id.isNull()) { continue; } ZoneSet const picture_zones(el.namedItem("zones").toElement(), m_pictureZonePropFactory); if (!picture_zones.empty()) { m_ptrSettings->setPictureZones(page_id, picture_zones); } ZoneSet const fill_zones(el.namedItem("fill-zones").toElement(), m_fillZonePropFactory); if (!fill_zones.empty()) { m_ptrSettings->setFillZones(page_id, fill_zones); } QDomElement const params_el(el.namedItem("params").toElement()); if (!params_el.isNull()) { Params const params(params_el); m_ptrSettings->setParams(page_id, params); } QDomElement const output_params_el(el.namedItem("output-params").toElement()); if (!output_params_el.isNull()) { OutputParams const output_params(output_params_el); m_ptrSettings->setOutputParams(page_id, output_params); } } } IntrusivePtr Filter::createTask( PageId const& page_id, IntrusivePtr const& thumbnail_cache, OutputFileNameGenerator const& out_file_name_gen, bool const batch, bool const debug) { ImageViewTab lastTab(TAB_OUTPUT); if (m_ptrOptionsWidget.get() != 0) lastTab = m_ptrOptionsWidget->lastTab(); return IntrusivePtr( new Task( IntrusivePtr(this), m_ptrSettings, thumbnail_cache, page_id, out_file_name_gen, lastTab, batch, debug ) ); } IntrusivePtr Filter::createCacheDrivenTask(OutputFileNameGenerator const& out_file_name_gen) { return IntrusivePtr( new CacheDrivenTask(m_ptrSettings, out_file_name_gen) ); } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/Filter.h000066400000000000000000000051331271170121200220670ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_FILTER_H_ #define OUTPUT_FILTER_H_ #include "NonCopyable.h" #include "AbstractFilter.h" #include "PageView.h" #include "IntrusivePtr.h" #include "FilterResult.h" #include "SafeDeletingQObjectPtr.h" #include "PictureZonePropFactory.h" #include "FillZonePropFactory.h" class PageId; class PageSelectionAccessor; class ThumbnailPixmapCache; class OutputFileNameGenerator; class QString; namespace output { class OptionsWidget; class Task; class CacheDrivenTask; class Settings; class Filter : public AbstractFilter { DECLARE_NON_COPYABLE(Filter) public: Filter(PageSelectionAccessor const& page_selection_accessor); virtual ~Filter(); virtual QString getName() const; virtual PageView getView() const; virtual void performRelinking(AbstractRelinker const& relinker); virtual void preUpdateUI(FilterUiInterface* ui, PageId const& page_id); virtual QDomElement saveSettings( ProjectWriter const& writer, QDomDocument& doc) const; virtual void loadSettings( ProjectReader const& reader, QDomElement const& filters_el); IntrusivePtr createTask( PageId const& page_id, IntrusivePtr const& thumbnail_cache, OutputFileNameGenerator const& out_file_name_gen, bool batch, bool debug); IntrusivePtr createCacheDrivenTask( OutputFileNameGenerator const& out_file_name_gen); OptionsWidget* optionsWidget() { return m_ptrOptionsWidget.get(); }; Settings* getSettings() { return m_ptrSettings.get(); }; private: void writePageSettings( QDomDocument& doc, QDomElement& filter_el, PageId const& page_id, int numeric_id) const; IntrusivePtr m_ptrSettings; SafeDeletingQObjectPtr m_ptrOptionsWidget; PictureZonePropFactory m_pictureZonePropFactory; FillZonePropFactory m_fillZonePropFactory; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/ImageView.cpp000066400000000000000000000025001271170121200230450ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ImageView.h" #include "ImageView.h.moc" #include "ImagePresentation.h" #include "OutputMargins.h" namespace output { ImageView::ImageView(QImage const& image, QImage const& 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); } ImageView::~ImageView() { } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/ImageView.h000066400000000000000000000023361271170121200225210ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_IMAGEVIEW_H_ #define OUTPUT_IMAGEVIEW_H_ #include "ImageViewBase.h" #include "DragHandler.h" #include "ZoomHandler.h" #include class ImageTransformation; namespace output { class ImageView : public ImageViewBase { Q_OBJECT public: ImageView(QImage const& image, QImage const& downscaled_image); virtual ~ImageView(); private: DragHandler m_dragHandler; ZoomHandler m_zoomHandler; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/ImageViewTab.h000066400000000000000000000017771271170121200231600ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_IMAGE_VIEW_TAB_H_ #define OUTPUT_IMAGE_VIEW_TAB_H_ namespace output { enum ImageViewTab { TAB_OUTPUT, TAB_PICTURE_ZONES, TAB_FILL_ZONES, TAB_DEWARPING, TAB_DESPECKLING }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/OptionsWidget.cpp000066400000000000000000000444251271170121200240030ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "OptionsWidget.h" #include "OptionsWidget.h.moc" #include "ChangeDpiDialog.h" #include "ChangeDewarpingDialog.h" #include "ApplyColorsDialog.h" #include "Settings.h" #include "Params.h" #include "dewarping/DistortionModel.h" #include "DespeckleLevel.h" #include "ZoneSet.h" #include "PictureZoneComparator.h" #include "FillZoneComparator.h" #include "../../Utils.h" #include "ScopedIncDec.h" #include "config.h" #ifndef Q_MOC_RUN #include #endif #include #include #include #include #include #include #include #include #include #include namespace output { OptionsWidget::OptionsWidget( IntrusivePtr const& settings, PageSelectionAccessor const& page_selection_accessor) : m_ptrSettings(settings), m_pageSelectionAccessor(page_selection_accessor), m_despeckleLevel(DESPECKLE_NORMAL), m_lastTab(TAB_OUTPUT), m_ignoreThresholdChanges(0) { setupUi(this); depthPerceptionSlider->setMinimum(qRound(DepthPerception::minValue() * 10)); depthPerceptionSlider->setMaximum(qRound(DepthPerception::maxValue() * 10)); colorModeSelector->addItem(tr("Black and White"), ColorParams::BLACK_AND_WHITE); colorModeSelector->addItem(tr("Color / Grayscale"), ColorParams::COLOR_GRAYSCALE); colorModeSelector->addItem(tr("Mixed"), ColorParams::MIXED); darkerThresholdLink->setText( Utils::richTextForLink(darkerThresholdLink->text()) ); lighterThresholdLink->setText( Utils::richTextForLink(lighterThresholdLink->text()) ); thresholdSlider->setToolTip(QString::number(thresholdSlider->value())); updateDpiDisplay(); updateColorsDisplay(); updateDewarpingDisplay(); connect( changeDpiButton, SIGNAL(clicked()), this, SLOT(changeDpiButtonClicked()) ); connect( colorModeSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(colorModeChanged(int)) ); connect( whiteMarginsCB, SIGNAL(clicked(bool)), this, SLOT(whiteMarginsToggled(bool)) ); connect( equalizeIlluminationCB, SIGNAL(clicked(bool)), this, SLOT(equalizeIlluminationToggled(bool)) ); connect( lighterThresholdLink, SIGNAL(linkActivated(QString const&)), this, SLOT(setLighterThreshold()) ); connect( darkerThresholdLink, SIGNAL(linkActivated(QString const&)), this, SLOT(setDarkerThreshold()) ); connect( neutralThresholdBtn, SIGNAL(clicked()), this, SLOT(setNeutralThreshold()) ); connect( thresholdSlider, SIGNAL(valueChanged(int)), this, SLOT(bwThresholdChanged()) ); connect( thresholdSlider, SIGNAL(sliderReleased()), this, SLOT(bwThresholdChanged()) ); connect( applyColorsButton, SIGNAL(clicked()), this, SLOT(applyColorsButtonClicked()) ); 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)) ); thresholdSlider->setMinimum(-50); thresholdSlider->setMaximum(50); thresholLabel->setText(QString::number(thresholdSlider->value())); } OptionsWidget::~OptionsWidget() { } void OptionsWidget::preUpdateUI(PageId const& page_id) { Params const params(m_ptrSettings->getParams(page_id)); m_pageId = page_id; m_outputDpi = params.outputDpi(); m_colorParams = params.colorParams(); m_dewarpingMode = params.dewarpingMode(); m_depthPerception = params.depthPerception(); m_despeckleLevel = params.despeckleLevel(); updateDpiDisplay(); updateColorsDisplay(); updateDewarpingDisplay(); } void OptionsWidget::postUpdateUI() { } void OptionsWidget::tabChanged(ImageViewTab const tab) { m_lastTab = tab; updateDpiDisplay(); updateColorsDisplay(); updateDewarpingDisplay(); reloadIfNecessary(); } void OptionsWidget::distortionModelChanged(dewarping::DistortionModel const& model) { m_ptrSettings->setDistortionModel(m_pageId, model); // Note that OFF remains OFF while AUTO becomes MANUAL. if (m_dewarpingMode == DewarpingMode::AUTO) { m_ptrSettings->setDewarpingMode(m_pageId, DewarpingMode::MANUAL); m_dewarpingMode = DewarpingMode::MANUAL; updateDewarpingDisplay(); } } void OptionsWidget::colorModeChanged(int const idx) { int const mode = colorModeSelector->itemData(idx).toInt(); m_colorParams.setColorMode((ColorParams::ColorMode)mode); m_ptrSettings->setColorParams(m_pageId, m_colorParams); updateColorsDisplay(); emit reloadRequested(); } void OptionsWidget::whiteMarginsToggled(bool const checked) { ColorGrayscaleOptions opt(m_colorParams.colorGrayscaleOptions()); opt.setWhiteMargins(checked); if (!checked) { opt.setNormalizeIllumination(false); equalizeIlluminationCB->setChecked(false); } m_colorParams.setColorGrayscaleOptions(opt); m_ptrSettings->setColorParams(m_pageId, m_colorParams); equalizeIlluminationCB->setEnabled(checked); emit reloadRequested(); } void OptionsWidget::equalizeIlluminationToggled(bool const checked) { ColorGrayscaleOptions opt(m_colorParams.colorGrayscaleOptions()); opt.setNormalizeIllumination(checked); m_colorParams.setColorGrayscaleOptions(opt); m_ptrSettings->setColorParams(m_pageId, m_colorParams); emit reloadRequested(); } void OptionsWidget::setLighterThreshold() { thresholdSlider->setValue(thresholdSlider->value() - 1); } void OptionsWidget::setDarkerThreshold() { thresholdSlider->setValue(thresholdSlider->value() + 1); } void OptionsWidget::setNeutralThreshold() { thresholdSlider->setValue(0); } void OptionsWidget::bwThresholdChanged() { int const value = thresholdSlider->value(); QString const tooltip_text(QString::number(value)); thresholdSlider->setToolTip(tooltip_text); thresholLabel->setText(QString::number(value)); if (m_ignoreThresholdChanges) { return; } // Show the tooltip immediately. QPoint const 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()) { // Wait for it to be released. // We could have just disabled tracking, but in that case we wouldn't // be able to show tooltips with a precise value. return; } BlackWhiteOptions opt(m_colorParams.blackWhiteOptions()); if (opt.thresholdAdjustment() == value) { // Didn't change. return; } opt.setThresholdAdjustment(value); m_colorParams.setBlackWhiteOptions(opt); m_ptrSettings->setColorParams(m_pageId, m_colorParams); emit reloadRequested(); emit invalidateThumbnail(m_pageId); } void OptionsWidget::changeDpiButtonClicked() { ChangeDpiDialog* dialog = new ChangeDpiDialog( this, m_outputDpi, m_pageId, m_pageSelectionAccessor ); dialog->setAttribute(Qt::WA_DeleteOnClose); connect( dialog, SIGNAL(accepted(std::set const&, Dpi const&)), this, SLOT(dpiChanged(std::set const&, Dpi const&)) ); dialog->show(); } void OptionsWidget::applyColorsButtonClicked() { ApplyColorsDialog* dialog = new ApplyColorsDialog( this, m_pageId, m_pageSelectionAccessor ); dialog->setAttribute(Qt::WA_DeleteOnClose); connect( dialog, SIGNAL(accepted(std::set const&)), this, SLOT(applyColorsConfirmed(std::set const&)) ); dialog->show(); } void OptionsWidget::dpiChanged(std::set const& pages, Dpi const& dpi) { BOOST_FOREACH(PageId const& page_id, pages) { m_ptrSettings->setDpi(page_id, dpi); emit invalidateThumbnail(page_id); } if (pages.find(m_pageId) != pages.end()) { m_outputDpi = dpi; updateDpiDisplay(); emit reloadRequested(); } } void OptionsWidget::applyColorsConfirmed(std::set const& pages) { BOOST_FOREACH(PageId const& page_id, pages) { m_ptrSettings->setColorParams(page_id, m_colorParams); emit invalidateThumbnail(page_id); } if (pages.find(m_pageId) != pages.end()) { emit reloadRequested(); } } void OptionsWidget::despeckleOffSelected() { handleDespeckleLevelChange(DESPECKLE_OFF); } void OptionsWidget::despeckleCautiousSelected() { handleDespeckleLevelChange(DESPECKLE_CAUTIOUS); } void OptionsWidget::despeckleNormalSelected() { handleDespeckleLevelChange(DESPECKLE_NORMAL); } void OptionsWidget::despeckleAggressiveSelected() { handleDespeckleLevelChange(DESPECKLE_AGGRESSIVE); } void OptionsWidget::handleDespeckleLevelChange(DespeckleLevel const level) { m_despeckleLevel = level; m_ptrSettings->setDespeckleLevel(m_pageId, level); bool handled = false; emit despeckleLevelChanged(level, &handled); if (handled) { // This means we are on the "Despeckling" tab. emit invalidateThumbnail(m_pageId); } else { emit reloadRequested(); } } void OptionsWidget::applyDespeckleButtonClicked() { ApplyColorsDialog* dialog = new ApplyColorsDialog( this, m_pageId, m_pageSelectionAccessor ); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setWindowTitle(tr("Apply Despeckling Level")); connect( dialog, SIGNAL(accepted(std::set const&)), this, SLOT(applyDespeckleConfirmed(std::set const&)) ); dialog->show(); } void OptionsWidget::applyDespeckleConfirmed(std::set const& pages) { BOOST_FOREACH(PageId const& page_id, pages) { m_ptrSettings->setDespeckleLevel(page_id, m_despeckleLevel); emit invalidateThumbnail(page_id); } if (pages.find(m_pageId) != pages.end()) { emit reloadRequested(); } } void OptionsWidget::changeDewarpingButtonClicked() { ChangeDewarpingDialog* dialog = new ChangeDewarpingDialog( this, m_pageId, m_dewarpingMode, m_pageSelectionAccessor ); dialog->setAttribute(Qt::WA_DeleteOnClose); connect( dialog, SIGNAL(accepted(std::set const&, DewarpingMode const&)), this, SLOT(dewarpingChanged(std::set const&, DewarpingMode const&)) ); dialog->show(); } void OptionsWidget::dewarpingChanged(std::set const& pages, DewarpingMode const& mode) { BOOST_FOREACH(PageId const& page_id, pages) { m_ptrSettings->setDewarpingMode(page_id, mode); emit invalidateThumbnail(page_id); } if (pages.find(m_pageId) != pages.end()) { if (m_dewarpingMode != mode) { m_dewarpingMode = mode; // We reload when we switch to auto dewarping, even if we've just // switched to manual, as we don't store the auto-generated distortion model. // 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 (mode == DewarpingMode::AUTO || m_lastTab != TAB_DEWARPING) { // 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(); } } } } void OptionsWidget::applyDepthPerceptionButtonClicked() { ApplyColorsDialog* dialog = new ApplyColorsDialog( this, m_pageId, m_pageSelectionAccessor ); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setWindowTitle(tr("Apply Depth Perception")); connect( dialog, SIGNAL(accepted(std::set const&)), this, SLOT(applyDepthPerceptionConfirmed(std::set const&)) ); dialog->show(); } void OptionsWidget::applyDepthPerceptionConfirmed(std::set const& pages) { BOOST_FOREACH(PageId const& page_id, pages) { m_ptrSettings->setDepthPerception(page_id, m_depthPerception); emit invalidateThumbnail(page_id); } if (pages.find(m_pageId) != pages.end()) { emit reloadRequested(); } } void OptionsWidget::depthPerceptionChangedSlot(int val) { m_depthPerception.setValue(0.1 * val); QString const tooltip_text(QString::number(m_depthPerception.value())); depthPerceptionSlider->setToolTip(tooltip_text); // Show the tooltip immediately. QPoint const 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); // Propagate the signal. emit depthPerceptionChanged(m_depthPerception.value()); } void OptionsWidget::reloadIfNecessary() { ZoneSet saved_picture_zones; ZoneSet saved_fill_zones; DewarpingMode saved_dewarping_mode; dewarping::DistortionModel saved_distortion_model; DepthPerception saved_depth_perception; DespeckleLevel saved_despeckle_level = DESPECKLE_CAUTIOUS; std::auto_ptr output_params(m_ptrSettings->getOutputParams(m_pageId)); if (output_params.get()) { saved_picture_zones = output_params->pictureZones(); saved_fill_zones = output_params->fillZones(); saved_dewarping_mode = 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(); return; } else if (!FillZoneComparator::equal(saved_fill_zones, m_ptrSettings->fillZonesForPage(m_pageId))) { emit reloadRequested(); return; } Params const params(m_ptrSettings->getParams(m_pageId)); if (saved_despeckle_level != params.despeckleLevel()) { emit reloadRequested(); return; } if (saved_dewarping_mode == DewarpingMode::OFF && params.dewarpingMode() == DewarpingMode::OFF) { // In this case the following two checks don't matter. } else if (saved_depth_perception.value() != params.depthPerception().value()) { emit reloadRequested(); return; } else if (saved_dewarping_mode == DewarpingMode::AUTO && params.dewarpingMode() == DewarpingMode::AUTO) { // The check below doesn't matter in this case. } else if (!saved_distortion_model.matches(params.distortionModel())) { emit reloadRequested(); return; } else if ((saved_dewarping_mode == DewarpingMode::OFF) != (params.dewarpingMode() == DewarpingMode::OFF)) { emit reloadRequested(); return; } } void OptionsWidget::updateDpiDisplay() { if (m_outputDpi.horizontal() != m_outputDpi.vertical()) { dpiLabel->setText( QString::fromAscii("%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); ColorParams::ColorMode const color_mode = m_colorParams.colorMode(); int const color_mode_idx = colorModeSelector->findData(color_mode); colorModeSelector->setCurrentIndex(color_mode_idx); bool color_grayscale_options_visible = false; bool bw_options_visible = false; switch (color_mode) { case ColorParams::BLACK_AND_WHITE: bw_options_visible = true; break; case ColorParams::COLOR_GRAYSCALE: color_grayscale_options_visible = true; break; case ColorParams::MIXED: bw_options_visible = true; break; } colorGrayscaleOptions->setVisible(color_grayscale_options_visible); if (color_grayscale_options_visible) { ColorGrayscaleOptions const opt( m_colorParams.colorGrayscaleOptions() ); whiteMarginsCB->setChecked(opt.whiteMargins()); equalizeIlluminationCB->setChecked(opt.normalizeIllumination()); equalizeIlluminationCB->setEnabled(opt.whiteMargins()); } modePanel->setVisible(m_lastTab != TAB_DEWARPING); bwOptions->setVisible(bw_options_visible); despecklePanel->setVisible(bw_options_visible && m_lastTab != TAB_DEWARPING); if (bw_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; } ScopedIncDec const guard(m_ignoreThresholdChanges); thresholdSlider->setValue( m_colorParams.blackWhiteOptions().thresholdAdjustment() ); } colorModeSelector->blockSignals(false); } void OptionsWidget::updateDewarpingDisplay() { depthPerceptionPanel->setVisible(m_lastTab == TAB_DEWARPING); switch (m_dewarpingMode) { case DewarpingMode::OFF: dewarpingStatusLabel->setText(tr("Off")); break; case DewarpingMode::AUTO: dewarpingStatusLabel->setText(tr("Auto")); break; case DewarpingMode::MANUAL: dewarpingStatusLabel->setText(tr("Manual")); break; } depthPerceptionSlider->blockSignals(true); depthPerceptionSlider->setValue(qRound(m_depthPerception.value() * 10)); depthPerceptionSlider->blockSignals(false); } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/OptionsWidget.h000066400000000000000000000066271271170121200234520ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_OPTIONSWIDGET_H_ #define OUTPUT_OPTIONSWIDGET_H_ #include "ui_OutputOptionsWidget.h" #include "FilterOptionsWidget.h" #include "IntrusivePtr.h" #include "PageId.h" #include "PageSelectionAccessor.h" #include "ColorParams.h" #include "DewarpingMode.h" #include "DepthPerception.h" #include "DespeckleLevel.h" #include "Dpi.h" #include "ImageViewTab.h" #include namespace dewarping { class DistortionModel; } namespace output { class Settings; class DewarpingParams; class OptionsWidget : public FilterOptionsWidget, private Ui::OutputOptionsWidget { Q_OBJECT public: OptionsWidget(IntrusivePtr const& settings, PageSelectionAccessor const& page_selection_accessor); virtual ~OptionsWidget(); void preUpdateUI(PageId const& page_id); void postUpdateUI(); ImageViewTab lastTab() const { return m_lastTab; } DepthPerception const& depthPerception() const { return m_depthPerception; } signals: void despeckleLevelChanged(DespeckleLevel level, bool* handled); void depthPerceptionChanged(double val); public slots: void tabChanged(ImageViewTab tab); void distortionModelChanged(dewarping::DistortionModel const& model); private slots: void changeDpiButtonClicked(); void applyColorsButtonClicked(); void dpiChanged(std::set const& pages, Dpi const& dpi); void applyColorsConfirmed(std::set const& pages); void colorModeChanged(int idx); void whiteMarginsToggled(bool checked); void equalizeIlluminationToggled(bool checked); void setLighterThreshold(); void setDarkerThreshold(); void setNeutralThreshold(); void bwThresholdChanged(); void despeckleOffSelected(); void despeckleCautiousSelected(); void despeckleNormalSelected(); void despeckleAggressiveSelected(); void applyDespeckleButtonClicked(); void applyDespeckleConfirmed(std::set const& pages); void changeDewarpingButtonClicked(); void dewarpingChanged(std::set const& pages, DewarpingMode const& mode); void applyDepthPerceptionButtonClicked(); void applyDepthPerceptionConfirmed(std::set const& pages); void depthPerceptionChangedSlot(int val); private: void handleDespeckleLevelChange(DespeckleLevel level); void reloadIfNecessary(); void updateDpiDisplay(); void updateColorsDisplay(); void updateDewarpingDisplay(); IntrusivePtr m_ptrSettings; PageSelectionAccessor m_pageSelectionAccessor; PageId m_pageId; Dpi m_outputDpi; ColorParams m_colorParams; DepthPerception m_depthPerception; DewarpingMode m_dewarpingMode; DespeckleLevel m_despeckleLevel; ImageViewTab m_lastTab; int m_ignoreThresholdChanges; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/OutputFileParams.cpp000066400000000000000000000036671271170121200244530ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "OutputFileParams.h" #include #include #include #include namespace output { OutputFileParams::OutputFileParams() : m_size(-1), m_mtime(0) { } OutputFileParams::OutputFileParams(QFileInfo const& 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(QDomElement const& 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(); } } QDomElement OutputFileParams::toXml(QDomDocument& doc, QString const& 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(); } } bool OutputFileParams::matches(OutputFileParams const& other) const { return isValid() && other.isValid() && m_size == other.m_size/* && m_mtime == other.m_mtime*/; } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/OutputFileParams.h000066400000000000000000000030361271170121200241060ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_OUTPUT_FILE_PARAMS_H_ #define OUTPUT_OUTPUT_FILE_PARAMS_H_ #include #include class QDomDocument; class QDomElement; class QFileInfo; namespace output { /** * \brief Parameters of the output file used to determine if it has changed. */ class OutputFileParams { public: OutputFileParams(); explicit OutputFileParams(QFileInfo const& file_info); explicit OutputFileParams(QDomElement const& el); QDomElement toXml(QDomDocument& doc, QString const& name) const; bool const isValid() const { return m_size >= 0; } /** * \brief Returns true if it's likely we have two identical files. */ bool matches(OutputFileParams const& other) const; private: qint64 m_size; time_t m_mtime; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/OutputGenerator.cpp000066400000000000000000001462141271170121200243520ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "OutputGenerator.h" #include "ImageTransformation.h" #include "FilterData.h" #include "TaskStatus.h" #include "Utils.h" #include "DebugImages.h" #include "EstimateBackground.h" #include "Despeckle.h" #include "RenderParams.h" #include "dewarping/DistortionModel.h" #include "Dpi.h" #include "Dpm.h" #include "Zone.h" #include "ZoneSet.h" #include "PictureLayerProperty.h" #include "FillColorProperty.h" #include "dewarping/CylindricalSurfaceDewarper.h" #include "dewarping/TextLineTracer.h" #include "dewarping/TopBottomEdgeTracer.h" #include "dewarping/DistortionModelBuilder.h" #include "dewarping/DewarpingPointMapper.h" #include "dewarping/RasterDewarper.h" #include "imageproc/GrayImage.h" #include "imageproc/BinaryImage.h" #include "imageproc/BinaryThreshold.h" #include "imageproc/Binarize.h" #include "imageproc/BWColor.h" #include "imageproc/Transform.h" #include "imageproc/Scale.h" #include "imageproc/Morphology.h" #include "imageproc/Connectivity.h" #include "imageproc/ConnCompEraser.h" #include "imageproc/SeedFill.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/InfluenceMap.h" #include "config.h" #ifndef Q_MOC_RUN #include #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace imageproc; 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; } unsigned const orig = src; unsigned const background = dst; return static_cast((orig * 255 + background / 2) / background); } }; struct CombineInverted { static uint8_t transform(uint8_t src, uint8_t dst) { unsigned const dilated = dst; unsigned const eroded = src; unsigned const res = 255 - (255 - dilated) * eroded / 255; return static_cast(res); } }; /** * In picture areas we make sure we don't use pure black and pure white colors. * These are reserved for text areas. This behaviour makes it possible to * detect those picture areas later and treat them differently, for example * encoding them as a background layer in DjVu format. */ template PixelType reserveBlackAndWhite(PixelType color); 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; } } template<> uint8_t reserveBlackAndWhite(uint8_t color) { switch (color) { case 0x00: return 0x01; case 0xFF: return 0xFE; default: return color; } } template void reserveBlackAndWhite(QSize size, int stride, PixelType* data) { int const width = size.width(); int const height = size.height(); PixelType* line = data; for (int y = 0; y < height; ++y, line += stride) { for (int x = 0; x < width; ++x) { line[x] = reserveBlackAndWhite(line[x]); } } } void reserveBlackAndWhite(QImage& img) { assert(img.depth() == 8 || img.depth() == 24 || img.depth() == 32); switch (img.format()) { case QImage::Format_Indexed8: reserveBlackAndWhite(img.size(), img.bytesPerLine(), img.bits()); break; case QImage::Format_RGB32: case QImage::Format_ARGB32: reserveBlackAndWhite(img.size(), img.bytesPerLine()/4, (uint32_t*)img.bits()); break; default:; // Should not happen. } } /** * Fills areas of \p mixed with pixels from \p bw_content in * areas where \p bw_mask is black. Supported \p mixed image formats * are Indexed8 grayscale, RGB32 and ARGB32. * The \p MixedPixel type is uint8_t for Indexed8 grayscale and uint32_t * for RGB32 and ARGB32. */ template void combineMixed( QImage& mixed, BinaryImage const& bw_content, BinaryImage const& bw_mask) { MixedPixel* mixed_line = reinterpret_cast(mixed.bits()); int const mixed_stride = mixed.bytesPerLine() / sizeof(MixedPixel); uint32_t const* bw_content_line = bw_content.data(); int const bw_content_stride = bw_content.wordsPerLine(); uint32_t const* bw_mask_line = bw_mask.data(); int const bw_mask_stride = bw_mask.wordsPerLine(); int const width = mixed.width(); int const height = mixed.height(); uint32_t const 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))) { // B/W content. uint32_t tmp = bw_content_line[x >> 5]; tmp >>= (31 - (x & 31)); tmp &= uint32_t(1); // Now it's 0 for white and 1 for black. --tmp; // 0 becomes 0xffffffff and 1 becomes 0. tmp |= 0xff000000; // Force opacity. mixed_line[x] = static_cast(tmp); } else { // Non-B/W content. mixed_line[x] = reserveBlackAndWhite(mixed_line[x]); } } mixed_line += mixed_stride; bw_content_line += bw_content_stride; bw_mask_line += bw_mask_stride; } } } // anonymous namespace OutputGenerator::OutputGenerator( Dpi const& dpi, ColorParams const& color_params, DespeckleLevel const despeckle_level, ImageTransformation const& xform, QPolygonF const& content_rect_phys) : m_dpi(dpi), m_colorParams(color_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)); // 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( TaskStatus const& status, FilterData const& input, ZoneSet const& picture_zones, ZoneSet const& fill_zones, DewarpingMode dewarping_mode, DistortionModel& distortion_model, DepthPerception const& depth_perception, imageproc::BinaryImage* auto_picture_mask, imageproc::BinaryImage* speckles_image, DebugImages* const dbg) const { QImage image( processImpl( status, input, picture_zones, fill_zones, dewarping_mode, distortion_model, depth_perception, auto_picture_mask, speckles_image, dbg ) ); assert(!image.isNull()); // Set the correct DPI. Dpm const output_dpm(m_dpi); image.setDotsPerMeterX(output_dpm.horizontal()); image.setDotsPerMeterY(output_dpm.vertical()); return image; } QSize OutputGenerator::outputImageSize() const { return m_outRect.size(); } QRect OutputGenerator::outputContentRect() const { return m_contentRect; } GrayImage OutputGenerator::normalizeIlluminationGray( TaskStatus const& status, QImage const& input, QPolygonF const& area_to_consider, QTransform const& xform, QRect const& 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"); } status.throwIfCancelled(); QPolygonF transformed_consideration_area(xform.map(area_to_consider)); transformed_consideration_area.translate(-target_rect.topLeft()); PolynomialSurface const bg_ps( estimateBackground( to_be_normalized, transformed_consideration_area, status, dbg ) ); status.throwIfCancelled(); GrayImage bg_img(bg_ps.render(to_be_normalized.size())); if (dbg) { dbg->add(bg_img, "background"); } if (background) { *background = bg_img; } status.throwIfCancelled(); grayRasterOp(bg_img, to_be_normalized); if (dbg) { dbg->add(bg_img, "normalized_illumination"); } return bg_img; } imageproc::BinaryImage OutputGenerator::estimateBinarizationMask( TaskStatus const& status, GrayImage const& gray_source, QRect const& source_rect, QRect const& source_sub_rect, DebugImages* const dbg) const { 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. 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() ); int const stride = gray_source.stride(); int const 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 )); } status.throwIfCancelled(); QSize const 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(); // Light areas indicate pictures. GrayImage picture_areas(detectPictures(downscaled_input, status, dbg)); downscaled_input = GrayImage(); // Save memory. status.throwIfCancelled(); BinaryThreshold const threshold( BinaryThreshold::mokjiThreshold(picture_areas, 5, 26) ); // Scale back to original size. picture_areas = scaleToGray( picture_areas, source_sub_rect.size() ); return BinaryImage(picture_areas, threshold); } void OutputGenerator::modifyBinarizationMask( imageproc::BinaryImage& bw_mask, QRect const& mask_rect, ZoneSet const& zones) const { QTransform xform(m_xform.transform()); xform *= QTransform().translate(-mask_rect.x(), -mask_rect.y()); typedef PictureLayerProperty PLP; // Pass 1: ERASER1 BOOST_FOREACH(Zone const& zone, zones) { if (zone.properties().locateOrDefault()->layer() == PLP::ERASER1) { QPolygonF const poly(zone.spline().toPolygon()); PolygonRasterizer::fill(bw_mask, BLACK, xform.map(poly), Qt::WindingFill); } } // Pass 2: PAINTER2 BOOST_FOREACH(Zone const& zone, zones) { if (zone.properties().locateOrDefault()->layer() == PLP::PAINTER2) { QPolygonF const poly(zone.spline().toPolygon()); PolygonRasterizer::fill(bw_mask, WHITE, xform.map(poly), Qt::WindingFill); } } // Pass 1: ERASER3 BOOST_FOREACH(Zone const& zone, zones) { if (zone.properties().locateOrDefault()->layer() == PLP::ERASER3) { QPolygonF const poly(zone.spline().toPolygon()); PolygonRasterizer::fill(bw_mask, BLACK, xform.map(poly), Qt::WindingFill); } } } QImage OutputGenerator::processImpl( TaskStatus const& status, FilterData const& input, ZoneSet const& picture_zones, ZoneSet const& fill_zones, DewarpingMode dewarping_mode, DistortionModel& distortion_model, DepthPerception const& depth_perception, imageproc::BinaryImage* auto_picture_mask, imageproc::BinaryImage* speckles_image, DebugImages* const dbg) const { RenderParams const render_params(m_colorParams); if (dewarping_mode == DewarpingMode::AUTO || (dewarping_mode == DewarpingMode::MANUAL && distortion_model.isValid())) { return processWithDewarping( status, input, picture_zones, fill_zones, dewarping_mode, distortion_model, depth_perception, auto_picture_mask, speckles_image, dbg ); } else if (!render_params.whiteMargins()) { return processAsIs( input, status, fill_zones, depth_perception, dbg ); } else { return processWithoutDewarping( status, input, picture_zones, fill_zones, auto_picture_mask, speckles_image, dbg ); } } QImage OutputGenerator::processAsIs( FilterData const& input, TaskStatus const& status, ZoneSet const& fill_zones, DepthPerception const& depth_perception, DebugImages* const dbg) const { uint8_t const dominant_gray = reserveBlackAndWhite( calcDominantBackgroundGrayLevel(input.grayImage()) ); status.throwIfCancelled(); QColor const bg_color(dominant_gray, dominant_gray, dominant_gray); QImage out; if (input.origImage().allGray()) { if (m_outRect.isEmpty()) { QImage image(1, 1, QImage::Format_Indexed8); image.setColorTable(createGrayscalePalette()); if (image.isNull()) { throw std::bad_alloc(); } image.fill(dominant_gray); return image; } out = transformToGray( input.grayImage(), m_xform.transform(), m_outRect, OutsidePixels::assumeColor(bg_color) ); } else { if (m_outRect.isEmpty()) { QImage image(1, 1, QImage::Format_RGB32); image.fill(bg_color.rgb()); return image; } out = transform( input.origImage(), m_xform.transform(), m_outRect, OutsidePixels::assumeColor(bg_color) ); } applyFillZonesInPlace(out, fill_zones); reserveBlackAndWhite(out); return out; } QImage OutputGenerator::processWithoutDewarping( TaskStatus const& status, FilterData const& input, ZoneSet const& picture_zones, ZoneSet const& fill_zones, imageproc::BinaryImage* auto_picture_mask, imageproc::BinaryImage* speckles_image, DebugImages* dbg) const { RenderParams const render_params(m_colorParams); // The whole image minus the part cut off by the split line. QRect const big_margins_rect( m_xform.resultingPreCropArea().boundingRect().toRect() | m_contentRect ); // For various reasons, we need some whitespace around the content // area. This is the number of pixels of such whitespace. int const 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. QRect const small_margins_rect( m_contentRect.adjusted( -content_margin, -content_margin, content_margin, content_margin ).intersected(big_margins_rect) ); // 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. QRect const normalize_illumination_rect( #if 1 small_margins_rect #else big_margins_rect #endif ); QImage maybe_normalized; // Crop area in original image coordinates. QPolygonF const orig_image_crop_area( m_xform.transformBack().map( m_xform.resultingPreCropArea() ) ); // Crop area in maybe_normalized image coordinates. QPolygonF normalize_illumination_crop_area(m_xform.resultingPreCropArea()); normalize_illumination_crop_area.translate(-normalize_illumination_rect.topLeft()); if (render_params.normalizeIllumination()) { maybe_normalized = normalizeIlluminationGray( status, input.grayImage(), orig_image_crop_area, m_xform.transform(), normalize_illumination_rect, 0, dbg ); } else { maybe_normalized = transform( input.origImage(), m_xform.transform(), normalize_illumination_rect, OutsidePixels::assumeColor(Qt::white) ); } status.throwIfCancelled(); QImage maybe_smoothed; // We only do smoothing if we are going to do binarization later. if (!render_params.needBinarization()) { maybe_smoothed = maybe_normalized; } else { maybe_smoothed = smoothToGrayscale(maybe_normalized, m_dpi); if (dbg) { dbg->add(maybe_smoothed, "smoothed"); } } status.throwIfCancelled(); if (render_params.binaryOutput() || m_outRect.isEmpty()) { BinaryImage dst(m_outRect.size().expandedTo(QSize(1, 1)), WHITE); if (!m_contentRect.isEmpty()) { BinaryImage bw_content( binarize(maybe_smoothed, normalize_illumination_crop_area) ); if (dbg) { dbg->add(bw_content, "binarized_and_cropped"); } status.throwIfCancelled(); morphologicalSmoothInPlace(bw_content, status); if (dbg) { dbg->add(bw_content, "edges_smoothed"); } status.throwIfCancelled(); QRect const src_rect(m_contentRect.translated(-normalize_illumination_rect.topLeft())); QRect const dst_rect(m_contentRect); rasterOp(dst, dst_rect, bw_content, src_rect.topLeft()); bw_content.release(); // 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( dst, m_outRect, m_outRect, m_despeckleLevel, speckles_image, m_dpi, status, dbg ); } applyFillZonesInPlace(dst, fill_zones); return dst.toQImage(); } QSize const target_size(m_outRect.size().expandedTo(QSize(1, 1))); BinaryImage bw_mask; if (render_params.mixedOutput()) { // This block should go before the block with // adjustBrightnessGrayscale(), which may convert // maybe_normalized from grayscale to color mode. bw_mask = estimateBinarizationMask( status, GrayImage(maybe_normalized), normalize_illumination_rect, small_margins_rect, dbg ); if (dbg) { dbg->add(bw_mask, "bw_mask"); } if (auto_picture_mask) { if (auto_picture_mask->size() != target_size) { BinaryImage(target_size).swap(*auto_picture_mask); } auto_picture_mask->fill(BLACK); if (!m_contentRect.isEmpty()) { QRect const src_rect(m_contentRect.translated(-small_margins_rect.topLeft())); QRect const dst_rect(m_contentRect); rasterOp(*auto_picture_mask, dst_rect, bw_mask, src_rect.topLeft()); } } status.throwIfCancelled(); modifyBinarizationMask(bw_mask, small_margins_rect, picture_zones); if (dbg) { dbg->add(bw_mask, "bw_mask with zones"); } } if (render_params.normalizeIllumination() && !input.origImage().allGray()) { assert(maybe_normalized.format() == QImage::Format_Indexed8); QImage tmp( transform( input.origImage(), m_xform.transform(), normalize_illumination_rect, OutsidePixels::assumeColor(Qt::white) ) ); status.throwIfCancelled(); adjustBrightnessGrayscale(tmp, maybe_normalized); maybe_normalized = tmp; if (dbg) { dbg->add(maybe_normalized, "norm_illum_color"); } } if (!render_params.mixedOutput()) { // It's "Color / Grayscale" mode, as we handle B/W above. reserveBlackAndWhite(maybe_normalized); } else { BinaryImage bw_content( binarize(maybe_smoothed, normalize_illumination_crop_area, &bw_mask) ); maybe_smoothed = QImage(); // Save memory. if (dbg) { dbg->add(bw_content, "binarized_and_cropped"); } status.throwIfCancelled(); morphologicalSmoothInPlace(bw_content, status); if (dbg) { dbg->add(bw_content, "edges_smoothed"); } status.throwIfCancelled(); // We don't want speckles in non-B/W areas, as they would // then get visualized on the Despeckling tab. rasterOp >(bw_content, bw_mask); 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, small_margins_rect, m_contentRect, m_despeckleLevel, speckles_image, m_dpi, status, dbg ); status.throwIfCancelled(); if (maybe_normalized.format() == QImage::Format_Indexed8) { combineMixed( maybe_normalized, bw_content, bw_mask ); } else { assert(maybe_normalized.format() == QImage::Format_RGB32 || maybe_normalized.format() == QImage::Format_ARGB32); combineMixed( maybe_normalized, bw_content, bw_mask ); } } status.throwIfCancelled(); assert(!target_size.isEmpty()); QImage dst(target_size, maybe_normalized.format()); if (maybe_normalized.format() == QImage::Format_Indexed8) { dst.setColorTable(createGrayscalePalette()); // White. 0xff is reserved if in "Color / Grayscale" mode. uint8_t const color = render_params.mixedOutput() ? 0xff : 0xfe; dst.fill(color); } else { // White. 0x[ff]ffffff is reserved if in "Color / Grayscale" mode. uint32_t const color = render_params.mixedOutput() ? 0xffffffff : 0xfffefefe; dst.fill(color); } if (dst.isNull()) { // Both the constructor and setColorTable() above can leave the image null. throw std::bad_alloc(); } if (!m_contentRect.isEmpty()) { QRect const src_rect(m_contentRect.translated(-small_margins_rect.topLeft())); QRect const dst_rect(m_contentRect); drawOver(dst, dst_rect, maybe_normalized, src_rect); } applyFillZonesInPlace(dst, fill_zones); return dst; } QImage OutputGenerator::processWithDewarping( TaskStatus const& status, FilterData const& input, ZoneSet const& picture_zones, ZoneSet const& fill_zones, DewarpingMode dewarping_mode, DistortionModel& distortion_model, DepthPerception const& depth_perception, imageproc::BinaryImage* auto_picture_mask, imageproc::BinaryImage* speckles_image, DebugImages* dbg) const { QSize const target_size(m_outRect.size().expandedTo(QSize(1, 1))); if (m_outRect.isEmpty()) { return BinaryImage(target_size, WHITE).toQImage(); } RenderParams const render_params(m_colorParams); // The whole image minus the part cut off by the split line. QRect const big_margins_rect( m_xform.resultingPreCropArea().boundingRect().toRect() | m_contentRect ); // For various reasons, we need some whitespace around the content // area. This is the number of pixels of such whitespace. int const 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. QRect const small_margins_rect( m_contentRect.adjusted( -content_margin, -content_margin, content_margin, content_margin ).intersected(big_margins_rect) ); // 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. QRect const normalize_illumination_rect( #if 1 small_margins_rect #else big_margins_rect #endif ); // Crop area in original image coordinates. QPolygonF const orig_image_crop_area( m_xform.transformBack().map(m_xform.resultingPreCropArea()) ); // Crop area in maybe_normalized image coordinates. QPolygonF normalize_illumination_crop_area(m_xform.resultingPreCropArea()); normalize_illumination_crop_area.translate(-normalize_illumination_rect.topLeft()); bool const color_original = !input.origImage().allGray(); // 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); QTransform const norm_illum_to_original( QTransform().translate( normalize_illumination_rect.left(), normalize_illumination_rect.top() ) * m_xform.transformBack() ); if (!render_params.normalizeIllumination()) { if (color_original) { normalized_original = convertToRGBorRGBA(input.origImage()); } else { normalized_original = input.grayImage(); } if (dewarping_mode == DewarpingMode::AUTO) { warped_gray_output = transformToGray( input.grayImage(), m_xform.transform(), normalize_illumination_rect, OutsidePixels::assumeWeakColor(Qt::white) ); } // Otherwise we just don't need it. } else { GrayImage warped_gray_background; warped_gray_output = normalizeIlluminationGray( status, input.grayImage(), orig_image_crop_area, m_xform.transform(), normalize_illumination_rect, &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, input.origImage().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, input.grayImage()); if (dbg) { dbg->add(warped_gray_background, "norm_illum_gray"); } status.throwIfCancelled(); if (!color_original || render_params.binaryOutput()) { normalized_original = warped_gray_background; } else { normalized_original = convertToRGBorRGBA(input.origImage()); adjustBrightnessGrayscale(normalized_original, warped_gray_background); if (dbg) { dbg->add(normalized_original, "norm_illum_color"); } } } status.throwIfCancelled(); if (render_params.binaryOutput()) { bw_threshold = calcBinarizationThreshold( warped_gray_output, normalize_illumination_crop_area ); status.throwIfCancelled(); } else if (render_params.mixedOutput()) { estimateBinarizationMask( status, GrayImage(warped_gray_output), normalize_illumination_rect, small_margins_rect, dbg ).swap(warped_bw_mask); if (dbg) { dbg->add(warped_bw_mask, "warped_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); if (!m_contentRect.isEmpty()) { QRect const src_rect(m_contentRect.translated(-small_margins_rect.topLeft())); QRect const dst_rect(m_contentRect); rasterOp(*auto_picture_mask, dst_rect, warped_bw_mask, src_rect.topLeft()); } } status.throwIfCancelled(); modifyBinarizationMask(warped_bw_mask, small_margins_rect, picture_zones); if (dbg) { dbg->add(warped_bw_mask, "warped_bw_mask with zones"); } status.throwIfCancelled(); // For Mixed output, we mask out pictures when calculating binarization threshold. bw_threshold = calcBinarizationThreshold( warped_gray_output, normalize_illumination_crop_area, &warped_bw_mask ); status.throwIfCancelled(); } if (dewarping_mode == DewarpingMode::AUTO) { DistortionModelBuilder model_builder(Vec2d(0, 1)); QRect const content_rect( m_contentRect.translated(-normalize_illumination_rect.topLeft()) ); TextLineTracer::trace( warped_gray_output, m_dpi, content_rect, model_builder, status, dbg ); model_builder.transform(norm_illum_to_original); TopBottomEdgeTracer::trace( input.grayImage(), model_builder.verticalBounds(), model_builder, status, dbg ); distortion_model = model_builder.tryBuildModel(dbg, &input.grayImage().toQImage()); if (!distortion_model.isValid()) { setupTrivialDistortionModel(distortion_model); } } warped_gray_output = GrayImage(); // Save memory. if (render_params.whiteMargins()) { // Fill everything except the content area in normalized_original to white. QPolygonF const orig_content_poly(m_xform.transformBack().map(QRectF(m_contentRect))); fillMarginsInPlace(normalized_original, orig_content_poly, Qt::white); if (dbg) { dbg->add(normalized_original, "white margins"); } } status.throwIfCancelled(); QColor bg_color(Qt::white); if (!render_params.whiteMargins()) { uint8_t const dominant_gray = reserveBlackAndWhite( calcDominantBackgroundGrayLevel(input.grayImage()) ); bg_color = QColor(dominant_gray, dominant_gray, dominant_gray); } QImage dewarped; try { dewarped = dewarp( QTransform(), normalized_original, m_xform.transform(), distortion_model, depth_perception, bg_color ); } catch (std::runtime_error const&) { // 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, bg_color ); } normalized_original = QImage(); // Save memory. if (dbg) { dbg->add(dewarped, "dewarped"); } status.throwIfCancelled(); QImage dewarped_and_maybe_smoothed; // We only do smoothing if we are going to do binarization later. if (!render_params.needBinarization()) { dewarped_and_maybe_smoothed = dewarped; } else { dewarped_and_maybe_smoothed = smoothToGrayscale(dewarped, m_dpi); if (dbg) { dbg->add(dewarped_and_maybe_smoothed, "smoothed"); } } boost::shared_ptr mapper( new DewarpingPointMapper( distortion_model, depth_perception.value(), m_xform.transform(), m_contentRect ) ); boost::function const orig_to_output( boost::bind(&DewarpingPointMapper::mapToDewarpedSpace, mapper, _1) ); if (render_params.binaryOutput()) { BinaryImage dewarped_bw_content(dewarped_and_maybe_smoothed, bw_threshold); dewarped_and_maybe_smoothed = QImage(); // Save memory. if (dbg) { dbg->add(dewarped_bw_content, "dewarped_bw_content"); } status.throwIfCancelled(); morphologicalSmoothInPlace(dewarped_bw_content, status); if (dbg) { dbg->add(dewarped_bw_content, "edges_smoothed"); } 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(dewarped_bw_content, fill_zones, orig_to_output); return dewarped_bw_content.toQImage(); } if (!render_params.mixedOutput()) { // It's "Color / Grayscale" mode, as we handle B/W above. reserveBlackAndWhite(dewarped); } else { status.throwIfCancelled(); // Dewarp the B/W mask. QTransform const orig_to_small_margins( m_xform.transform() * QTransform().translate( -small_margins_rect.left(), -small_margins_rect.top() ) ); QTransform small_margins_to_output; small_margins_to_output.translate( small_margins_rect.left(), small_margins_rect.top() ); BinaryImage const dewarped_bw_mask( dewarp( orig_to_small_margins, warped_bw_mask.toQImage(), small_margins_to_output, distortion_model, depth_perception, Qt::black ) ); if (dbg) { dbg->add(dewarped_bw_mask, "dewarped_bw_mask"); } status.throwIfCancelled(); BinaryImage dewarped_bw_content(dewarped_and_maybe_smoothed, bw_threshold); dewarped_and_maybe_smoothed = QImage(); // Save memory. if (dbg) { dbg->add(dewarped_bw_content, "dewarped_bw_content"); } status.throwIfCancelled(); morphologicalSmoothInPlace(dewarped_bw_content, status); if (dbg) { dbg->add(dewarped_bw_content, "edges_smoothed"); } status.throwIfCancelled(); // We don't want speckles in non-B/W areas, as they would // then get visualized on the Despeckling tab. rasterOp >(dewarped_bw_content, dewarped_bw_mask); 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_contentRect, m_despeckleLevel, speckles_image, m_dpi, status, dbg ); status.throwIfCancelled(); if (dewarped.format() == QImage::Format_Indexed8) { combineMixed( dewarped, dewarped_bw_content, dewarped_bw_mask ); } else { assert(dewarped.format() == QImage::Format_RGB32 || dewarped.format() == QImage::Format_ARGB32); combineMixed( dewarped, dewarped_bw_content, dewarped_bw_mask ); } } applyFillZonesInPlace(dewarped, fill_zones, orig_to_output); return dewarped; } /** * Set up a distortion model corresponding to the content rect, * 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)); } CylindricalSurfaceDewarper OutputGenerator::createDewarper( DistortionModel const& distortion_model, QTransform const& 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()); BOOST_FOREACH(QPointF& pt, top_polyline) { pt = distortion_model_to_target.map(pt); } BOOST_FOREACH(QPointF& pt, bottom_polyline) { pt = distortion_model_to_target.map(pt); } return CylindricalSurfaceDewarper( top_polyline, bottom_polyline, depth_perception ); } /** * \param orig_to_src Transformation from the original image coordinates * to the coordinate system of \p src image. * \param src_to_output Transformation from the \p src image coordinates * to output image coordinates. * \param distortion_model Distortion model. * \param depth_perception Depth perception. * \param bg_color The color to use for areas outsize of \p src. * \param modified_content_rect A vertically shrunk version of outputContentRect(). * See function definition for more details. */ QImage OutputGenerator::dewarp( QTransform const& orig_to_src, QImage const& src, QTransform const& src_to_output, DistortionModel const& distortion_model, DepthPerception const& depth_perception, QColor const& bg_color) const { CylindricalSurfaceDewarper const 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. QRect const 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 RasterDewarper::dewarp( src, m_outRect.size(), dewarper, model_domain, bg_color ); } QSize OutputGenerator::from300dpi(QSize const& size, Dpi const& target_dpi) { double const hscale = target_dpi.horizontal() / 300.0; double const vscale = target_dpi.vertical() / 300.0; int const width = qRound(size.width() * hscale); int const height = qRound(size.height() * vscale); return QSize(std::max(1, width), std::max(1, height)); } QSize OutputGenerator::to300dpi(QSize const& size, Dpi const& source_dpi) { double const hscale = 300.0 / source_dpi.horizontal(); double const vscale = 300.0 / source_dpi.vertical(); int const width = qRound(size.width() * hscale); int const height = qRound(size.height() * vscale); return QSize(std::max(1, width), std::max(1, height)); } QImage OutputGenerator::convertToRGBorRGBA(QImage const& src) { QImage::Format const fmt = src.hasAlphaChannel() ? QImage::Format_ARGB32 : QImage::Format_RGB32; return src.convertToFormat(fmt); } void OutputGenerator::fillMarginsInPlace( QImage& image, QPolygonF const& content_poly, QColor const& color) { if (image.format() == QImage::Format_Indexed8 && image.isGrayscale()) { PolygonRasterizer::grayFillExcept( image, qGray(color.rgb()), content_poly, Qt::WindingFill ); return; } assert(image.format() == QImage::Format_RGB32 || image.format() == QImage::Format_ARGB32); if (image.format() == QImage::Format_ARGB32) { image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); } { QPainter painter(&image); painter.setRenderHint(QPainter::Antialiasing); painter.setBrush(color); painter.setPen(Qt::NoPen); QPainterPath outer_path; outer_path.addRect(image.rect()); QPainterPath inner_path; inner_path.addPolygon(content_poly); painter.drawPath(outer_path.subtracted(inner_path)); } if (image.format() == QImage::Format_ARGB32_Premultiplied) { image = image.convertToFormat(QImage::Format_ARGB32); } } GrayImage OutputGenerator::detectPictures( GrayImage const& input_300dpi, TaskStatus const& status, DebugImages* const dbg) { // 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"); } return holes_filled; } QImage OutputGenerator::smoothToGrayscale(QImage const& src, Dpi const& dpi) { int const 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 { int const 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)); } BinaryThreshold OutputGenerator::calcBinarizationThreshold( QImage const& image, BinaryImage const& mask) const { GrayscaleHistogram hist(image, mask); return adjustThreshold(BinaryThreshold::otsuThreshold(hist)); } BinaryThreshold OutputGenerator::calcBinarizationThreshold( QImage const& image, QPolygonF const& crop_area, BinaryImage const* 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); if (mask) { rasterOp >(modified_mask, *mask); } return calcBinarizationThreshold(image, modified_mask); } } BinaryImage OutputGenerator::binarize(QImage const& image, BinaryImage const& mask) const { GrayscaleHistogram hist(image, mask); BinaryThreshold const bw_thresh(BinaryThreshold::otsuThreshold(hist)); BinaryImage binarized(image, adjustThreshold(bw_thresh)); // Fill masked out areas with white. rasterOp >(binarized, mask); return binarized; } BinaryImage OutputGenerator::binarize(QImage const& image, QPolygonF const& crop_area, BinaryImage const* mask) const { QPainterPath path; path.addPolygon(crop_area); if (path.contains(image.rect()) && !mask) { BinaryThreshold const bw_thresh(BinaryThreshold::otsuThreshold(image)); return BinaryImage(image, adjustThreshold(bw_thresh)); } 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); if (mask) { rasterOp >(modified_mask, *mask); } return binarize(image, modified_mask); } } /** * \brief Remove small connected components that are considered to be garbage. * * Both the size and the distance to other components are taken into account. * * \param[in,out] image The image to despeckle. * \param image_rect The rectangle corresponding to \p image in the same * coordinate system where m_contentRect and m_cropRect are defined. * \param mask_rect The area within the image to consider. Defined not * relative to \p image, but in the same coordinate system where * m_contentRect and m_cropRect are defined. This only affects * \p speckles_img, if provided. * \param level Despeckling aggressiveness. * \param speckles_img If provided, the removed black speckles will be written * there. The speckles image is always considered to correspond * to m_cropRect, so it will have the size of m_cropRect.size(). * Only the area within \p mask_rect will be copied to \p speckles_img. * The rest will be filled with white. * \param dpi The DPI of the input image. See the note below. * \param status Task status. * \param dbg An optional sink for debugging images. * * \note This function only works effectively when the DPI is symmetric, * that is, its horizontal and vertical components are equal. */ void OutputGenerator::maybeDespeckleInPlace( imageproc::BinaryImage& image, QRect const& image_rect, QRect const& mask_rect, DespeckleLevel const level, BinaryImage* speckles_img, Dpi const& dpi, TaskStatus const& status, DebugImages* dbg) const { QRect const src_rect(mask_rect.translated(-image_rect.topLeft())); QRect const 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 (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 (dbg) { dbg->add(image, "despeckled"); } } if (speckles_img) { if (!mask_rect.isEmpty()) { rasterOp >( *speckles_img, dst_rect, image, src_rect.topLeft() ); } } } void OutputGenerator::morphologicalSmoothInPlace( BinaryImage& bin_img, TaskStatus const& status) { // When removing black noise, remove small ones first. { char const pattern[] = "XXX" " - " " "; hitMissReplaceAllDirections(bin_img, pattern, 3, 3); } status.throwIfCancelled(); { char const pattern[] = "X ?" "X " "X- " "X- " "X " "X ?"; hitMissReplaceAllDirections(bin_img, pattern, 3, 6); } status.throwIfCancelled(); { char const pattern[] = "X ?" "X ?" "X " "X- " "X- " "X- " "X " "X ?" "X ?"; hitMissReplaceAllDirections(bin_img, pattern, 3, 9); } status.throwIfCancelled(); { char const pattern[] = "XX?" "XX?" "XX " "X+ " "X+ " "X+ " "XX " "XX?" "XX?"; hitMissReplaceAllDirections(bin_img, pattern, 3, 9); } status.throwIfCancelled(); { char const pattern[] = "XX?" "XX " "X+ " "X+ " "XX " "XX?"; hitMissReplaceAllDirections(bin_img, pattern, 3, 6); } status.throwIfCancelled(); { char const pattern[] = " " "X+X" "XXX"; hitMissReplaceAllDirections(bin_img, pattern, 3, 3); } } void OutputGenerator::hitMissReplaceAllDirections( imageproc::BinaryImage& img, char const* const pattern, int const pattern_width, int const pattern_height) { hitMissReplaceInPlace(img, WHITE, pattern, pattern_width, pattern_height); std::vector pattern_data(pattern_width * pattern_height, ' '); char* const new_pattern = &pattern_data[0]; // Rotate 90 degrees clockwise. char const* 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) { int const new_x = pattern_height - 1 - y; int const 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) { int const new_x = pattern_width - 1 - x; int const 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) { int const new_x = y; int const new_y = pattern_width - 1 - x; new_pattern[new_y * new_width + new_x] = *p; } } hitMissReplaceInPlace(img, WHITE, new_pattern, new_width, new_height); } QSize OutputGenerator::calcLocalWindowSize(Dpi const& dpi) { QSizeF const size_mm(3, 30); QSizeF const size_inch(size_mm * constants::MM2INCH); QSizeF const 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; } unsigned char OutputGenerator::calcDominantBackgroundGrayLevel(QImage const& img) { // TODO: make a color version. // In ColorPickupInteraction.cpp we have code for median color finding. // We can use that. QImage const gray(toGrayscale(img)); BinaryImage mask(binarizeOtsu(gray)); mask.invert(); GrayscaleHistogram const hist(gray, mask); 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 const num_colors = 256; int const 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) { int const 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 i; } } assert(!"Unreachable"); return 0; } void OutputGenerator::applyFillZonesInPlace( QImage& img, ZoneSet const& zones, boost::function const& orig_to_output) const { if (zones.empty()) { return; } QImage canvas(img.convertToFormat(QImage::Format_ARGB32_Premultiplied)); { QPainter painter(&canvas); painter.setRenderHint(QPainter::Antialiasing, true); painter.setPen(Qt::NoPen); BOOST_FOREACH(Zone const& zone, zones) { QColor const color(zone.properties().locateOrDefault()->color()); QPolygonF const poly(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()); } } /** * A simplified version of the above, using toOutput() for translation * from original image to output image coordinates. */ void OutputGenerator::applyFillZonesInPlace(QImage& img, ZoneSet const& zones) const { typedef QPointF (QTransform::*MapPointFunc)(QPointF const&) const; applyFillZonesInPlace( img, zones, boost::bind((MapPointFunc)&QTransform::map, m_xform.transform(), _1) ); } void OutputGenerator::applyFillZonesInPlace( imageproc::BinaryImage& img, ZoneSet const& zones, boost::function const& orig_to_output) const { if (zones.empty()) { return; } BOOST_FOREACH(Zone const& zone, zones) { QColor const color(zone.properties().locateOrDefault()->color()); BWColor const bw_color = qGray(color.rgb()) < 128 ? BLACK : WHITE; QPolygonF const poly(zone.spline().transformed(orig_to_output).toPolygon()); PolygonRasterizer::fill(img, bw_color, poly, Qt::WindingFill); } } /** * A simplified version of the above, using toOutput() for translation * from original image to output image coordinates. */ void OutputGenerator::applyFillZonesInPlace( imageproc::BinaryImage& img, ZoneSet const& zones) const { typedef QPointF (QTransform::*MapPointFunc)(QPointF const&) const; applyFillZonesInPlace( img, zones, boost::bind((MapPointFunc)&QTransform::map, m_xform.transform(), _1) ); } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/OutputGenerator.h000066400000000000000000000215021271170121200240070ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_OUTPUTGENERATOR_H_ #define OUTPUT_OUTPUTGENERATOR_H_ #include "imageproc/Connectivity.h" #include "Dpi.h" #include "ColorParams.h" #include "DepthPerception.h" #include "DespeckleLevel.h" #include "DewarpingMode.h" #include "ImageTransformation.h" #ifndef Q_MOC_RUN #include #endif #include #include #include #include #include #include #include #include #include #include class TaskStatus; class DebugImages; class FilterData; class ZoneSet; class QSize; class QImage; namespace imageproc { class BinaryImage; class BinaryThreshold; class GrayImage; } namespace dewarping { class DistortionModel; class CylindricalSurfaceDewarper; } namespace output { class OutputGenerator { public: OutputGenerator( Dpi const& dpi, ColorParams const& color_params, DespeckleLevel despeckle_level, ImageTransformation const& xform, QPolygonF const& 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( TaskStatus const& status, FilterData const& input, ZoneSet const& picture_zones, ZoneSet const& fill_zones, DewarpingMode dewarping_mode, dewarping::DistortionModel& distortion_model, DepthPerception const& depth_perception, imageproc::BinaryImage* auto_picture_mask = 0, imageproc::BinaryImage* speckles_image = 0, DebugImages* dbg = 0) const; QSize outputImageSize() const; /** * \brief Returns the content rectangle in output image coordinates. */ QRect outputContentRect() const; private: QImage processImpl( TaskStatus const& status, FilterData const& input, ZoneSet const& picture_zones, ZoneSet const& fill_zones, DewarpingMode dewarping_mode, dewarping::DistortionModel& distortion_model, DepthPerception const& depth_perception, imageproc::BinaryImage* auto_picture_mask = 0, imageproc::BinaryImage* speckles_image = 0, DebugImages* dbg = 0) const; QImage processAsIs( FilterData const& input, TaskStatus const& status, ZoneSet const& fill_zones, DepthPerception const& depth_perception, DebugImages* dbg = 0) const; QImage processWithoutDewarping( TaskStatus const& status, FilterData const& input, ZoneSet const& picture_zones, ZoneSet const& fill_zones, imageproc::BinaryImage* auto_picture_mask = 0, imageproc::BinaryImage* speckles_image = 0, DebugImages* dbg = 0) const; QImage processWithDewarping( TaskStatus const& status, FilterData const& input, ZoneSet const& picture_zones, ZoneSet const& fill_zones, DewarpingMode dewarping_mode, dewarping::DistortionModel& distortion_model, DepthPerception const& depth_perception, imageproc::BinaryImage* auto_picture_mask = 0, imageproc::BinaryImage* speckles_image = 0, DebugImages* dbg = 0) const; void setupTrivialDistortionModel(dewarping::DistortionModel& distortion_model) const; static dewarping::CylindricalSurfaceDewarper createDewarper( dewarping::DistortionModel const& distortion_model, QTransform const& distortion_model_to_target, double depth_perception); QImage dewarp( QTransform const& orig_to_src, QImage const& src, QTransform const& src_to_output, dewarping::DistortionModel const& distortion_model, DepthPerception const& depth_perception, QColor const& bg_color) const; static QSize from300dpi(QSize const& size, Dpi const& target_dpi); static QSize to300dpi(QSize const& size, Dpi const& source_dpi); static QImage convertToRGBorRGBA(QImage const& src); static void fillMarginsInPlace( QImage& image, QPolygonF const& content_poly, QColor const& color); static imageproc::GrayImage normalizeIlluminationGray( TaskStatus const& status, QImage const& input, QPolygonF const& area_to_consider, QTransform const& xform, QRect const& target_rect, imageproc::GrayImage* background = 0, DebugImages* dbg = 0); static imageproc::GrayImage detectPictures( imageproc::GrayImage const& input_300dpi, TaskStatus const& status, DebugImages* dbg = 0); imageproc::BinaryImage estimateBinarizationMask( TaskStatus const& status, imageproc::GrayImage const& gray_source, QRect const& source_rect, QRect const& source_sub_rect, DebugImages* const dbg) const; void modifyBinarizationMask( imageproc::BinaryImage& bw_mask, QRect const& mask_rect, ZoneSet const& zones) const; imageproc::BinaryThreshold adjustThreshold( imageproc::BinaryThreshold threshold) const; imageproc::BinaryThreshold calcBinarizationThreshold( QImage const& image, imageproc::BinaryImage const& mask) const; imageproc::BinaryThreshold calcBinarizationThreshold( QImage const& image, QPolygonF const& crop_area, imageproc::BinaryImage const* mask = 0) const; imageproc::BinaryImage binarize( QImage const& image, imageproc::BinaryImage const& mask) const; imageproc::BinaryImage binarize( QImage const& image, QPolygonF const& crop_area, imageproc::BinaryImage const* mask = 0) const; void maybeDespeckleInPlace( imageproc::BinaryImage& image, QRect const& image_rect, QRect const& mask_rect, DespeckleLevel level, imageproc::BinaryImage* speckles_img, Dpi const& dpi, TaskStatus const& status, DebugImages* dbg) const; static QImage smoothToGrayscale(QImage const& src, Dpi const& dpi); static void morphologicalSmoothInPlace( imageproc::BinaryImage& img, TaskStatus const& status); static void hitMissReplaceAllDirections( imageproc::BinaryImage& img, char const* pattern, int pattern_width, int pattern_height); static QSize calcLocalWindowSize(Dpi const& dpi); static unsigned char calcDominantBackgroundGrayLevel(QImage const& img); static QImage normalizeIllumination(QImage const& gray_input, DebugImages* dbg); QImage transformAndNormalizeIllumination( QImage const& gray_input, DebugImages* dbg, QImage const* morph_background = 0) const; QImage transformAndNormalizeIllumination2( QImage const& gray_input, DebugImages* dbg, QImage const* morph_background = 0) const; void applyFillZonesInPlace(QImage& img, ZoneSet const& zones, boost::function const& orig_to_output) const; void applyFillZonesInPlace(QImage& img, ZoneSet const& zones) const; void applyFillZonesInPlace(imageproc::BinaryImage& img, ZoneSet const& zones, boost::function const& orig_to_output) const; void applyFillZonesInPlace(imageproc::BinaryImage& img, ZoneSet const& zones) const; Dpi m_dpi; ColorParams m_colorParams; /** * 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 content rectangle in output image coordinates. */ QRect m_contentRect; DespeckleLevel m_despeckleLevel; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/OutputImageParams.cpp000066400000000000000000000137321271170121200246100ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "OutputImageParams.h" #include "ImageTransformation.h" #include "XmlMarshaller.h" #include "XmlUnmarshaller.h" #include "../../Utils.h" #include #include #include #include namespace output { OutputImageParams::OutputImageParams( QSize const& out_image_size, QRect const& content_rect, ImageTransformation xform, Dpi const& dpi, ColorParams const& color_params, DewarpingMode const& dewarping_mode, dewarping::DistortionModel const& distortion_model, DepthPerception const& depth_perception, DespeckleLevel const despeckle_level) : m_size(out_image_size), m_contentRect(content_rect), m_dpi(dpi), m_colorParams(color_params), m_distortionModel(distortion_model), m_depthPerception(depth_perception), m_dewarpingMode(dewarping_mode), m_despeckleLevel(despeckle_level) { // 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(QDomElement const& el) : m_size(XmlUnmarshaller::size(el.namedItem("size").toElement())), m_contentRect(XmlUnmarshaller::rect(el.namedItem("content-rect").toElement())), m_partialXform(el.namedItem("partial-xform").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_dewarpingMode(el.attribute("dewarpingMode")), m_despeckleLevel(despeckleLevelFromString(el.attribute("despeckleLevel"))) { } QDomElement OutputImageParams::toXml(QDomDocument& doc, QString const& 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(m_partialXform.toXml(doc, "partial-xform")); el.appendChild(marshaller.dpi(m_dpi, "dpi")); el.appendChild(m_colorParams.toXml(doc, "color-params")); el.appendChild(m_distortionModel.toXml(doc, "distortion-model")); el.setAttribute("depthPerception", m_depthPerception.toString()); el.setAttribute("dewarpingMode", m_dewarpingMode.toString()); el.setAttribute("despeckleLevel", despeckleLevelToString(m_despeckleLevel)); return el; } bool OutputImageParams::matches(OutputImageParams const& other) const { if (m_size != other.m_size) { return false; } if (m_contentRect != other.m_contentRect) { 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, other.m_colorParams, other.m_despeckleLevel)) { return false; } if (m_dewarpingMode != other.m_dewarpingMode) { return false; } else if (m_dewarpingMode != DewarpingMode::OFF) { if (!m_distortionModel.matches(other.m_distortionModel)) { return false; } if (m_depthPerception.value() != other.m_depthPerception.value()) { return false; } } return true; } bool OutputImageParams::colorParamsMatch( ColorParams const& cp1, DespeckleLevel const dl1, ColorParams const& cp2, DespeckleLevel const dl2) { if (cp1.colorMode() != cp2.colorMode()) { return false; } switch (cp1.colorMode()) { case ColorParams::COLOR_GRAYSCALE: case ColorParams::MIXED: if (cp1.colorGrayscaleOptions() != cp2.colorGrayscaleOptions()) { return false; } break; default:; } switch (cp1.colorMode()) { case ColorParams::BLACK_AND_WHITE: case ColorParams::MIXED: if (cp1.blackWhiteOptions() != cp2.blackWhiteOptions()) { return false; } if (dl1 != dl2) { return false; } break; default:; } return true; } /*=============================== PartialXform =============================*/ OutputImageParams::PartialXform::PartialXform() : m_11(), m_12(), m_21(), m_22() { } OutputImageParams::PartialXform::PartialXform(QTransform const& xform) : m_11(xform.m11()), m_12(xform.m12()), m_21(xform.m21()), m_22(xform.m22()) { } OutputImageParams::PartialXform::PartialXform(QDomElement const& 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()) { } QDomElement OutputImageParams::PartialXform::toXml(QDomDocument& doc, QString const& name) const { 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")); return el; } bool OutputImageParams::PartialXform::matches(PartialXform const& 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); } bool OutputImageParams::PartialXform::closeEnough(double v1, double v2) { return fabs(v1 - v2) < 0.0001; } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/OutputImageParams.h000066400000000000000000000072371271170121200242600ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_OUTPUT_IMAGE_PARAMS_H_ #define OUTPUT_OUTPUT_IMAGE_PARAMS_H_ #include "Dpi.h" #include "ColorParams.h" #include "DewarpingMode.h" #include "dewarping/DistortionModel.h" #include "DepthPerception.h" #include "DespeckleLevel.h" #include #include class ImageTransformation; class QDomDocument; class QDomElement; class QTransform; namespace output { /** * \brief Parameters of the output image used to determine if we need to re-generate it. */ class OutputImageParams { public: OutputImageParams(QSize const& out_image_size, QRect const& content_rect, ImageTransformation xform, Dpi const& dpi, ColorParams const& color_params, DewarpingMode const& dewarping_mode, dewarping::DistortionModel const& distortion_model, DepthPerception const& depth_perception, DespeckleLevel despeckle_level); explicit OutputImageParams(QDomElement const& el); DewarpingMode const& dewarpingMode() const { return m_dewarpingMode; } dewarping::DistortionModel const& distortionModel() const { return m_distortionModel; } void setDistortionModel(dewarping::DistortionModel const& model) { m_distortionModel = model; } DepthPerception const& depthPerception() const { return m_depthPerception; } DespeckleLevel despeckleLevel() const { return m_despeckleLevel; } QDomElement toXml(QDomDocument& doc, QString const& name) const; /** * \brief Returns true if two sets of parameters are close enough * to avoid re-generating the output image. */ bool matches(OutputImageParams const& other) const; private: class PartialXform { public: PartialXform(); PartialXform(QTransform const& xform); PartialXform(QDomElement const& el); QDomElement toXml(QDomDocument& doc, QString const& name) const; bool matches(PartialXform const& other) const; private: static bool closeEnough(double v1, double v2); double m_11; double m_12; double m_21; double m_22; }; static bool colorParamsMatch( ColorParams const& cp1, DespeckleLevel dl1, ColorParams const& cp2, DespeckleLevel dl2); /** Pixel size of the output image. */ QSize m_size; /** Content rectangle in output image coordinates. */ QRect m_contentRect; /** * 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; /** Non-geometric parameters used to generate the output image. */ ColorParams m_colorParams; /** Two curves and two lines connecting their endpoints. Used for dewarping. */ dewarping::DistortionModel m_distortionModel; /** \see imageproc::CylindricalSurfaceDewarper */ DepthPerception m_depthPerception; /** Off / Auto / Manual */ DewarpingMode m_dewarpingMode; /** Despeckle level of the output image. */ DespeckleLevel m_despeckleLevel; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/OutputMargins.h000066400000000000000000000023721271170121200234650ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_OUTPUT_MARGINS_H_ #define OUTPUT_OUTPUT_MARGINS_H_ #include "Margins.h" namespace output { /** * Having margins on the Output stage is useful when creating zones * that are meant to cover a corner or an edge of a page. * We use the same margins on all tabs to preserve their geometrical * one-to-one relationship. */ class OutputMargins : public Margins { public: OutputMargins() : Margins(10.0, 10.0, 10.0, 10.0) {} }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/OutputParams.cpp000066400000000000000000000045651271170121200236510ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "OutputParams.h" #include "PictureZonePropFactory.h" #include "FillZonePropFactory.h" #include #include namespace output { OutputParams::OutputParams( OutputImageParams const& output_image_params, OutputFileParams const& output_file_params, OutputFileParams const& automask_file_params, OutputFileParams const& speckles_file_params, ZoneSet const& picture_zones, ZoneSet const& fill_zones) : m_outputImageParams(output_image_params), m_outputFileParams(output_file_params), m_automaskFileParams(automask_file_params), m_specklesFileParams(speckles_file_params), m_pictureZones(picture_zones), m_fillZones(fill_zones) { } OutputParams::OutputParams(QDomElement const& el) : m_outputImageParams(el.namedItem("image").toElement()), m_outputFileParams(el.namedItem("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, QString const& name) const { QDomElement el(doc.createElement(name)); el.appendChild(m_outputImageParams.toXml(doc, "image")); el.appendChild(m_outputFileParams.toXml(doc, "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; } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/OutputParams.h000066400000000000000000000041371271170121200233110ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_OUTPUT_PARAMS_H_ #define OUTPUT_OUTPUT_PARAMS_H_ #include "OutputImageParams.h" #include "OutputFileParams.h" #include "ZoneSet.h" class QDomDocument; class QDomElement; class QString; namespace output { class OutputParams { public: OutputParams(OutputImageParams const& output_image_params, OutputFileParams const& output_file_params, OutputFileParams const& automask_file_params, OutputFileParams const& speckles_file_params, ZoneSet const& picture_zones, ZoneSet const& fill_zones); explicit OutputParams(QDomElement const& el); QDomElement toXml(QDomDocument& doc, QString const& name) const; OutputImageParams const& outputImageParams() const { return m_outputImageParams; } OutputFileParams const& outputFileParams() const { return m_outputFileParams; } OutputFileParams const& automaskFileParams() const { return m_automaskFileParams; } OutputFileParams const& specklesFileParams() const { return m_specklesFileParams; } ZoneSet const& pictureZones() const { return m_pictureZones; } ZoneSet const& fillZones() const { return m_fillZones; } private: OutputImageParams m_outputImageParams; OutputFileParams m_outputFileParams; OutputFileParams m_automaskFileParams; OutputFileParams m_specklesFileParams; ZoneSet m_pictureZones; ZoneSet m_fillZones; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/Params.cpp000066400000000000000000000065451271170121200224300ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Params.h" #include "ColorGrayscaleOptions.h" #include "BlackWhiteOptions.h" #include "XmlMarshaller.h" #include "XmlUnmarshaller.h" #include #include #include #include namespace output { Params::Params() : m_dpi(600, 600), m_despeckleLevel(DESPECKLE_CAUTIOUS) { } Params::Params(QDomElement const& el) : m_dpi(XmlUnmarshaller::dpi(el.namedItem("dpi").toElement())), m_distortionModel(el.namedItem("distortion-model").toElement()), m_depthPerception(el.attribute("depthPerception")), m_dewarpingMode(el.attribute("dewarpingMode")), m_despeckleLevel(despeckleLevelFromString(el.attribute("despeckleLevel"))) { QDomElement const cp(el.namedItem("color-params").toElement()); m_colorParams.setColorMode(parseColorMode(cp.attribute("colorMode"))); m_colorParams.setColorGrayscaleOptions( ColorGrayscaleOptions( cp.namedItem("color-or-grayscale").toElement() ) ); m_colorParams.setBlackWhiteOptions( BlackWhiteOptions(cp.namedItem("bw").toElement()) ); } QDomElement Params::toXml(QDomDocument& doc, QString const& name) const { XmlMarshaller marshaller(doc); QDomElement el(doc.createElement(name)); el.appendChild(m_distortionModel.toXml(doc, "distortion-model")); el.setAttribute("depthPerception", m_depthPerception.toString()); el.setAttribute("dewarpingMode", m_dewarpingMode.toString()); el.setAttribute("despeckleLevel", despeckleLevelToString(m_despeckleLevel)); el.appendChild(marshaller.dpi(m_dpi, "dpi")); QDomElement cp(doc.createElement("color-params")); cp.setAttribute( "colorMode", formatColorMode(m_colorParams.colorMode()) ); cp.appendChild( m_colorParams.colorGrayscaleOptions().toXml( doc, "color-or-grayscale" ) ); cp.appendChild(m_colorParams.blackWhiteOptions().toXml(doc, "bw")); el.appendChild(cp); return el; } ColorParams::ColorMode Params::parseColorMode(QString const& str) { if (str == "bw") { return ColorParams::BLACK_AND_WHITE; } else if (str == "bitonal") { // Backwards compatibility. return ColorParams::BLACK_AND_WHITE; } else if (str == "colorOrGray") { return ColorParams::COLOR_GRAYSCALE; } else if (str == "mixed") { return ColorParams::MIXED; } else { return ColorParams::BLACK_AND_WHITE; } } QString Params::formatColorMode(ColorParams::ColorMode const mode) { char const* str = ""; switch (mode) { case ColorParams::BLACK_AND_WHITE: str = "bw"; break; case ColorParams::COLOR_GRAYSCALE: str = "colorOrGray"; break; case ColorParams::MIXED: str = "mixed"; break; } return QString::fromAscii(str); } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/Params.h000066400000000000000000000046741271170121200220760ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_PARAMS_H_ #define OUTPUT_PARAMS_H_ #include "Dpi.h" #include "ColorParams.h" #include "DewarpingMode.h" #include "dewarping/DistortionModel.h" #include "DepthPerception.h" #include "DespeckleLevel.h" class QDomDocument; class QDomElement; namespace output { class Params { public: Params(); Params(QDomElement const& el); Dpi const& outputDpi() const { return m_dpi; } void setOutputDpi(Dpi const& dpi) { m_dpi = dpi; } ColorParams const& colorParams() const { return m_colorParams; } void setColorParams(ColorParams const& params) { m_colorParams = params; } DewarpingMode const& dewarpingMode() const { return m_dewarpingMode; } void setDewarpingMode(DewarpingMode const& mode) { m_dewarpingMode = mode; } dewarping::DistortionModel const& distortionModel() const { return m_distortionModel; } void setDistortionModel(dewarping::DistortionModel const& model) { m_distortionModel = model; } DepthPerception const& depthPerception() const { return m_depthPerception; } void setDepthPerception(DepthPerception depth_perception) { m_depthPerception = depth_perception; } DespeckleLevel despeckleLevel() const { return m_despeckleLevel; } void setDespeckleLevel(DespeckleLevel level) { m_despeckleLevel = level; } QDomElement toXml(QDomDocument& doc, QString const& name) const; private: static ColorParams::ColorMode parseColorMode(QString const& str); static QString formatColorMode(ColorParams::ColorMode mode); Dpi m_dpi; ColorParams m_colorParams; dewarping::DistortionModel m_distortionModel; DepthPerception m_depthPerception; DewarpingMode m_dewarpingMode; DespeckleLevel m_despeckleLevel; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/PictureLayerProperty.cpp000066400000000000000000000044541271170121200253570ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "PictureLayerProperty.h" #include "PropertyFactory.h" #include #include #include namespace output { char const PictureLayerProperty::m_propertyName[] = "PictureZoneProperty"; PictureLayerProperty::PictureLayerProperty(QDomElement const& el) : m_layer(layerFromString(el.attribute("layer"))) { } void PictureLayerProperty::registerIn(PropertyFactory& factory) { factory.registerProperty(m_propertyName, &PictureLayerProperty::construct); } IntrusivePtr PictureLayerProperty::clone() const { return IntrusivePtr(new PictureLayerProperty(*this)); } QDomElement PictureLayerProperty::toXml(QDomDocument& doc, QString const& name) const { QDomElement el(doc.createElement(name)); el.setAttribute("type", m_propertyName); el.setAttribute("layer", layerToString(m_layer)); return el; } IntrusivePtr PictureLayerProperty::construct(QDomElement const& el) { return IntrusivePtr(new PictureLayerProperty(el)); } PictureLayerProperty::Layer PictureLayerProperty::layerFromString(QString const& str) { 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) { char const* str = 0; switch (layer) { case ERASER1: str = "eraser1"; break; case PAINTER2: str = "painter2"; break; case ERASER3: str = "eraser3"; break; default: str = ""; break; } return str; } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/PictureLayerProperty.h000066400000000000000000000033061271170121200250170ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_PICTURE_LAYER_PROPERTY_H_ #define OUTPUT_PICTURE_LAYER_PROPERTY_H_ #include "Property.h" #include "IntrusivePtr.h" class PropertyFactory; class QDomDocument; class QDomElement; class QString; namespace output { class PictureLayerProperty : public Property { public: enum Layer { NO_OP, ERASER1, PAINTER2, ERASER3 }; PictureLayerProperty(Layer layer = NO_OP) : m_layer(layer) {} PictureLayerProperty(QDomElement const& el); static void registerIn(PropertyFactory& factory); virtual IntrusivePtr clone() const; virtual QDomElement toXml(QDomDocument& doc, QString const& name) const; Layer layer() const { return m_layer; } void setLayer(Layer layer) { m_layer = layer; } private: static IntrusivePtr construct(QDomElement const& el); static Layer layerFromString(QString const& str); static QString layerToString(Layer layer); static char const m_propertyName[]; Layer m_layer; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/PictureZoneComparator.cpp000066400000000000000000000034351271170121200254770ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "PictureZoneComparator.h" #include "ZoneSet.h" #include "Zone.h" #include "PropertySet.h" #include "PictureLayerProperty.h" #include namespace output { bool PictureZoneComparator::equal(ZoneSet const& lhs, ZoneSet const& rhs) { ZoneSet::const_iterator lhs_it(lhs.begin()); ZoneSet::const_iterator rhs_it(rhs.begin()); ZoneSet::const_iterator const lhs_end(lhs.end()); ZoneSet::const_iterator const 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); } bool PictureZoneComparator::equal(Zone const& lhs, Zone const& rhs) { if (lhs.spline().toPolygon() != rhs.spline().toPolygon()) { return false; } return equal(lhs.properties(), rhs.properties()); } bool PictureZoneComparator::equal(PropertySet const& lhs, PropertySet const& rhs) { typedef PictureLayerProperty PLP; return lhs.locateOrDefault()->layer() == rhs.locateOrDefault()->layer(); } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/PictureZoneComparator.h000066400000000000000000000022031271170121200251340ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PICTURE_ZONE_COMPARATOR_H_ #define PICTURE_ZONE_COMPARATOR_H_ class ZoneSet; class Zone; class PropertySet; namespace output { class PictureZoneComparator { public: static bool equal(ZoneSet const& lhs, ZoneSet const& rhs); static bool equal(Zone const& lhs, Zone const& rhs); static bool equal(PropertySet const& lhs, PropertySet const& rhs); }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/PictureZoneEditor.cpp000066400000000000000000000265161271170121200246230ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "PictureZoneEditor.h" #include "PictureZoneEditor.h.moc" #include "NonCopyable.h" #include "Zone.h" #include "ZoneSet.h" #include "SerializableSpline.h" #include "PropertySet.h" #include "PictureLayerProperty.h" #include "PictureZonePropDialog.h" #include "Settings.h" #include "ImageTransformation.h" #include "ImagePresentation.h" #include "OutputMargins.h" #include "PixmapRenderer.h" #include "BackgroundExecutor.h" #include "AbstractCommand.h" #include "imageproc/Transform.h" #include "imageproc/Constants.h" #include "imageproc/GrayImage.h" #include #include #include #include #include #include #ifndef Q_MOC_RUN #include #include #endif #include namespace output { static QRgb const mask_color = 0xff587ff4; using namespace imageproc; class PictureZoneEditor::MaskTransformTask : public AbstractCommand0 > >, public QObject { DECLARE_NON_COPYABLE(MaskTransformTask) public: MaskTransformTask( PictureZoneEditor* zone_editor, BinaryImage const& orig_mask, QTransform const& xform, QSize const& target_size); void cancel() { m_ptrResult->cancel(); } bool const isCancelled() const { return m_ptrResult->isCancelled(); } virtual IntrusivePtr > operator()(); private: class Result : public AbstractCommand0 { public: Result(PictureZoneEditor* zone_editor); void setData(QPoint const& origin, QImage const& mask); void cancel() { m_cancelFlag.fetchAndStoreRelaxed(1); } bool isCancelled() const { return m_cancelFlag.fetchAndAddRelaxed(0) != 0; } virtual void operator()(); private: QPointer m_ptrZoneEditor; QPoint m_origin; QImage m_mask; mutable QAtomicInt m_cancelFlag; }; IntrusivePtr m_ptrResult; BinaryImage m_origMask; QTransform m_xform; QSize m_targetSize; }; PictureZoneEditor::PictureZoneEditor( QImage const& image, ImagePixmapUnion const& downscaled_image, imageproc::BinaryImage const& picture_mask, QTransform const& image_to_virt, QPolygonF const& virt_display_area, PageId const& page_id, IntrusivePtr const& 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(settings) { m_zones.setDefaultProperties(m_ptrSettings->defaultPictureZoneProperties()); setMouseTracking(true); m_context.setShowPropertiesCommand( boost::bind(&PictureZoneEditor::showPropertiesDialog, 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); 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); BOOST_FOREACH(Zone const& zone, m_ptrSettings->pictureZonesForPage(page_id)) { EditableSpline::Ptr spline(new EditableSpline(zone.spline())); m_zones.addZone(spline, zone.properties()); } } PictureZoneEditor::~PictureZoneEditor() { m_ptrSettings->setDefaultPictureZoneProperties(m_zones.defaultProperties()); } void PictureZoneEditor::onPaint(QPainter& painter, InteractionState const& interaction) { painter.setWorldTransform(QTransform()); painter.setRenderHint(QPainter::Antialiasing); if (!validateScreenPictureMask()) { schedulePictureMaskRebuild(); } else { double const sn = sin(constants::DEG2RAD * m_pictureMaskAnimationPhase); double const scale = 0.5 * (sn + 1.0); // 0 .. 1 double const opacity = 0.35 * scale + 0.15; QPixmap mask(m_screenPictureMask); { QPainter mask_painter(&mask); 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(); } bool PictureZoneEditor::validateScreenPictureMask() const { return !m_screenPictureMask.isNull() && m_screenPictureMaskXform == virtualToWidget(); } void PictureZoneEditor::schedulePictureMaskRebuild() { if (!m_pictureMaskRebuildTimer.isActive() || m_potentialPictureMaskXform != virtualToWidget()) { if (m_ptrMaskTransformTask.get()) { m_ptrMaskTransformTask->cancel(); m_ptrMaskTransformTask.reset(); } m_potentialPictureMaskXform = virtualToWidget(); } m_pictureMaskRebuildTimer.start(); } void PictureZoneEditor::initiateBuildingScreenPictureMask() { if (validateScreenPictureMask()) { return; } m_screenPictureMask = QPixmap(); if (m_ptrMaskTransformTask.get()) { m_ptrMaskTransformTask->cancel(); m_ptrMaskTransformTask.reset(); } QTransform const xform(virtualToWidget()); IntrusivePtr const task( new MaskTransformTask(this, m_origPictureMask, xform, viewport()->size()) ); backgroundExecutor().enqueueTask(task); m_screenPictureMask = QPixmap(); m_ptrMaskTransformTask = task; m_screenPictureMaskXform = xform; } void PictureZoneEditor::screenPictureMaskBuilt(QPoint const& origin, QImage const& mask) { m_screenPictureMask = QPixmap::fromImage(mask); m_screenPictureMaskOrigin = origin; m_pictureMaskAnimationPhase = 270; m_ptrMaskTransformTask.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)); #ifndef Q_WS_X11 // That's how it's supposed to be. painter.setCompositionMode(QPainter::CompositionMode_Clear); #else // QPainter::CompositionMode_Clear doesn't work for arbitrarily shaped // objects on X11, as well as CompositionMode_Source with a transparent // brush. Fortunately, CompositionMode_DestinationOut with a non-transparent // brush does actually work. painter.setCompositionMode(QPainter::CompositionMode_DestinationOut); #endif typedef PictureLayerProperty PLP; // First pass: ERASER1 BOOST_FOREACH(EditableZoneSet::Zone const& zone, m_zones) { if (zone.properties()->locateOrDefault()->layer() == PLP::ERASER1) { painter.drawPolygon(zone.spline()->toPolygon(), Qt::WindingFill); } } painter.setCompositionMode(QPainter::CompositionMode_SourceOver); // Second pass: PAINTER2 BOOST_FOREACH (EditableZoneSet::Zone const& zone, m_zones) { if (zone.properties()->locateOrDefault()->layer() == PLP::PAINTER2) { painter.drawPolygon(zone.spline()->toPolygon(), Qt::WindingFill); } } #ifndef Q_WS_X11 // That's how it's supposed to be. painter.setCompositionMode(QPainter::CompositionMode_Clear); #else // QPainter::CompositionMode_Clear doesn't work for arbitrarily shaped // objects on X11, as well as CompositionMode_Source with a transparent // brush. Fortunately, CompositionMode_DestinationOut with a non-transparent // brush does actually work. painter.setCompositionMode(QPainter::CompositionMode_DestinationOut); #endif // Third pass: ERASER1 BOOST_FOREACH (EditableZoneSet::Zone const& zone, m_zones) { if (zone.properties()->locateOrDefault()->layer() == PLP::ERASER3) { painter.drawPolygon(zone.spline()->toPolygon(), Qt::WindingFill); } } } void PictureZoneEditor::showPropertiesDialog(EditableZoneSet::Zone const& zone) { 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(); } else { zone.properties()->swap(saved_properties); update(); } } void PictureZoneEditor::commitZones() { ZoneSet zones; BOOST_FOREACH(EditableZoneSet::Zone const& zone, m_zones) { zones.add(Zone(*zone.spline(), *zone.properties())); } m_ptrSettings->setPictureZones(m_pageId, zones); emit invalidateThumbnail(m_pageId); } void PictureZoneEditor::updateRequested() { update(); } /*============================= MaskTransformTask ===============================*/ PictureZoneEditor::MaskTransformTask::MaskTransformTask( PictureZoneEditor* zone_editor, BinaryImage const& mask, QTransform const& xform, QSize const& target_size) : m_ptrResult(new Result(zone_editor)), m_origMask(mask), m_xform(xform), m_targetSize(target_size) { } IntrusivePtr > PictureZoneEditor::MaskTransformTask::operator()() { if (isCancelled()) { return IntrusivePtr >(); } QRect const 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 mask(gray_mask.size(), QImage::Format_ARGB32_Premultiplied); mask.fill(mask_color); mask.setAlphaChannel(gray_mask); m_ptrResult->setData(target_rect.topLeft(), mask); return m_ptrResult; } /*===================== MaskTransformTask::Result ===================*/ PictureZoneEditor::MaskTransformTask::Result::Result( PictureZoneEditor* zone_editor) : m_ptrZoneEditor(zone_editor) { } void PictureZoneEditor::MaskTransformTask::Result::setData( QPoint const& origin, QImage const& mask) { m_mask = mask; m_origin = origin; } void PictureZoneEditor::MaskTransformTask::Result::operator()() { if (m_ptrZoneEditor && !isCancelled()) { m_ptrZoneEditor->screenPictureMaskBuilt(m_origin, m_mask); } } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/PictureZoneEditor.h000066400000000000000000000056621271170121200242670ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_PICTURE_ZONE_EDITOR_H_ #define OUTPUT_PICTURE_ZONE_EDITOR_H_ #include "ImageViewBase.h" #include "NonCopyable.h" #include "RefCountable.h" #include "IntrusivePtr.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 class ImageTransformation; class InteractionState; class QPainter; class QMenu; namespace output { class Settings; class PictureZoneEditor : public ImageViewBase, private InteractionHandler { Q_OBJECT public: PictureZoneEditor( QImage const& image, ImagePixmapUnion const& downscaled_image, imageproc::BinaryImage const& picture_mask, QTransform const& image_to_virt, QPolygonF const& virt_display_area, PageId const& page_id, IntrusivePtr const& settings); virtual ~PictureZoneEditor(); signals: void invalidateThumbnail(PageId const& page_id); protected: virtual void onPaint(QPainter& painter, InteractionState const& interaction); private slots: void advancePictureMaskAnimation(); void initiateBuildingScreenPictureMask(); void commitZones(); void updateRequested(); private: class MaskTransformTask; bool validateScreenPictureMask() const; void schedulePictureMaskRebuild(); void screenPictureMaskBuilt(QPoint const& origin, QImage const& mask); void paintOverPictureMask(QPainter& painter); void showPropertiesDialog(EditableZoneSet::Zone const& zone); EditableZoneSet m_zones; // Must go after m_zones. ZoneInteractionContext m_context; 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 IntrusivePtr m_ptrMaskTransformTask; PageId m_pageId; IntrusivePtr m_ptrSettings; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/PictureZonePropDialog.cpp000066400000000000000000000042121271170121200254220ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "PictureZonePropDialog.h" #include "PictureZonePropDialog.h.moc" #include "Property.h" #include "PictureLayerProperty.h" namespace output { PictureZonePropDialog::PictureZonePropDialog( IntrusivePtr const& props, QWidget* parent) : QDialog(parent), m_ptrProps(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; } 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; 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); emit updated(); } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/PictureZonePropDialog.h000066400000000000000000000025111271170121200250670ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_PICTURE_ZONE_PROP_DIALOG_H_ #define OUTPUT_PICTURE_ZONE_PROP_DIALOG_H_ #include "ui_PictureZonePropDialog.h" #include "PropertySet.h" #include "IntrusivePtr.h" #include namespace output { class PictureZonePropDialog : public QDialog { Q_OBJECT public: PictureZonePropDialog(IntrusivePtr const& props, QWidget* parent = 0); signals: void updated(); private slots: void itemToggled(bool selected); private: Ui::PictureZonePropDialog ui; IntrusivePtr m_ptrProps; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/PictureZonePropFactory.cpp000066400000000000000000000017141271170121200256360ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "PictureZonePropFactory.h" #include "PictureLayerProperty.h" namespace output { PictureZonePropFactory::PictureZonePropFactory() { PictureLayerProperty::registerIn(*this); } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/PictureZonePropFactory.h000066400000000000000000000020031271170121200252730ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_PICTURE_ZONE_PROP_FACTORY_H_ #define OUTPUT_PICTURE_ZONE_PROP_FACTORY_H_ #include "PropertyFactory.h" namespace output { class PictureZonePropFactory : public PropertyFactory { public: PictureZonePropFactory(); }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/RenderParams.cpp000066400000000000000000000030451271170121200235600ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "RenderParams.h" #include "ColorParams.h" #include "ColorGrayscaleOptions.h" #include "DespeckleLevel.h" namespace output { RenderParams::RenderParams(ColorParams const& cp) : m_mask(0) { switch (cp.colorMode()) { case ColorParams::BLACK_AND_WHITE: m_mask |= WHITE_MARGINS|NORMALIZE_ILLUMINATION |NEED_BINARIZATION; break; case ColorParams::COLOR_GRAYSCALE: { ColorGrayscaleOptions const opt( cp.colorGrayscaleOptions() ); if (opt.whiteMargins()) { m_mask |= WHITE_MARGINS; if (opt.normalizeIllumination()) { m_mask |= NORMALIZE_ILLUMINATION; } } break; } case ColorParams::MIXED: m_mask |= WHITE_MARGINS|NORMALIZE_ILLUMINATION |NEED_BINARIZATION|MIXED_OUTPUT; break; } } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/RenderParams.h000066400000000000000000000031071271170121200232240ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_RENDER_PARAMS_H_ #define OUTPUT_RENDER_PARAMS_H_ namespace output { class ColorParams; class RenderParams { public: RenderParams() : m_mask(0) {} RenderParams(ColorParams const& color_params); bool whiteMargins() const { return (m_mask & WHITE_MARGINS) != 0; } bool normalizeIllumination() const { return (m_mask & NORMALIZE_ILLUMINATION) != 0; } bool needBinarization() const { return (m_mask & NEED_BINARIZATION) != 0; } bool mixedOutput() const { return (m_mask & MIXED_OUTPUT) != 0; } bool binaryOutput() const { return (m_mask & (NEED_BINARIZATION|MIXED_OUTPUT)) == NEED_BINARIZATION; } private: enum { WHITE_MARGINS = 1, NORMALIZE_ILLUMINATION = 2, NEED_BINARIZATION = 4, MIXED_OUTPUT = 8 }; int m_mask; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/Settings.cpp000066400000000000000000000216621271170121200230020ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Settings.h" #include "Params.h" #include "PictureLayerProperty.h" #include "FillColorProperty.h" #include "RelinkablePath.h" #include "AbstractRelinker.h" #include "../../Utils.h" #ifndef Q_MOC_RUN #include #endif #include #include #include namespace output { Settings::Settings() : m_defaultPictureZoneProps(initialPictureZoneProps()), m_defaultFillZoneProps(initialFillZoneProps()) { } Settings::~Settings() { } void Settings::clear() { QMutexLocker const locker(&m_mutex); initialPictureZoneProps().swap(m_defaultPictureZoneProps); initialFillZoneProps().swap(m_defaultFillZoneProps); m_perPageParams.clear(); m_perPageOutputParams.clear(); m_perPagePictureZones.clear(); m_perPageFillZones.clear(); } void Settings::performRelinking(AbstractRelinker const& relinker) { QMutexLocker const locker(&m_mutex); PerPageParams new_params; PerPageOutputParams new_output_params; PerPageZones new_picture_zones; PerPageZones new_fill_zones; BOOST_FOREACH(PerPageParams::value_type const& kv, m_perPageParams) { RelinkablePath const 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)); } BOOST_FOREACH(PerPageOutputParams::value_type const& kv, m_perPageOutputParams) { RelinkablePath const 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)); } BOOST_FOREACH(PerPageZones::value_type const& kv, m_perPagePictureZones) { RelinkablePath const 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)); } BOOST_FOREACH(PerPageZones::value_type const& kv, m_perPageFillZones) { RelinkablePath const 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)); } m_perPageParams.swap(new_params); m_perPageOutputParams.swap(new_output_params); m_perPagePictureZones.swap(new_picture_zones); m_perPageFillZones.swap(new_fill_zones); } Params Settings::getParams(PageId const& page_id) const { QMutexLocker const locker(&m_mutex); PerPageParams::const_iterator const it(m_perPageParams.find(page_id)); if (it != m_perPageParams.end()) { return it->second; } else { return Params(); } } void Settings::setParams(PageId const& page_id, Params const& params) { QMutexLocker const locker(&m_mutex); Utils::mapSetValue(m_perPageParams, page_id, params); } void Settings::setColorParams(PageId const& page_id, ColorParams const& prms) { QMutexLocker const locker(&m_mutex); PerPageParams::iterator const it(m_perPageParams.lower_bound(page_id)); if (it == m_perPageParams.end() || m_perPageParams.key_comp()(page_id, it->first)) { Params params; params.setColorParams(prms); m_perPageParams.insert(it, PerPageParams::value_type(page_id, params)); } else { it->second.setColorParams(prms); } } void Settings::setDpi(PageId const& page_id, Dpi const& dpi) { QMutexLocker const locker(&m_mutex); PerPageParams::iterator const it(m_perPageParams.lower_bound(page_id)); if (it == m_perPageParams.end() || m_perPageParams.key_comp()(page_id, it->first)) { Params params; params.setOutputDpi(dpi); m_perPageParams.insert(it, PerPageParams::value_type(page_id, params)); } else { it->second.setOutputDpi(dpi); } } void Settings::setDewarpingMode(PageId const& page_id, DewarpingMode const& mode) { QMutexLocker const locker(&m_mutex); PerPageParams::iterator const it(m_perPageParams.lower_bound(page_id)); if (it == m_perPageParams.end() || m_perPageParams.key_comp()(page_id, it->first)) { Params params; params.setDewarpingMode(mode); m_perPageParams.insert(it, PerPageParams::value_type(page_id, params)); } else { it->second.setDewarpingMode(mode); } } void Settings::setDistortionModel(PageId const& page_id, dewarping::DistortionModel const& model) { QMutexLocker const locker(&m_mutex); PerPageParams::iterator const it(m_perPageParams.lower_bound(page_id)); if (it == m_perPageParams.end() || m_perPageParams.key_comp()(page_id, it->first)) { Params params; params.setDistortionModel(model); m_perPageParams.insert(it, PerPageParams::value_type(page_id, params)); } else { it->second.setDistortionModel(model); } } void Settings::setDepthPerception(PageId const& page_id, DepthPerception const& depth_perception) { QMutexLocker const locker(&m_mutex); PerPageParams::iterator const it(m_perPageParams.lower_bound(page_id)); if (it == m_perPageParams.end() || m_perPageParams.key_comp()(page_id, it->first)) { 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(PageId const& page_id, DespeckleLevel level) { QMutexLocker const locker(&m_mutex); PerPageParams::iterator const it(m_perPageParams.lower_bound(page_id)); if (it == m_perPageParams.end() || m_perPageParams.key_comp()(page_id, it->first)) { Params params; params.setDespeckleLevel(level); m_perPageParams.insert(it, PerPageParams::value_type(page_id, params)); } else { it->second.setDespeckleLevel(level); } } std::auto_ptr Settings::getOutputParams(PageId const& page_id) const { QMutexLocker const locker(&m_mutex); PerPageOutputParams::const_iterator const it(m_perPageOutputParams.find(page_id)); if (it != m_perPageOutputParams.end()) { return std::auto_ptr(new OutputParams(it->second)); } else { return std::auto_ptr(); } } void Settings::removeOutputParams(PageId const& page_id) { QMutexLocker const locker(&m_mutex); m_perPageOutputParams.erase(page_id); } void Settings::setOutputParams(PageId const& page_id, OutputParams const& params) { QMutexLocker const locker(&m_mutex); Utils::mapSetValue(m_perPageOutputParams, page_id, params); } ZoneSet Settings::pictureZonesForPage(PageId const& page_id) const { QMutexLocker const locker(&m_mutex); PerPageZones::const_iterator const it(m_perPagePictureZones.find(page_id)); if (it != m_perPagePictureZones.end()) { return it->second; } else { return ZoneSet(); } } ZoneSet Settings::fillZonesForPage(PageId const& page_id) const { QMutexLocker const locker(&m_mutex); PerPageZones::const_iterator const it(m_perPageFillZones.find(page_id)); if (it != m_perPageFillZones.end()) { return it->second; } else { return ZoneSet(); } } void Settings::setPictureZones(PageId const& page_id, ZoneSet const& zones) { QMutexLocker const locker(&m_mutex); Utils::mapSetValue(m_perPagePictureZones, page_id, zones); } void Settings::setFillZones(PageId const& page_id, ZoneSet const& zones) { QMutexLocker const locker(&m_mutex); Utils::mapSetValue(m_perPageFillZones, page_id, zones); } PropertySet Settings::defaultPictureZoneProperties() const { QMutexLocker const locker(&m_mutex); return m_defaultPictureZoneProps; } PropertySet Settings::defaultFillZoneProperties() const { QMutexLocker const locker(&m_mutex); return m_defaultFillZoneProps; } void Settings::setDefaultPictureZoneProperties(PropertySet const& props) { QMutexLocker const locker(&m_mutex); m_defaultPictureZoneProps = props; } void Settings::setDefaultFillZoneProperties(PropertySet const& props) { QMutexLocker const locker(&m_mutex); m_defaultFillZoneProps = props; } PropertySet Settings::initialPictureZoneProps() { PropertySet props; props.locateOrCreate()->setLayer(PictureLayerProperty::PAINTER2); return props; } PropertySet Settings::initialFillZoneProps() { PropertySet props; props.locateOrCreate()->setColor(Qt::white); return props; } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/Settings.h000066400000000000000000000064241271170121200224460ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_SETTINGS_H_ #define OUTPUT_SETTINGS_H_ #include "RefCountable.h" #include "NonCopyable.h" #include "PageId.h" #include "Dpi.h" #include "ColorParams.h" #include "OutputParams.h" #include "DewarpingMode.h" #include "dewarping/DistortionModel.h" #include "DespeckleLevel.h" #include "ZoneSet.h" #include "PropertySet.h" #include #include #include class AbstractRelinker; namespace output { class Params; class Settings : public RefCountable { DECLARE_NON_COPYABLE(Settings) public: Settings(); virtual ~Settings(); void clear(); void performRelinking(AbstractRelinker const& relinker); Params getParams(PageId const& page_id) const; void setParams(PageId const& page_id, Params const& params); void setColorParams(PageId const& page_id, ColorParams const& prms); void setDpi(PageId const& page_id, Dpi const& dpi); void setDewarpingMode(PageId const& page_id, DewarpingMode const& mode); void setDistortionModel(PageId const& page_id, dewarping::DistortionModel const& model); void setDepthPerception(PageId const& page_id, DepthPerception const& depth_perception); void setDespeckleLevel(PageId const& page_id, DespeckleLevel level); std::auto_ptr getOutputParams(PageId const& page_id) const; void removeOutputParams(PageId const& page_id); void setOutputParams(PageId const& page_id, OutputParams const& params); ZoneSet pictureZonesForPage(PageId const& page_id) const; ZoneSet fillZonesForPage(PageId const& page_id) const; void setPictureZones(PageId const& page_id, ZoneSet const& zones); void setFillZones(PageId const& page_id, ZoneSet const& zones); /** * For now, default zone properties are not persistent. * They may become persistent later though. */ PropertySet defaultPictureZoneProperties() const; PropertySet defaultFillZoneProperties() const; void setDefaultPictureZoneProperties(PropertySet const& props); void setDefaultFillZoneProperties(PropertySet const& props); private: typedef std::map PerPageParams; typedef std::map PerPageOutputParams; typedef std::map PerPageZones; static PropertySet initialPictureZoneProps(); 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; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/TabbedImageView.cpp000066400000000000000000000033151271170121200241540ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "TabbedImageView.h.moc" namespace output { TabbedImageView::TabbedImageView(QWidget* parent) : QTabWidget(parent) { connect(this, SIGNAL(currentChanged(int)), SLOT(tabChangedSlot(int))); } void TabbedImageView::addTab(QWidget* widget, QString const& label, ImageViewTab tab) { QTabWidget::addTab(widget, label); m_registry[widget] = tab; } void TabbedImageView::setCurrentTab(ImageViewTab const tab) { int const cnt = count(); for (int i = 0; i < cnt; ++i) { QWidget* wgt = widget(i); std::map::const_iterator it(m_registry.find(wgt)); if (it != m_registry.end()) { if (it->second == tab) { setCurrentIndex(i); break; } } } } void TabbedImageView::tabChangedSlot(int const idx) { QWidget* wgt = widget(idx); std::map::const_iterator it(m_registry.find(wgt)); if (it != m_registry.end()) { emit tabChanged(it->second); } } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/TabbedImageView.h000066400000000000000000000025221271170121200236200ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_TABBED_IMAGE_VIEW_H_ #define OUTPUT_TABBED_IMAGE_VIEW_H_ #include "ImageViewTab.h" #include #include namespace output { class TabbedImageView : public QTabWidget { Q_OBJECT public: TabbedImageView(QWidget* parent = 0); void addTab(QWidget* widget, QString const& label, ImageViewTab tab); public slots: void setCurrentTab(ImageViewTab tab); signals: void tabChanged(ImageViewTab tab); private slots: void tabChangedSlot(int idx); private: std::map m_registry; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/Task.cpp000066400000000000000000000443711271170121200221060ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Task.h" #include "Filter.h" #include "OptionsWidget.h" #include "Params.h" #include "Settings.h" #include "ColorParams.h" #include "OutputParams.h" #include "OutputImageParams.h" #include "OutputFileParams.h" #include "RenderParams.h" #include "FilterUiInterface.h" #include "TaskStatus.h" #include "FilterData.h" #include "ImageView.h" #include "ImageViewTab.h" #include "TabbedImageView.h" #include "PictureZoneComparator.h" #include "PictureZoneEditor.h" #include "FillZoneComparator.h" #include "FillZoneEditor.h" #include "DespeckleState.h" #include "DespeckleView.h" #include "DespeckleVisualization.h" #include "DespeckleLevel.h" #include "DewarpingMode.h" #include "dewarping/DistortionModel.h" #include "dewarping/DewarpingPointMapper.h" #include "DewarpingView.h" #include "ImageId.h" #include "PageId.h" #include "Dpi.h" #include "Dpm.h" #include "Utils.h" #include "ImageTransformation.h" #include "ThumbnailPixmapCache.h" #include "DebugImages.h" #include "OutputGenerator.h" #include "TiffWriter.h" #include "ImageLoader.h" #include "ErrorWidget.h" #include "imageproc/BinaryImage.h" #include "imageproc/PolygonUtils.h" #ifndef Q_MOC_RUN #include #include #endif #include #include #include #include #include #include #include #include #include #include "CommandLine.h" using namespace imageproc; using namespace dewarping; namespace output { class Task::UiUpdater : public FilterResult { Q_DECLARE_TR_FUNCTIONS(output::Task::UiUpdater) public: UiUpdater(IntrusivePtr const& filter, IntrusivePtr const& settings, std::auto_ptr dbg_img, Params const& params, ImageTransformation const& xform, QRect const& virt_content_rect, PageId const& page_id, QImage const& orig_image, QImage const& output_image, BinaryImage const& picture_mask, DespeckleState const& despeckle_state, DespeckleVisualization const& despeckle_visualization, bool batch, bool debug); virtual void updateUI(FilterUiInterface* ui); virtual IntrusivePtr filter() { return m_ptrFilter; } private: IntrusivePtr m_ptrFilter; IntrusivePtr m_ptrSettings; std::auto_ptr m_ptrDbg; Params m_params; ImageTransformation m_xform; 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; DespeckleLevel m_despeckleLevel; bool m_batchProcessing; bool m_debug; }; Task::Task(IntrusivePtr const& filter, IntrusivePtr const& settings, IntrusivePtr const& thumbnail_cache, PageId const& page_id, OutputFileNameGenerator const& out_file_name_gen, ImageViewTab const last_tab, bool const batch, bool const debug) : m_ptrFilter(filter), m_ptrSettings(settings), m_ptrThumbnailCache(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.reset(new DebugImages); } } Task::~Task() { } FilterResultPtr Task::process( TaskStatus const& status, FilterData const& data, QPolygonF const& content_rect_phys) { status.throwIfCancelled(); Params params(m_ptrSettings->getParams(m_pageId)); RenderParams const render_params(params.colorParams()); QString const out_file_path(m_outFileNameGen.filePathFor(m_pageId)); QFileInfo const out_file_info(out_file_path); ImageTransformation new_xform(data.xform()); new_xform.postScaleToDpi(params.outputDpi()); QString const automask_dir(Utils::automaskDir(m_outFileNameGen.outDir())); QString const automask_file_path( QDir(automask_dir).absoluteFilePath(out_file_info.fileName()) ); QFileInfo automask_file_info(automask_file_path); QString const speckles_dir(Utils::specklesDir(m_outFileNameGen.outDir())); QString const speckles_file_path( QDir(speckles_dir).absoluteFilePath(out_file_info.fileName()) ); QFileInfo speckles_file_info(speckles_file_path); bool const need_picture_editor = render_params.mixedOutput() && !m_batchProcessing; bool const need_speckles_image = params.despeckleLevel() != DESPECKLE_OFF && params.colorParams().colorMode() != ColorParams::COLOR_GRAYSCALE && !m_batchProcessing; OutputGenerator const generator( params.outputDpi(), params.colorParams(), params.despeckleLevel(), new_xform, content_rect_phys ); OutputImageParams new_output_image_params( generator.outputImageSize(), generator.outputContentRect(), new_xform, params.outputDpi(), params.colorParams(), params.dewarpingMode(), params.distortionModel(), params.depthPerception(), params.despeckleLevel() ); ZoneSet const new_picture_zones(m_ptrSettings->pictureZonesForPage(m_pageId)); ZoneSet const new_fill_zones(m_ptrSettings->fillZonesForPage(m_pageId)); bool need_reprocess = false; do { // Just to be able to break from it. std::auto_ptr stored_output_params( m_ptrSettings->getOutputParams(m_pageId) ); if (!stored_output_params.get()) { need_reprocess = true; break; } if (!stored_output_params->outputImageParams().matches(new_output_image_params)) { need_reprocess = true; break; } if (!PictureZoneComparator::equal(stored_output_params->pictureZones(), new_picture_zones)) { need_reprocess = true; break; } if (!FillZoneComparator::equal(stored_output_params->fillZones(), new_fill_zones)) { need_reprocess = true; break; } if (!out_file_info.exists()) { need_reprocess = true; break; } if (!stored_output_params->outputFileParams().matches(OutputFileParams(out_file_info))) { need_reprocess = true; break; } if (need_picture_editor) { if (!automask_file_info.exists()) { need_reprocess = true; break; } if (!stored_output_params->automaskFileParams().matches(OutputFileParams(automask_file_info))) { 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; } } } while (false); QImage out_img; BinaryImage automask_img; BinaryImage speckles_img; if (!need_reprocess) { QFile out_file(out_file_path); if (out_file.open(QIODevice::ReadOnly)) { out_img = ImageLoader::load(out_file, 0); } 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 (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. bool const write_automask = render_params.mixedOutput(); bool const write_speckles_file = params.despeckleLevel() != DESPECKLE_OFF && params.colorParams().colorMode() != ColorParams::COLOR_GRAYSCALE; automask_img = BinaryImage(); speckles_img = BinaryImage(); DistortionModel distortion_model; if (params.dewarpingMode() == DewarpingMode::MANUAL) { distortion_model = params.distortionModel(); } // OutputGenerator will write a new distortion model // there, if dewarping mode is AUTO. out_img = generator.process( status, data, new_picture_zones, new_fill_zones, params.dewarpingMode(), distortion_model, params.depthPerception(), write_automask ? &automask_img : 0, write_speckles_file ? &speckles_img : 0, m_ptrDbg.get() ); if (params.dewarpingMode() == DewarpingMode::AUTO && 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 (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); } bool invalidate_params = false; if (!TiffWriter::writeImage(out_file_path, out_img)) { invalidate_params = true; } else { deleteMutuallyExclusiveOutputFiles(); } 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 (invalidate_params) { m_ptrSettings->removeOutputParams(m_pageId); } else { // Note that we can't reuse *_file_info objects // as we've just overwritten those files. OutputParams const out_params( new_output_image_params, OutputFileParams(QFileInfo(out_file_path)), 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); } m_ptrThumbnailCache->recreateThumbnail(ImageId(out_file_path), out_img); } DespeckleState const 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 FilterResultPtr( new UiUpdater( m_ptrFilter, m_ptrSettings, m_ptrDbg, params, new_xform, generator.outputContentRect(), m_pageId, data.origImage(), out_img, automask_img, despeckle_state, despeckle_visualization, m_batchProcessing, m_debug ) ); } else { return FilterResultPtr(0); } } /** * 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; } } /*============================ Task::UiUpdater ==========================*/ Task::UiUpdater::UiUpdater( IntrusivePtr const& filter, IntrusivePtr const& settings, std::auto_ptr dbg_img, Params const& params, ImageTransformation const& xform, QRect const& virt_content_rect, PageId const& page_id, QImage const& orig_image, QImage const& output_image, BinaryImage const& picture_mask, DespeckleState const& despeckle_state, DespeckleVisualization const& despeckle_visualization, bool const batch, bool const debug) : m_ptrFilter(filter), m_ptrSettings(settings), m_ptrDbg(dbg_img), m_params(params), m_xform(xform), 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; } std::auto_ptr image_view( new ImageView(m_outputImage, m_downscaledOutputImage) ); QPixmap const downscaled_output_pixmap(image_view->downscaledPixmap()); std::auto_ptr dewarping_view( new DewarpingView( m_origImage, m_downscaledOrigImage, m_xform.transform(), PolygonUtils::convexHull( (m_xform.resultingPreCropArea() + m_xform.resultingPostCropArea()).toStdVector() ), m_virtContentRect, m_pageId, m_params.dewarpingMode(), m_params.distortionModel(), opt_widget->depthPerception() ) ); QPixmap const 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(dewarping::DistortionModel const&)), opt_widget, SLOT(distortionModelChanged(dewarping::DistortionModel const&)) ); std::auto_ptr picture_zone_editor; if (m_pictureMask.isNull()) { picture_zone_editor.reset( new ErrorWidget(tr("Picture zones are only available in Mixed mode.")) ); } else { picture_zone_editor.reset( new PictureZoneEditor( m_origImage, downscaled_orig_pixmap, m_pictureMask, m_xform.transform(), m_xform.resultingPostCropArea(), m_pageId, m_ptrSettings ) ); QObject::connect( picture_zone_editor.get(), SIGNAL(invalidateThumbnail(PageId const&)), opt_widget, SIGNAL(invalidateThumbnail(PageId const&)) ); } // 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.dewarpingMode() != DewarpingMode::OFF && m_params.distortionModel().isValid()) { boost::shared_ptr mapper( new DewarpingPointMapper( m_params.distortionModel(), m_params.depthPerception().value(), m_xform.transform(), m_virtContentRect ) ); orig_to_output = boost::bind(&DewarpingPointMapper::mapToDewarpedSpace, mapper, _1); output_to_orig = boost::bind(&DewarpingPointMapper::mapToWarpedSpace, mapper, _1); } else { typedef QPointF (QTransform::*MapPointFunc)(QPointF const&) 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::auto_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(PageId const&)), opt_widget, SIGNAL(invalidateThumbnail(PageId const&)) ); std::auto_ptr despeckle_view; if (m_params.colorParams().colorMode() == ColorParams::COLOR_GRAYSCALE) { despeckle_view.reset( new ErrorWidget(tr("Despeckling can't be done in Color / Grayscale mode.")) ); } else { despeckle_view.reset( new DespeckleView( m_despeckleState, m_despeckleVisualization, m_debug ) ); QObject::connect( opt_widget, SIGNAL(despeckleLevelChanged(DespeckleLevel, bool*)), despeckle_view.get(), SLOT(despeckleLevelChanged(DespeckleLevel, bool*)) ); } std::auto_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()); QObject::connect( tab_widget.get(), SIGNAL(tabChanged(ImageViewTab)), opt_widget, SLOT(tabChanged(ImageViewTab)) ); ui->setImageWidget(tab_widget.release(), ui->TRANSFER_OWNERSHIP, m_ptrDbg.get()); } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/Task.h000066400000000000000000000042141271170121200215430ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_TASK_H_ #define OUTPUT_TASK_H_ #include "NonCopyable.h" #include "RefCountable.h" #include "FilterResult.h" #include "PageId.h" #include "ImageViewTab.h" #include "OutputFileNameGenerator.h" #include #include class DebugImages; class TaskStatus; class FilterData; class ThumbnailPixmapCache; class ImageTransformation; class QPolygonF; class QSize; class QImage; class Dpi; namespace imageproc { class BinaryImage; } namespace output { class Filter; class Settings; class Task : public RefCountable { DECLARE_NON_COPYABLE(Task) public: Task(IntrusivePtr const& filter, IntrusivePtr const& settings, IntrusivePtr const& thumbnail_cache, PageId const& page_id, OutputFileNameGenerator const& out_file_name_gen, ImageViewTab last_tab, bool batch, bool debug); virtual ~Task(); FilterResultPtr process( TaskStatus const& status, FilterData const& data, QPolygonF const& content_rect_phys); private: class UiUpdater; void deleteMutuallyExclusiveOutputFiles(); IntrusivePtr m_ptrFilter; IntrusivePtr m_ptrSettings; IntrusivePtr m_ptrThumbnailCache; std::auto_ptr m_ptrDbg; PageId m_pageId; OutputFileNameGenerator m_outFileNameGen; ImageViewTab m_lastTab; bool m_batchProcessing; bool m_debug; }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/Thumbnail.cpp000066400000000000000000000021111271170121200231110ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Thumbnail.h" namespace output { Thumbnail::Thumbnail( IntrusivePtr const& thumbnail_cache, QSizeF const& max_size, ImageId const& image_id, ImageTransformation const& xform) : ThumbnailBase(thumbnail_cache, max_size, image_id, xform) { } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/Thumbnail.h000066400000000000000000000023501271170121200225630ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_THUMBNAIL_H_ #define OUTPUT_THUMBNAIL_H_ #include "ThumbnailBase.h" #include "IntrusivePtr.h" class ThumbnailPixmapCache; class ImageTransformation; class ImageId; class QSizeF; namespace output { class Thumbnail : public ThumbnailBase { public: Thumbnail(IntrusivePtr const& thumbnail_cache, QSizeF const& max_size, ImageId const& image_id, ImageTransformation const& xform); }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/Utils.cpp000066400000000000000000000027301271170121200222750ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Utils.h" #include "Dpi.h" #include #include #include namespace output { QString Utils::automaskDir(QString const& out_dir) { return QDir(out_dir).absoluteFilePath("cache/automask"); } QString Utils::predespeckleDir(QString const& out_dir) { return QDir(out_dir).absoluteFilePath("cache/predespeckle"); } QString Utils::specklesDir(QString const& out_dir) { return QDir(out_dir).absoluteFilePath("cache/speckles"); } QTransform Utils::scaleFromToDpi(Dpi const& from, Dpi const& to) { QTransform xform; xform.scale( (double)to.horizontal() / from.horizontal(), (double)to.vertical() / from.vertical() ); return xform; } } // namespace output scantailor-RELEASE_0_9_12_2/filters/output/Utils.h000066400000000000000000000022631271170121200217430ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OUTPUT_UTILS_H_ #define OUTPUT_UTILS_H_ class Dpi; class QString; class QTransform; namespace output { class Utils { public: static QString automaskDir(QString const& out_dir); static QString predespeckleDir(QString const& out_dir); static QString specklesDir(QString const& out_dir); static QTransform scaleFromToDpi(Dpi const& from, Dpi const& to); }; } // namespace output #endif scantailor-RELEASE_0_9_12_2/filters/output/ui/000077500000000000000000000000001271170121200211045ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/filters/output/ui/OutputApplyColorsDialog.ui000066400000000000000000000100711271170121200262520ustar00rootroot00000000000000 OutputApplyColorsDialog Qt::WindowModal 0 0 320 195 Apply Mode Apply to This page only (already applied) true All pages This page and the following ones 0 0 Selected pages 0 Qt::Horizontal QSizePolicy::Fixed 30 0 7 Use Ctrl+Click / Shift+Click to select multiple pages. Qt::Vertical 20 0 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox rejected() OutputApplyColorsDialog reject() 316 260 286 274 scantailor-RELEASE_0_9_12_2/filters/output/ui/OutputChangeDewarpingDialog.ui000066400000000000000000000117451271170121200270420ustar00rootroot00000000000000 OutputChangeDewarpingDialog Qt::WindowModal 0 0 320 287 Apply Dewarping Mode Mode Off true true Auto (experimental) Manual Scope This page only true All pages This page and the following ones 0 0 Selected pages 0 Qt::Horizontal QSizePolicy::Fixed 30 0 7 Use Ctrl+Click / Shift+Click to select multiple pages. Qt::Vertical 20 0 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox rejected() OutputChangeDewarpingDialog reject() 316 260 286 274 scantailor-RELEASE_0_9_12_2/filters/output/ui/OutputChangeDpiDialog.ui000066400000000000000000000123221271170121200256260ustar00rootroot00000000000000 OutputChangeDpiDialog Qt::WindowModal 0 0 320 266 Apply Output Resolution DPI Qt::Horizontal 0 20 QComboBox::NoInsert true Qt::Horizontal 0 20 Scope This page only true All pages This page and the following ones 0 0 Selected pages 0 Qt::Horizontal QSizePolicy::Fixed 30 0 7 Use Ctrl+Click / Shift+Click to select multiple pages. Qt::Vertical 20 0 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox rejected() OutputChangeDpiDialog reject() 316 260 286 274 scantailor-RELEASE_0_9_12_2/filters/output/ui/OutputOptionsWidget.ui000066400000000000000000000502551271170121200254720ustar00rootroot00000000000000 OutputOptionsWidget 0 0 228 621 Form Output Resolution (DPI) Qt::Horizontal 0 20 Qt::Horizontal 0 20 Qt::Horizontal 0 20 Change ... Qt::Horizontal 0 20 Mode false false Qt::Horizontal 0 20 Qt::Horizontal 0 20 0 Qt::Horizontal 13 17 White margins Equalize illumination Qt::Horizontal 13 17 Qt::Horizontal 40 20 Qt::Horizontal 40 20 -30 30 5 true Qt::Horizontal false false QSlider::TicksBelow 0 Thinner PointingHandCursor 12 12 Qt::ToolButtonIconOnly true Qt::UpArrow Thicker Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::Horizontal 0 20 Apply To ... Qt::Horizontal 0 20 Dewarping Qt::Horizontal 0 20 Qt::Horizontal 0 20 Qt::Horizontal 40 20 Change ... Qt::Horizontal 40 20 Depth perception 10 30 5 10 Qt::Horizontal Qt::Horizontal 40 20 Apply To ... Qt::Horizontal 40 20 Despeckling 6 Qt::Horizontal 40 20 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 Aggressive despeckling ... :/icons/despeckle-aggressive.png.png:/icons/despeckle-aggressive.png.png 32 32 true true Qt::Horizontal 40 20 Qt::Horizontal 0 20 Apply To ... Qt::Horizontal 0 20 Qt::Vertical 20 40 scantailor-RELEASE_0_9_12_2/filters/output/ui/PictureZonePropDialog.ui000066400000000000000000000043631271170121200257010ustar00rootroot00000000000000 PictureZonePropDialog 0 0 366 125 Zone Properties Subtract from all layers Add to auto layer Subtract from auto layer Qt::Vertical 20 40 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() PictureZonePropDialog accept() 248 254 157 274 buttonBox rejected() PictureZonePropDialog reject() 316 260 286 274 scantailor-RELEASE_0_9_12_2/filters/page_layout/000077500000000000000000000000001271170121200214405ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/filters/page_layout/Alignment.cpp000066400000000000000000000037121271170121200240650ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Alignment.h" #include #include #include namespace page_layout { Alignment::Alignment(QDomElement const& el) { QString const vert(el.attribute("vert")); QString const hor(el.attribute("hor")); m_isNull = el.attribute("null").toInt() != 0; if (vert == "top") { m_vert = TOP; } else if (vert == "bottom") { m_vert = BOTTOM; } else { m_vert = VCENTER; } if (hor == "left") { m_hor = LEFT; } else if (hor == "right") { m_hor = RIGHT; } else { m_hor = HCENTER; } } QDomElement Alignment::toXml(QDomDocument& doc, QString const& name) const { char const* vert = 0; switch (m_vert) { case TOP: vert = "top"; break; case VCENTER: vert = "vcenter"; break; case BOTTOM: vert = "bottom"; break; } char const* hor = 0; switch (m_hor) { case LEFT: hor = "left"; break; case HCENTER: hor = "hcenter"; break; case RIGHT: hor = "right"; break; } QDomElement el(doc.createElement(name)); el.setAttribute("vert", QString::fromAscii(vert)); el.setAttribute("hor", QString::fromAscii(hor)); el.setAttribute("null", m_isNull ? 1 : 0); return el; } } // namespace page_layout scantailor-RELEASE_0_9_12_2/filters/page_layout/Alignment.h000066400000000000000000000037261271170121200235370ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_LAYOUT_ALIGNMENT_H_ #define PAGE_LAYOUT_ALIGNMENT_H_ class QDomDocument; class QDomElement; class QString; namespace page_layout { class Alignment { public: enum Vertical { TOP, VCENTER, BOTTOM }; enum Horizontal { LEFT, HCENTER, RIGHT }; /** * \brief Constructs a null alignment. */ Alignment() : m_vert(VCENTER), m_hor(HCENTER), m_isNull(true) {} Alignment(Vertical vert, Horizontal hor) : m_vert(vert), m_hor(hor), m_isNull(false) {} Alignment(QDomElement const& el); Vertical vertical() const { return m_vert; } void setVertical(Vertical vert) { m_vert = vert; } Horizontal horizontal() const { return m_hor; } void setHorizontal(Horizontal hor) { m_hor = hor; } bool isNull() const { return m_isNull; } void setNull(bool is_null) { m_isNull = is_null; } bool operator==(Alignment const& other) const { return m_vert == other.m_vert && m_hor == other.m_hor && m_isNull == other.m_isNull; } bool operator!=(Alignment const& other) const { return !(*this == other); } QDomElement toXml(QDomDocument& doc, QString const& name) const; private: Vertical m_vert; Horizontal m_hor; bool m_isNull; }; } // namespace page_layout #endif scantailor-RELEASE_0_9_12_2/filters/page_layout/ApplyDialog.cpp000066400000000000000000000055131271170121200243550ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ApplyDialog.h" #include "ApplyDialog.h.moc" #include "PageSelectionAccessor.h" #include #include namespace page_layout { ApplyDialog::ApplyDialog(QWidget* parent, PageId const& cur_page, PageSelectionAccessor const& 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(everyOtherSelectedRB); if (m_selectedPages.size() <= 1) { selectedPagesWidget->setEnabled(false); everyOtherSelectedWidget->setEnabled(false); everyOtherSelectedHint->setText(selectedPagesHint->text()); } else if (m_selectedRanges.size() > 1) { everyOtherSelectedWidget->setEnabled(false); everyOtherSelectedHint->setText(tr("Can't do: more than one group is selected.")); } connect(buttonBox, SIGNAL(accepted()), this, SLOT(onSubmit())); } ApplyDialog::~ApplyDialog() { } void ApplyDialog::onSubmit() { 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(); return; } else if (everyOtherRB->isChecked()) { m_pages.selectEveryOther(m_curPage).swap(pages); } else if (everyOtherSelectedRB->isChecked()) { assert(m_selectedRanges.size() == 1); PageRange const& range = m_selectedRanges.front(); range.selectEveryOther(m_curPage).swap(pages); } emit accepted(pages); // We assume the default connection from accepted() to accept() // was removed. accept(); } } // namespace page_layout scantailor-RELEASE_0_9_12_2/filters/page_layout/ApplyDialog.h000066400000000000000000000031331271170121200240160ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_LAYOUT_APPLYDIALOG_H_ #define PAGE_LAYOUT_APPLYDIALOG_H_ #include "ui_PageLayoutApplyDialog.h" #include "PageId.h" #include "PageRange.h" #include "PageSequence.h" #include "IntrusivePtr.h" #include #include class PageSelectionAccessor; class QButtonGroup; namespace page_layout { class ApplyDialog : public QDialog, private Ui::PageLayoutApplyDialog { Q_OBJECT public: ApplyDialog(QWidget* parent, PageId const& cur_page, PageSelectionAccessor const& page_selection_accessor); virtual ~ApplyDialog(); signals: void accepted(std::set const& pages); private slots: void onSubmit(); private: PageSequence m_pages; std::set m_selectedPages; std::vector m_selectedRanges; PageId m_curPage; QButtonGroup* m_pScopeGroup; }; } // namespace page_layout #endif scantailor-RELEASE_0_9_12_2/filters/page_layout/CMakeLists.txt000066400000000000000000000016021271170121200241770ustar00rootroot00000000000000PROJECT("Page Layout Filter") INCLUDE_DIRECTORIES(BEFORE "${CMAKE_CURRENT_BINARY_DIR}") FILE(GLOB ui_files "ui/*.ui") QT4_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}) QT4_AUTOMOC(${sources}) ADD_LIBRARY(page_layout STATIC ${sources} ${ui_sources}) TRANSLATION_SOURCES(scantailor ${sources} ${ui_files})scantailor-RELEASE_0_9_12_2/filters/page_layout/CacheDrivenTask.cpp000066400000000000000000000057401271170121200251500ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "CacheDrivenTask.h" #include "Settings.h" #include "Params.h" #include "Thumbnail.h" #include "IncompleteThumbnail.h" #include "ImageTransformation.h" #include "PageInfo.h" #include "PageId.h" #include "Utils.h" #include "filters/output/CacheDrivenTask.h" #include "filter_dc/AbstractFilterDataCollector.h" #include "filter_dc/ThumbnailCollector.h" #include #include #include #include namespace page_layout { CacheDrivenTask::CacheDrivenTask( IntrusivePtr const& next_task, IntrusivePtr const& settings) : m_ptrNextTask(next_task), m_ptrSettings(settings) { } CacheDrivenTask::~CacheDrivenTask() { } void CacheDrivenTask::process( PageInfo const& page_info, AbstractFilterDataCollector* collector, ImageTransformation const& xform, QRectF const& content_rect) { std::auto_ptr const params( m_ptrSettings->getPageParams(page_info.id()) ); if (!params.get() || !params->contentSizeMM().isValid()) { if (ThumbnailCollector* thumb_col = dynamic_cast(collector)) { thumb_col->processThumbnail( std::auto_ptr( new IncompleteThumbnail( thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), page_info.imageId(), xform ) ) ); } return; } QRectF const adapted_content_rect( Utils::adaptContentRect(xform, content_rect) ); QPolygonF const content_rect_phys( xform.transformBack().map(adapted_content_rect) ); QPolygonF const page_rect_phys( Utils::calcPageRectPhys( xform, content_rect_phys, *params, m_ptrSettings->getAggregateHardSizeMM() ) ); ImageTransformation new_xform(xform); new_xform.setPostCropArea(xform.transform().map(page_rect_phys)); if (m_ptrNextTask) { m_ptrNextTask->process(page_info, collector, new_xform, content_rect_phys); return; } if (ThumbnailCollector* thumb_col = dynamic_cast(collector)) { thumb_col->processThumbnail( std::auto_ptr( new Thumbnail( thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), page_info.imageId(), *params, new_xform, content_rect_phys ) ) ); } } } // namespace page_layout scantailor-RELEASE_0_9_12_2/filters/page_layout/CacheDrivenTask.h000066400000000000000000000032031271170121200246050ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_LAYOUT_CACHEDRIVENTASK_H_ #define PAGE_LAYOUT_CACHEDRIVENTASK_H_ #include "NonCopyable.h" #include "RefCountable.h" #include "IntrusivePtr.h" class QRectF; class PageInfo; class AbstractFilterDataCollector; class ImageTransformation; namespace output { class CacheDrivenTask; } namespace page_layout { class Settings; class CacheDrivenTask : public RefCountable { DECLARE_NON_COPYABLE(CacheDrivenTask) public: CacheDrivenTask(IntrusivePtr const& next_task, IntrusivePtr const& settings); virtual ~CacheDrivenTask(); void process( PageInfo const& page_info, AbstractFilterDataCollector* collector, ImageTransformation const& xform, QRectF const& content_rect); private: IntrusivePtr m_ptrNextTask; IntrusivePtr m_ptrSettings; }; } // namespace page_layout #endif scantailor-RELEASE_0_9_12_2/filters/page_layout/Filter.cpp000066400000000000000000000137541271170121200234030ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Filter.h" #include "FilterUiInterface.h" #include "OptionsWidget.h" #include "Task.h" #include "PageId.h" #include "Settings.h" #include "Margins.h" #include "Alignment.h" #include "Params.h" #include "ProjectPages.h" #include "ProjectReader.h" #include "ProjectWriter.h" #include "CacheDrivenTask.h" #include "OrderByWidthProvider.h" #include "OrderByHeightProvider.h" #include "Utils.h" #ifndef Q_MOC_RUN #include #include #endif #include #include #include #include #include #include #include #include #include "CommandLine.h" namespace page_layout { Filter::Filter(IntrusivePtr const& pages, PageSelectionAccessor const& page_selection_accessor) : m_ptrPages(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; ProviderPtr const default_order; ProviderPtr const order_by_width(new OrderByWidthProvider(m_ptrSettings)); ProviderPtr const order_by_height(new OrderByHeightProvider(m_ptrSettings)); m_pageOrderOptions.push_back(PageOrderOption(tr("Natural order"), default_order)); m_pageOrderOptions.push_back(PageOrderOption(tr("Order by increasing width"), order_by_width)); m_pageOrderOptions.push_back(PageOrderOption(tr("Order by increasing height"), order_by_height)); } Filter::~Filter() { } QString Filter::getName() const { return tr("Margins"); } PageView Filter::getView() const { return PAGE_VIEW; } void Filter::selected() { m_ptrSettings->removePagesMissingFrom(m_ptrPages->toPageSequence(getView())); } int Filter::selectedPageOrder() const { return m_selectedPageOrder; } void Filter::selectPageOrder(int option) { assert((unsigned)option < m_pageOrderOptions.size()); m_selectedPageOrder = option; } std::vector Filter::pageOrderOptions() const { return m_pageOrderOptions; } void Filter::performRelinking(AbstractRelinker const& relinker) { m_ptrSettings->performRelinking(relinker); } void Filter::preUpdateUI(FilterUiInterface* ui, PageId const& page_id) { Margins const margins_mm(m_ptrSettings->getHardMarginsMM(page_id)); Alignment const alignment(m_ptrSettings->getPageAlignment(page_id)); m_ptrOptionsWidget->preUpdateUI(page_id, margins_mm, alignment); ui->setOptionsWidget(m_ptrOptionsWidget.get(), ui->KEEP_OWNERSHIP); } QDomElement Filter::saveSettings( ProjectWriter const& writer, QDomDocument& doc) const { using namespace boost::lambda; QDomElement filter_el(doc.createElement("page-layout")); writer.enumPages( boost::lambda::bind( &Filter::writePageSettings, this, boost::ref(doc), var(filter_el), boost::lambda::_1, boost::lambda::_2 ) ); return filter_el; } void Filter::writePageSettings( QDomDocument& doc, QDomElement& filter_el, PageId const& page_id, int numeric_id) const { std::auto_ptr const params(m_ptrSettings->getPageParams(page_id)); if (!params.get()) { return; } QDomElement page_el(doc.createElement("page")); page_el.setAttribute("id", numeric_id); page_el.appendChild(params->toXml(doc, "params")); filter_el.appendChild(page_el); } void Filter::loadSettings(ProjectReader const& reader, QDomElement const& filters_el) { m_ptrSettings->clear(); QDomElement const filter_el( filters_el.namedItem("page-layout").toElement() ); QString const 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; } QDomElement const el(node.toElement()); bool ok = true; int const id = el.attribute("id").toInt(&ok); if (!ok) { continue; } PageId const page_id(reader.pageId(id)); if (page_id.isNull()) { continue; } QDomElement const params_el(el.namedItem("params").toElement()); if (params_el.isNull()) { continue; } Params const params(params_el); m_ptrSettings->setPageParams(page_id, params); } } void Filter::setContentBox( PageId const& page_id, ImageTransformation const& xform, QRectF const& content_rect) { QSizeF const content_size_mm(Utils::calcRectSizeMM(xform, content_rect)); m_ptrSettings->setContentSizeMM(page_id, content_size_mm); } void Filter::invalidateContentBox(PageId const& page_id) { m_ptrSettings->invalidateContentSize(page_id); } bool Filter::checkReadyForOutput(ProjectPages const& pages, PageId const* ignore) { PageSequence const snapshot(pages.toPageSequence(PAGE_VIEW)); return m_ptrSettings->checkEverythingDefined(snapshot, ignore); } IntrusivePtr Filter::createTask( PageId const& page_id, IntrusivePtr const& next_task, bool const batch, bool const debug) { return IntrusivePtr( new Task( IntrusivePtr(this), next_task, m_ptrSettings, page_id, batch, debug ) ); } IntrusivePtr Filter::createCacheDrivenTask( IntrusivePtr const& next_task) { return IntrusivePtr( new CacheDrivenTask(next_task, m_ptrSettings) ); } } // namespace page_layout scantailor-RELEASE_0_9_12_2/filters/page_layout/Filter.h000066400000000000000000000062271271170121200230450ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_LAYOUT_FILTER_H_ #define PAGE_LAYOUT_FILTER_H_ #include "NonCopyable.h" #include "AbstractFilter.h" #include "PageView.h" #include "IntrusivePtr.h" #include "FilterResult.h" #include "SafeDeletingQObjectPtr.h" #include "PageOrderOption.h" #include #include class PageId; class ProjectPages; class PageSelectionAccessor; class ImageTransformation; class QString; class QRectF; namespace output { class Task; class CacheDrivenTask; } namespace page_layout { class OptionsWidget; class Task; class CacheDrivenTask; class Settings; class Filter : public AbstractFilter { DECLARE_NON_COPYABLE(Filter) Q_DECLARE_TR_FUNCTIONS(page_layout::Filter) public: Filter(IntrusivePtr const& page_sequence, PageSelectionAccessor const& page_selection_accessor); virtual ~Filter(); virtual QString getName() const; virtual PageView getView() const; virtual void selected(); virtual int selectedPageOrder() const; virtual void selectPageOrder(int option); virtual std::vector pageOrderOptions() const; virtual void performRelinking(AbstractRelinker const& relinker); virtual void preUpdateUI(FilterUiInterface* ui, PageId const& page_id); virtual QDomElement saveSettings( ProjectWriter const& writer, QDomDocument& doc) const; virtual void loadSettings( ProjectReader const& reader, QDomElement const& filters_el); void setContentBox( PageId const& page_id, ImageTransformation const& xform, QRectF const& content_rect); void invalidateContentBox(PageId const& page_id); bool checkReadyForOutput( ProjectPages const& pages, PageId const* ignore = 0); IntrusivePtr createTask( PageId const& page_id, IntrusivePtr const& next_task, bool batch, bool debug); IntrusivePtr createCacheDrivenTask( IntrusivePtr const& next_task); OptionsWidget* optionsWidget() { return m_ptrOptionsWidget.get(); }; Settings* getSettings() { return m_ptrSettings.get(); }; private: void writePageSettings( QDomDocument& doc, QDomElement& filter_el, PageId const& page_id, int numeric_id) const; IntrusivePtr m_ptrPages; IntrusivePtr m_ptrSettings; SafeDeletingQObjectPtr m_ptrOptionsWidget; std::vector m_pageOrderOptions; int m_selectedPageOrder; }; } // namespace page_layout #endif scantailor-RELEASE_0_9_12_2/filters/page_layout/ImageView.cpp000066400000000000000000000473631271170121200240360ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ImageView.h" #include "ImageView.h.moc" #include "OptionsWidget.h" #include "Margins.h" #include "Settings.h" #include "ImageTransformation.h" #include "ImagePresentation.h" #include "Utils.h" #include "imageproc/PolygonUtils.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifndef Q_MOC_RUN #include #include #endif #include #include #include using namespace imageproc; namespace page_layout { ImageView::ImageView( IntrusivePtr const& settings, PageId const& page_id, QImage const& image, QImage const& downscaled_image, ImageTransformation const& xform, QRectF const& adapted_content_rect, OptionsWidget const& opt_widget) : ImageViewBase( image, downscaled_image, ImagePresentation(xform.transform(), xform.resultingPreCropArea()), Margins(5, 5, 5, 5) ), m_dragHandler(*this), m_zoomHandler(*this), m_ptrSettings(settings), m_pageId(page_id), m_physXform(xform.origDpi()), 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 int const masks_by_edge[] = { TOP, RIGHT, BOTTOM, LEFT }; static int const 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]); } rootInteractionHandler().makeLastFollower(*this); rootInteractionHandler().makeLastFollower(m_dragHandler); rootInteractionHandler().makeLastFollower(m_zoomHandler); recalcBoxesAndFit(opt_widget.marginsMM()); } ImageView::~ImageView() { } void ImageView::marginsSetExternally(Margins const& margins_mm) { AggregateSizeChanged const changed = commitHardMargins(margins_mm); recalcBoxesAndFit(margins_mm); invalidateThumbnails(changed); } void ImageView::leftRightLinkToggled(bool const linked) { m_leftRightLinked = linked; if (linked) { Margins margins_mm(calcHardMarginsMM()); if (margins_mm.left() != margins_mm.right()) { double const new_margin = std::min( margins_mm.left(), margins_mm.right() ); margins_mm.setLeft(new_margin); margins_mm.setRight(new_margin); AggregateSizeChanged const changed = commitHardMargins(margins_mm); recalcBoxesAndFit(margins_mm); emit marginsSetLocally(margins_mm); invalidateThumbnails(changed); } } } void ImageView::topBottomLinkToggled(bool const linked) { m_topBottomLinked = linked; if (linked) { Margins margins_mm(calcHardMarginsMM()); if (margins_mm.top() != margins_mm.bottom()) { double const new_margin = std::min( margins_mm.top(), margins_mm.bottom() ); margins_mm.setTop(new_margin); margins_mm.setBottom(new_margin); AggregateSizeChanged const changed = commitHardMargins(margins_mm); recalcBoxesAndFit(margins_mm); emit marginsSetLocally(margins_mm); invalidateThumbnails(changed); } } } void ImageView::alignmentChanged(Alignment const& alignment) { m_alignment = alignment; Settings::AggregateSizeChanged const size_changed = m_ptrSettings->setPageAlignment(m_pageId, alignment); recalcBoxesAndFit(calcHardMarginsMM()); 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); } void ImageView::onPaint(QPainter& painter, InteractionState const& 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); painter.setPen(Qt::NoPen); painter.setBrush(bg_color); painter.drawPath(outer_outline.subtracted(content_outline)); QPen pen(fg_color); pen.setCosmetic(true); pen.setWidthF(2.0); painter.setPen(pen); painter.setBrush(Qt::NoBrush); painter.drawRect(m_middleRect); painter.drawRect(m_innerRect); if (!m_alignment.isNull()) { pen.setStyle(Qt::DashLine); painter.setPen(pen); painter.drawRect(m_outerRect); } } Proximity ImageView::cornerProximity( int const edge_mask, QRectF const* box, QPointF const& mouse_pos) const { QRectF const 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( int const edge_mask, QRectF const* box, QPointF const& mouse_pos) const { QRectF const 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(QPointF const& 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(); } void ImageView::innerRectDragContinuation(int edge_mask, QPointF const& 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. QPointF const 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; } } 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); 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; QRectF const& 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); m_aggregateHardSizeMM = m_ptrSettings->getAggregateHardSizeMM( m_pageId, origRectToSizeMM(m_middleRect), m_alignment ); recalcOuterRect(); updatePresentationTransform(DONT_FIT); emit marginsSetLocally(calcHardMarginsMM()); } void ImageView::middleRectDragContinuation(int const edge_mask, QPointF const& mouse_pos) { QPointF const delta(mouse_pos - m_beforeResizing.mousePos); qreal left_adjust = 0; qreal right_adjust = 0; qreal top_adjust = 0; qreal bottom_adjust = 0; QRectF const bounds(maxViewportRect()); QRectF const 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; } } 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); 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 ); recalcOuterRect(); updatePresentationTransform(DONT_FIT); emit marginsSetLocally(calcHardMarginsMM()); } void ImageView::dragFinished() { AggregateSizeChanged const agg_size_changed( commitHardMargins(calcHardMarginsMM()) ); QRectF const 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); } /** * Updates m_middleRect and m_outerRect based on \p margins_mm, * m_aggregateHardSizeMM and m_alignment, updates the displayed area. */ void ImageView::recalcBoxesAndFit(Margins const& margins_mm) { QTransform const virt_to_mm(virtualToImage() * m_physXform.pixelsToMM()); QTransform const mm_to_virt(m_physXform.mmToPixels() * imageToVirtual()); QPolygonF poly_mm(virt_to_mm.map(m_innerRect)); Utils::extendPolyRectWithMargins(poly_mm, margins_mm); QRectF const middle_rect(mm_to_virt.map(poly_mm).boundingRect()); QSizeF const hard_size_mm( QLineF(poly_mm[0], poly_mm[1]).length(), QLineF(poly_mm[0], poly_mm[3]).length() ); Margins const soft_margins_mm( Utils::calcSoftMarginsMM( hard_size_mm, m_aggregateHardSizeMM, m_alignment ) ); Utils::extendPolyRectWithMargins(poly_mm, soft_margins_mm); QRectF const outer_rect(mm_to_virt.map(poly_mm).boundingRect()); updateTransformAndFixFocalPoint(ImagePresentation(imageToVirtual(), outer_rect), CENTER_IF_FITS); m_middleRect = middle_rect; m_outerRect = outer_rect; } /** * Updates the virtual image area to be displayed by ImageViewBase, * optionally ensuring that this area completely fits into the view. * * \note virtualToImage() and imageToVirtual() are not affected by this. */ void ImageView::updatePresentationTransform(FitMode const fit_mode) { if (fit_mode == DONT_FIT) { updateTransformPreservingScale(ImagePresentation(imageToVirtual(), m_outerRect)); } else { setZoomLevel(1.0); updateTransformAndFixFocalPoint( ImagePresentation(imageToVirtual(), 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()); } } /** * \brief Calculates margins in millimeters between m_innerRect and m_middleRect. */ Margins ImageView::calcHardMarginsMM() const { QPointF const center(m_innerRect.center()); QLineF const top_margin_line( QPointF(center.x(), m_middleRect.top()), QPointF(center.x(), m_innerRect.top()) ); QLineF const bottom_margin_line( QPointF(center.x(), m_innerRect.bottom()), QPointF(center.x(), m_middleRect.bottom()) ); QLineF const left_margin_line( QPointF(m_middleRect.left(), center.y()), QPointF(m_innerRect.left(), center.y()) ); QLineF const right_margin_line( QPointF(m_innerRect.right(), center.y()), QPointF(m_middleRect.right(), center.y()) ); QTransform const virt_to_mm(virtualToImage() * m_physXform.pixelsToMM()); 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; } /** * \brief Recalculates m_outerRect based on m_middleRect, m_aggregateHardSizeMM * and m_alignment. */ void ImageView::recalcOuterRect() { QTransform const virt_to_mm(virtualToImage() * m_physXform.pixelsToMM()); QTransform const mm_to_virt(m_physXform.mmToPixels() * imageToVirtual()); QPolygonF poly_mm(virt_to_mm.map(m_middleRect)); QSizeF const hard_size_mm( QLineF(poly_mm[0], poly_mm[1]).length(), QLineF(poly_mm[0], poly_mm[3]).length() ); Margins const soft_margins_mm( Utils::calcSoftMarginsMM( hard_size_mm, m_aggregateHardSizeMM, m_alignment ) ); Utils::extendPolyRectWithMargins(poly_mm, soft_margins_mm); m_outerRect = mm_to_virt.map(poly_mm).boundingRect(); } QSizeF ImageView::origRectToSizeMM(QRectF const& rect) const { QTransform const virt_to_mm(virtualToImage() * m_physXform.pixelsToMM()); QLineF const hor_line(rect.topLeft(), rect.topRight()); QLineF const vert_line(rect.topLeft(), rect.bottomLeft()); QSizeF const size_mm( virt_to_mm.map(hor_line).length(), virt_to_mm.map(vert_line).length() ); return size_mm; } ImageView::AggregateSizeChanged ImageView::commitHardMargins(Margins const& margins_mm) { m_ptrSettings->setHardMarginsMM(m_pageId, margins_mm); m_aggregateHardSizeMM = m_ptrSettings->getAggregateHardSizeMM(); AggregateSizeChanged changed = AGGREGATE_SIZE_UNCHANGED; if (m_committedAggregateHardSizeMM != m_aggregateHardSizeMM) { changed = AGGREGATE_SIZE_CHANGED; } m_committedAggregateHardSizeMM = m_aggregateHardSizeMM; return changed; } void ImageView::invalidateThumbnails(AggregateSizeChanged const agg_size_changed) { if (agg_size_changed == AGGREGATE_SIZE_CHANGED) { emit invalidateAllThumbnails(); } else { emit invalidateThumbnail(m_pageId); } } } // namespace page_layout scantailor-RELEASE_0_9_12_2/filters/page_layout/ImageView.h000066400000000000000000000133721271170121200234740ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_LAYOUT_IMAGEVIEW_H_ #define PAGE_LAYOUT_IMAGEVIEW_H_ #include "ImageViewBase.h" #include "ImageTransformation.h" #include "PhysicalTransformation.h" #include "InteractionHandler.h" #include "DragHandler.h" #include "ZoomHandler.h" #include "DraggableObject.h" #include "ObjectDragHandler.h" #include "Alignment.h" #include "IntrusivePtr.h" #include "PageId.h" #include #include #include #include #include class Margins; namespace page_layout { class OptionsWidget; class Settings; class ImageView : public ImageViewBase, private InteractionHandler { Q_OBJECT public: ImageView( IntrusivePtr const& settings, PageId const& page_id, QImage const& image, QImage const& downscaled_image, ImageTransformation const& xform, QRectF const& adapted_content_rect, OptionsWidget const& opt_widget); virtual ~ImageView(); signals: void invalidateThumbnail(PageId const& page_id); void invalidateAllThumbnails(); void marginsSetLocally(Margins const& margins_mm); public slots: void marginsSetExternally(Margins const& margins_mm); void leftRightLinkToggled(bool linked); void topBottomLinkToggled(bool linked); void alignmentChanged(Alignment const& alignment); void aggregateHardSizeChanged(); private: enum Edge { LEFT = 1, RIGHT = 2, TOP = 4, BOTTOM = 8 }; enum FitMode { FIT, DONT_FIT }; enum AggregateSizeChanged { AGGREGATE_SIZE_UNCHANGED, AGGREGATE_SIZE_CHANGED }; struct StateBeforeResizing { /** * Transformation from virtual image coordinates to widget coordinates. */ QTransform virtToWidget; /** * Transformation from widget coordinates to virtual image coordinates. */ QTransform widgetToVirt; /** * m_middleRect in widget coordinates. */ QRectF middleWidgetRect; /** * 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; }; virtual void onPaint(QPainter& painter, InteractionState const& interaction); Proximity cornerProximity(int edge_mask, QRectF const* box, QPointF const& mouse_pos) const; Proximity edgeProximity(int edge_mask, QRectF const* box, QPointF const& mouse_pos) const; void dragInitiated(QPointF const& mouse_pos); void innerRectDragContinuation(int edge_mask, QPointF const& mouse_pos); void middleRectDragContinuation(int edge_mask, QPointF const& mouse_pos); void dragFinished(); void recalcBoxesAndFit(Margins const& margins_mm); void updatePresentationTransform(FitMode fit_mode); void forceNonNegativeHardMargins(QRectF& middle_rect) const; Margins calcHardMarginsMM() const; void recalcOuterRect(); QSizeF origRectToSizeMM(QRectF const& rect) const; AggregateSizeChanged commitHardMargins(Margins const& margins_mm); void invalidateThumbnails(AggregateSizeChanged agg_size_changed); 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; IntrusivePtr m_ptrSettings; PageId const 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. */ PhysicalTransformation const m_physXform; /** * Content box in virtual image coordinates. */ QRectF const 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_ptrSettings->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_ptrSettings->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_topBottomLinked; }; } // namespace page_layout #endif scantailor-RELEASE_0_9_12_2/filters/page_layout/OptionsWidget.cpp000066400000000000000000000241111271170121200247420ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "OptionsWidget.h" #include "OptionsWidget.h.moc" #include "Settings.h" #include "ApplyDialog.h" #include "../../Utils.h" #include "ScopedIncDec.h" #include "PageInfo.h" #include "PageId.h" #include "imageproc/Constants.h" #ifndef Q_MOC_RUN #include #endif #include #include #include #include #include using namespace imageproc::constants; namespace page_layout { OptionsWidget::OptionsWidget( IntrusivePtr const& settings, PageSelectionAccessor const& page_selection_accessor) : m_ptrSettings(settings), m_pageSelectionAccessor(page_selection_accessor), m_mmToUnit(1.0), m_unitToMM(1.0), m_ignoreMarginChanges(0), 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::fromAscii(":/icons/stock-vchain-24.png")) ); m_brokenChainIcon.addPixmap( QPixmap(QString::fromAscii(":/icons/stock-vchain-broken-24.png")) ); setupUi(this); updateLinkDisplay(topBottomLink, m_topBottomLinked); updateLinkDisplay(leftRightLink, m_leftRightLinked); enableDisableAlignmentButtons(); 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) ); connect( unitsComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(unitsChanged(int)) ); 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( 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()) ); typedef AlignmentByButton::value_type KeyVal; BOOST_FOREACH (KeyVal const& kv, m_alignmentByButton) { connect( kv.first, SIGNAL(clicked()), this, SLOT(alignmentButtonClicked()) ); } } OptionsWidget::~OptionsWidget() { } void OptionsWidget::preUpdateUI( PageId const& page_id, Margins const& margins_mm, Alignment const& alignment) { m_pageId = page_id; m_marginsMM = margins_mm; m_alignment = alignment; typedef AlignmentByButton::value_type KeyVal; BOOST_FOREACH (KeyVal const& kv, m_alignmentByButton) { if (kv.second == m_alignment) { kv.first->setChecked(true); } } updateMarginsDisplay(); alignWithOthersCB->blockSignals(true); alignWithOthersCB->setChecked(!alignment.isNull()); alignWithOthersCB->blockSignals(false); enableDisableAlignmentButtons(); 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); } void OptionsWidget::postUpdateUI() { marginsGroup->setEnabled(true); alignmentGroup->setEnabled(true); } void OptionsWidget::marginsSetExternally(Margins const& margins_mm) { m_marginsMM = margins_mm; updateMarginsDisplay(); } void OptionsWidget::unitsChanged(int const idx) { int decimals = 0; double step = 0.0; if (idx == 0) { // mm m_mmToUnit = 1.0; m_unitToMM = 1.0; decimals = 1; step = 1.0; } else { // in m_mmToUnit = MM2INCH; m_unitToMM = INCH2MM; decimals = 2; step = 0.01; } 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(); } void OptionsWidget::horMarginsChanged(double const val) { if (m_ignoreMarginChanges) { return; } if (m_leftRightLinked) { ScopedIncDec const ingore_scope(m_ignoreMarginChanges); leftMarginSpinBox->setValue(val); rightMarginSpinBox->setValue(val); } m_marginsMM.setLeft(leftMarginSpinBox->value() * m_unitToMM); m_marginsMM.setRight(rightMarginSpinBox->value() * m_unitToMM); emit marginsSetLocally(m_marginsMM); } void OptionsWidget::vertMarginsChanged(double const val) { if (m_ignoreMarginChanges) { return; } if (m_topBottomLinked) { ScopedIncDec const ingore_scope(m_ignoreMarginChanges); topMarginSpinBox->setValue(val); bottomMarginSpinBox->setValue(val); } m_marginsMM.setTop(topMarginSpinBox->value() * m_unitToMM); m_marginsMM.setBottom(bottomMarginSpinBox->value() * m_unitToMM); emit marginsSetLocally(m_marginsMM); } void OptionsWidget::topBottomLinkClicked() { 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); } void OptionsWidget::alignWithOthersToggled() { m_alignment.setNull(!alignWithOthersCB->isChecked()); enableDisableAlignmentButtons(); emit alignmentChanged(m_alignment); } void OptionsWidget::alignmentButtonClicked() { QToolButton* const button = dynamic_cast(sender()); assert(button); AlignmentByButton::iterator const it(m_alignmentByButton.find(button)); assert(it != m_alignmentByButton.end()); m_alignment = it->second; emit alignmentChanged(m_alignment); } void OptionsWidget::showApplyMarginsDialog() { ApplyDialog* dialog = new ApplyDialog( this, m_pageId, m_pageSelectionAccessor ); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setWindowTitle(tr("Apply Margins")); connect( dialog, SIGNAL(accepted(std::set const&)), this, SLOT(applyMargins(std::set const&)) ); dialog->show(); } void OptionsWidget::showApplyAlignmentDialog() { ApplyDialog* dialog = new ApplyDialog( this, m_pageId, m_pageSelectionAccessor ); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setWindowTitle(tr("Apply Alignment")); connect( dialog, SIGNAL(accepted(std::set const&)), this, SLOT(applyAlignment(std::set const&)) ); dialog->show(); } void OptionsWidget::applyMargins(std::set const& pages) { if (pages.empty()) { return; } BOOST_FOREACH(PageId const& page_id, pages) { m_ptrSettings->setHardMarginsMM(page_id, m_marginsMM); } emit aggregateHardSizeChanged(); emit invalidateAllThumbnails(); } void OptionsWidget::applyAlignment(std::set const& pages) { if (pages.empty()) { return; } BOOST_FOREACH(PageId const& page_id, pages) { m_ptrSettings->setPageAlignment(page_id, m_alignment); } emit invalidateAllThumbnails(); } void OptionsWidget::updateMarginsDisplay() { ScopedIncDec const ignore_scope(m_ignoreMarginChanges); topMarginSpinBox->setValue(m_marginsMM.top() * m_mmToUnit); bottomMarginSpinBox->setValue(m_marginsMM.bottom() * m_mmToUnit); leftMarginSpinBox->setValue(m_marginsMM.left() * m_mmToUnit); rightMarginSpinBox->setValue(m_marginsMM.right() * m_mmToUnit); } void OptionsWidget::updateLinkDisplay(QToolButton* button, bool const linked) { button->setIcon(linked ? m_chainIcon : m_brokenChainIcon); } void OptionsWidget::enableDisableAlignmentButtons() { bool const enabled = alignWithOthersCB->isChecked(); alignTopLeftBtn->setEnabled(enabled); alignTopBtn->setEnabled(enabled); alignTopRightBtn->setEnabled(enabled); alignLeftBtn->setEnabled(enabled); alignCenterBtn->setEnabled(enabled); alignRightBtn->setEnabled(enabled); alignBottomLeftBtn->setEnabled(enabled); alignBottomBtn->setEnabled(enabled); alignBottomRightBtn->setEnabled(enabled); } } // namespace page_layout scantailor-RELEASE_0_9_12_2/filters/page_layout/OptionsWidget.h000066400000000000000000000061541271170121200244160ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_LAYOUT_OPTIONSWIDGET_H_ #define PAGE_LAYOUT_OPTIONSWIDGET_H_ #include "ui_PageLayoutOptionsWidget.h" #include "FilterOptionsWidget.h" #include "PageSelectionAccessor.h" #include "IntrusivePtr.h" #include "Margins.h" #include "Alignment.h" #include "PageId.h" #include #include #include #include class QToolButton; class ProjectPages; namespace page_layout { class Settings; class OptionsWidget : public FilterOptionsWidget, public Ui::PageLayoutOptionsWidget { Q_OBJECT public: OptionsWidget( IntrusivePtr const& settings, PageSelectionAccessor const& page_selection_accessor); virtual ~OptionsWidget(); void preUpdateUI(PageId const& page_id, Margins const& margins_mm, Alignment const& alignment); void postUpdateUI(); bool leftRightLinked() const { return m_leftRightLinked; } bool topBottomLinked() const { return m_topBottomLinked; } Margins const& marginsMM() const { return m_marginsMM; } Alignment const& alignment() const { return m_alignment; } signals: void leftRightLinkToggled(bool linked); void topBottomLinkToggled(bool linked); void alignmentChanged(Alignment const& alignment); void marginsSetLocally(Margins const& margins_mm); void aggregateHardSizeChanged(); public slots: void marginsSetExternally(Margins const& margins_mm); private slots: void unitsChanged(int idx); void horMarginsChanged(double val); void vertMarginsChanged(double val); void topBottomLinkClicked(); void leftRightLinkClicked(); void alignWithOthersToggled(); void alignmentButtonClicked(); void showApplyMarginsDialog(); void showApplyAlignmentDialog(); void applyMargins(std::set const& pages); void applyAlignment(std::set const& pages); private: typedef std::map AlignmentByButton; void updateMarginsDisplay(); void updateLinkDisplay(QToolButton* button, bool linked); void enableDisableAlignmentButtons(); IntrusivePtr m_ptrSettings; PageSelectionAccessor m_pageSelectionAccessor; QIcon m_chainIcon; QIcon m_brokenChainIcon; AlignmentByButton m_alignmentByButton; double m_mmToUnit; double m_unitToMM; PageId m_pageId; Margins m_marginsMM; Alignment m_alignment; int m_ignoreMarginChanges; bool m_leftRightLinked; bool m_topBottomLinked; }; } // namespace page_layout #endif scantailor-RELEASE_0_9_12_2/filters/page_layout/OrderByHeightProvider.cpp000066400000000000000000000041471271170121200263640ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "OrderByHeightProvider.h" #include "Params.h" #include "Margins.h" #include #include namespace page_layout { OrderByHeightProvider::OrderByHeightProvider(IntrusivePtr const& settings) : m_ptrSettings(settings) { } bool OrderByHeightProvider::precedes( PageId const& lhs_page, bool const lhs_incomplete, PageId const& rhs_page, bool const rhs_incomplete) const { std::auto_ptr const lhs_params(m_ptrSettings->getPageParams(lhs_page)); std::auto_ptr const rhs_params(m_ptrSettings->getPageParams(rhs_page)); QSizeF lhs_size; if (lhs_params.get()) { Margins const 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.get()) { Margins const margins(rhs_params->hardMarginsMM()); rhs_size = rhs_params->contentSizeMM(); rhs_size += QSizeF( margins.left() + margins.right(), margins.top() + margins.bottom() ); } bool const lhs_valid = !lhs_incomplete && lhs_size.isValid(); bool const 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 page_layout scantailor-RELEASE_0_9_12_2/filters/page_layout/OrderByHeightProvider.h000066400000000000000000000024771271170121200260350ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_LAYOUT_ORDER_BY_HEIGHT_PROVIDER_H_ #define PAGE_LAYOUT_ORDER_BY_HEIGHT_PROVIDER_H_ #include "Settings.h" #include "IntrusivePtr.h" #include "PageOrderProvider.h" namespace page_layout { class OrderByHeightProvider : public PageOrderProvider { public: OrderByHeightProvider(IntrusivePtr const& settings); virtual bool precedes( PageId const& lhs_page, bool lhs_incomplete, PageId const& rhs_page, bool rhs_incomplete) const; private: IntrusivePtr m_ptrSettings; }; } // namespace page_layout #endif scantailor-RELEASE_0_9_12_2/filters/page_layout/OrderByWidthProvider.cpp000066400000000000000000000041411271170121200262250ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "OrderByWidthProvider.h" #include "Params.h" #include "Margins.h" #include #include namespace page_layout { OrderByWidthProvider::OrderByWidthProvider(IntrusivePtr const& settings) : m_ptrSettings(settings) { } bool OrderByWidthProvider::precedes( PageId const& lhs_page, bool const lhs_incomplete, PageId const& rhs_page, bool const rhs_incomplete) const { std::auto_ptr const lhs_params(m_ptrSettings->getPageParams(lhs_page)); std::auto_ptr const rhs_params(m_ptrSettings->getPageParams(rhs_page)); QSizeF lhs_size; if (lhs_params.get()) { Margins const 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.get()) { Margins const margins(rhs_params->hardMarginsMM()); rhs_size = rhs_params->contentSizeMM(); rhs_size += QSizeF( margins.left() + margins.right(), margins.top() + margins.bottom() ); } bool const lhs_valid = !lhs_incomplete && lhs_size.isValid(); bool const 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 page_layout scantailor-RELEASE_0_9_12_2/filters/page_layout/OrderByWidthProvider.h000066400000000000000000000024731271170121200257000ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_LAYOUT_ORDER_BY_WIDTH_PROVIDER_H_ #define PAGE_LAYOUT_ORDER_BY_WIDTH_PROVIDER_H_ #include "Settings.h" #include "IntrusivePtr.h" #include "PageOrderProvider.h" namespace page_layout { class OrderByWidthProvider : public PageOrderProvider { public: OrderByWidthProvider(IntrusivePtr const& settings); virtual bool precedes( PageId const& lhs_page, bool lhs_incomplete, PageId const& rhs_page, bool rhs_incomplete) const; private: IntrusivePtr m_ptrSettings; }; } // namespace page_layout #endif scantailor-RELEASE_0_9_12_2/filters/page_layout/Params.cpp000066400000000000000000000034751271170121200234000ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Params.h" #include "XmlMarshaller.h" #include "XmlUnmarshaller.h" #include #include #include namespace page_layout { Params::Params( Margins const& hard_margins_mm, QSizeF const& content_size_mm, Alignment const& alignment) : m_hardMarginsMM(hard_margins_mm), m_contentSizeMM(content_size_mm), m_alignment(alignment) { } Params::Params(QDomElement const& el) : m_hardMarginsMM( XmlUnmarshaller::margins( el.namedItem("hardMarginsMM").toElement() ) ), m_contentSizeMM( XmlUnmarshaller::sizeF( el.namedItem("contentSizeMM").toElement() ) ), m_alignment(el.namedItem("alignment").toElement()) { } QDomElement Params::toXml(QDomDocument& doc, QString const& name) const { XmlMarshaller marshaller(doc); QDomElement el(doc.createElement(name)); el.appendChild(marshaller.margins(m_hardMarginsMM, "hardMarginsMM")); el.appendChild(marshaller.sizeF(m_contentSizeMM, "contentSizeMM")); el.appendChild(m_alignment.toXml(doc, "alignment")); return el; } } // namespace page_layout scantailor-RELEASE_0_9_12_2/filters/page_layout/Params.h000066400000000000000000000030531271170121200230350ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_LAYOUT_PARAMS_H_ #define PAGE_LAYOUT_PARAMS_H_ #include "Margins.h" #include "Alignment.h" #include class QDomDocument; class QDomElement; class QString; namespace page_layout { class Params { // Member-wise copying is OK. public: Params(Margins const& hard_margins_mm, QSizeF const& content_size_mm, Alignment const& alignment); Params(QDomElement const& el); Margins const& hardMarginsMM() const { return m_hardMarginsMM; } QSizeF const& contentSizeMM() const { return m_contentSizeMM; } Alignment const& alignment() const { return m_alignment; } QDomElement toXml(QDomDocument& doc, QString const& name) const; private: Margins m_hardMarginsMM; QSizeF m_contentSizeMM; Alignment m_alignment; }; } // namespace page_layout #endif scantailor-RELEASE_0_9_12_2/filters/page_layout/Settings.cpp000066400000000000000000000400501271170121200237430ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Settings.h" #include "PageId.h" #include "PageSequence.h" #include "Params.h" #include "Margins.h" #include "Alignment.h" #include "RelinkablePath.h" #include "AbstractRelinker.h" #include #include #include #ifndef Q_MOC_RUN #include #include #include #include #include #include #include #endif #include #include // for std::greater<> #include #include using namespace ::boost; using namespace ::boost::multi_index; namespace page_layout { class Settings::Item { public: PageId pageId; Margins hardMarginsMM; QSizeF contentSizeMM; Alignment alignment; Item(PageId const& page_id, Margins const& hard_margins_mm, QSizeF const& content_size_mm, Alignment const& alignment); double hardWidthMM() const; double hardHeightMM() const; double influenceHardWidthMM() const; double influenceHardHeightMM() const; bool alignedWithOthers() const { return !alignment.isNull(); } }; class Settings::ModifyMargins { public: ModifyMargins(Margins const& margins_mm) : m_marginsMM(margins_mm) {} void operator()(Item& item) { item.hardMarginsMM = m_marginsMM; } private: Margins m_marginsMM; }; class Settings::ModifyAlignment { public: ModifyAlignment(Alignment const& alignment) : m_alignment(alignment) {} void operator()(Item& item) { item.alignment = m_alignment; } private: Alignment m_alignment; }; class Settings::ModifyContentSize { public: ModifyContentSize(QSizeF const& content_size_mm) : m_contentSizeMM(content_size_mm) {} void operator()(Item& item) { item.contentSizeMM = m_contentSizeMM; } private: QSizeF m_contentSizeMM; }; class Settings::Impl { public: Impl(); ~Impl(); void clear(); void performRelinking(AbstractRelinker const& relinker); void removePagesMissingFrom(PageSequence const& pages); bool checkEverythingDefined( PageSequence const& pages, PageId const* ignore) const; std::auto_ptr getPageParams(PageId const& page_id) const; void setPageParams(PageId const& page_id, Params const& params); Params updateContentSizeAndGetParams( PageId const& page_id, QSizeF const& content_size_mm, QSizeF* agg_hard_size_before, QSizeF* agg_hard_size_after); Margins getHardMarginsMM(PageId const& page_id) const; void setHardMarginsMM(PageId const& page_id, Margins const& margins_mm); Alignment getPageAlignment(PageId const& page_id) const; AggregateSizeChanged setPageAlignment( PageId const& page_id, Alignment const& alignment); AggregateSizeChanged setContentSizeMM( PageId const& page_id, QSizeF const& content_size_mm); void invalidateContentSize(PageId const& page_id); QSizeF getAggregateHardSizeMM() const; QSizeF getAggregateHardSizeMMLocked() const; QSizeF getAggregateHardSizeMM( PageId const& page_id, QSizeF const& hard_size_mm, Alignment const& alignment) const; private: class SequencedTag; class DescWidthTag; class DescHeightTag; typedef multi_index_container< Item, indexed_by< ordered_unique >, sequenced >, ordered_non_unique< tag, // ORDER BY alignedWithOthers DESC, hardWidthMM DESC composite_key< Item, const_mem_fun, const_mem_fun >, composite_key_compare< std::greater, std::greater > >, ordered_non_unique< tag, // ORDER BY alignedWithOthers DESC, hardHeightMM DESC composite_key< Item, const_mem_fun, const_mem_fun >, composite_key_compare< std::greater, std::greater > > > > Container; 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; QSizeF const m_invalidSize; Margins const m_defaultHardMarginsMM; Alignment const m_defaultAlignment; }; /*=============================== Settings ==================================*/ Settings::Settings() : m_ptrImpl(new Impl()) { } Settings::~Settings() { } void Settings::clear() { return m_ptrImpl->clear(); } void Settings::performRelinking(AbstractRelinker const& relinker) { m_ptrImpl->performRelinking(relinker); } void Settings::removePagesMissingFrom(PageSequence const& pages) { m_ptrImpl->removePagesMissingFrom(pages); } bool Settings::checkEverythingDefined( PageSequence const& pages, PageId const* ignore) const { return m_ptrImpl->checkEverythingDefined(pages, ignore); } std::auto_ptr Settings::getPageParams(PageId const& page_id) const { return m_ptrImpl->getPageParams(page_id); } void Settings::setPageParams(PageId const& page_id, Params const& params) { return m_ptrImpl->setPageParams(page_id, params); } Params Settings::updateContentSizeAndGetParams( PageId const& page_id, QSizeF const& content_size_mm, QSizeF* agg_hard_size_before, QSizeF* agg_hard_size_after) { return m_ptrImpl->updateContentSizeAndGetParams( page_id, content_size_mm, agg_hard_size_before, agg_hard_size_after ); } Margins Settings::getHardMarginsMM(PageId const& page_id) const { return m_ptrImpl->getHardMarginsMM(page_id); } void Settings::setHardMarginsMM(PageId const& page_id, Margins const& margins_mm) { m_ptrImpl->setHardMarginsMM(page_id, margins_mm); } Alignment Settings::getPageAlignment(PageId const& page_id) const { return m_ptrImpl->getPageAlignment(page_id); } Settings::AggregateSizeChanged Settings::setPageAlignment(PageId const& page_id, Alignment const& alignment) { return m_ptrImpl->setPageAlignment(page_id, alignment); } Settings::AggregateSizeChanged Settings::setContentSizeMM( PageId const& page_id, QSizeF const& content_size_mm) { return m_ptrImpl->setContentSizeMM(page_id, content_size_mm); } void Settings::invalidateContentSize(PageId const& page_id) { return m_ptrImpl->invalidateContentSize(page_id); } QSizeF Settings::getAggregateHardSizeMM() const { return m_ptrImpl->getAggregateHardSizeMM(); } QSizeF Settings::getAggregateHardSizeMM( PageId const& page_id, QSizeF const& hard_size_mm, Alignment const& alignment) const { return m_ptrImpl->getAggregateHardSizeMM(page_id, hard_size_mm, alignment); } /*============================== Settings::Item =============================*/ Settings::Item::Item( PageId const& page_id, Margins const& hard_margins_mm, QSizeF const& content_size_mm, Alignment const& align) : pageId(page_id), hardMarginsMM(hard_margins_mm), contentSizeMM(content_size_mm), alignment(align) { } double Settings::Item::hardWidthMM() const { return contentSizeMM.width() + hardMarginsMM.left() + hardMarginsMM.right(); } double Settings::Item::hardHeightMM() const { return contentSizeMM.height() + hardMarginsMM.top() + hardMarginsMM.bottom(); } double Settings::Item::influenceHardWidthMM() const { return alignment.isNull() ? 0.0 : hardWidthMM(); } double Settings::Item::influenceHardHeightMM() const { 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_invalidSize(), m_defaultHardMarginsMM(page_layout::Settings::defaultHardMarginsMM()), m_defaultAlignment(Alignment::TOP, Alignment::HCENTER) { } Settings::Impl::~Impl() { } void Settings::Impl::clear() { QMutexLocker const locker(&m_mutex); m_items.clear(); } void Settings::Impl::performRelinking(AbstractRelinker const& relinker) { QMutexLocker locker(&m_mutex); Container new_items; BOOST_FOREACH(Item const& item, m_unorderedItems) { RelinkablePath const 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_items.swap(new_items); } void Settings::Impl::removePagesMissingFrom(PageSequence const& pages) { QMutexLocker const locker(&m_mutex); std::vector sorted_pages; size_t const num_pages = pages.numPages(); sorted_pages.reserve(num_pages); for (size_t i = 0; i < num_pages; ++i) { sorted_pages.push_back(pages.pageAt(i).id()); } std::sort(sorted_pages.begin(), sorted_pages.end()); UnorderedItems::const_iterator it(m_unorderedItems.begin()); UnorderedItems::const_iterator const end(m_unorderedItems.end()); while (it != end) { if (std::binary_search(sorted_pages.begin(), sorted_pages.end(), it->pageId)) { ++it; } else { m_unorderedItems.erase(it++); } } } bool Settings::Impl::checkEverythingDefined( PageSequence const& pages, PageId const* ignore) const { QMutexLocker const locker(&m_mutex); size_t const num_pages = pages.numPages(); for (size_t i = 0; i < num_pages; ++i) { PageInfo const& page_info = pages.pageAt(i); if (ignore && *ignore == page_info.id()) { continue; } Container::iterator const it(m_items.find(page_info.id())); if (it == m_items.end() || !it->contentSizeMM.isValid()) { return false; } } return true; } std::auto_ptr Settings::Impl::getPageParams(PageId const& page_id) const { QMutexLocker const locker(&m_mutex); Container::iterator const it(m_items.find(page_id)); if (it == m_items.end()) { return std::auto_ptr(); } return std::auto_ptr( new Params(it->hardMarginsMM, it->contentSizeMM, it->alignment) ); } void Settings::Impl::setPageParams(PageId const& page_id, Params const& params) { QMutexLocker const locker(&m_mutex); Item const new_item( page_id, params.hardMarginsMM(), params.contentSizeMM(), params.alignment() ); Container::iterator const 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); } } Params Settings::Impl::updateContentSizeAndGetParams( PageId const& page_id, QSizeF const& content_size_mm, QSizeF* agg_hard_size_before, QSizeF* agg_hard_size_after) { QMutexLocker const locker(&m_mutex); if (agg_hard_size_before) { *agg_hard_size_before = getAggregateHardSizeMMLocked(); } Container::iterator const it(m_items.lower_bound(page_id)); Container::iterator item_it(it); if (it == m_items.end() || page_id < it->pageId) { Item const item( page_id, m_defaultHardMarginsMM, content_size_mm, m_defaultAlignment ); item_it = m_items.insert(it, item); } else { m_items.modify(it, ModifyContentSize(content_size_mm)); } if (agg_hard_size_after) { *agg_hard_size_after = getAggregateHardSizeMMLocked(); } return Params( item_it->hardMarginsMM, item_it->contentSizeMM, item_it->alignment ); } Margins Settings::Impl::getHardMarginsMM(PageId const& page_id) const { QMutexLocker const locker(&m_mutex); Container::iterator const it(m_items.find(page_id)); if (it == m_items.end()) { return m_defaultHardMarginsMM; } else { return it->hardMarginsMM; } } void Settings::Impl::setHardMarginsMM( PageId const& page_id, Margins const& margins_mm) { QMutexLocker const locker(&m_mutex); Container::iterator const it(m_items.lower_bound(page_id)); if (it == m_items.end() || page_id < it->pageId) { Item const item( page_id, margins_mm, m_invalidSize, m_defaultAlignment ); m_items.insert(it, item); } else { m_items.modify(it, ModifyMargins(margins_mm)); } } Alignment Settings::Impl::getPageAlignment(PageId const& page_id) const { QMutexLocker const locker(&m_mutex); Container::iterator const it(m_items.find(page_id)); if (it == m_items.end()) { return m_defaultAlignment; } else { return it->alignment; } } Settings::AggregateSizeChanged Settings::Impl::setPageAlignment( PageId const& page_id, Alignment const& alignment) { QMutexLocker const locker(&m_mutex); QSizeF const agg_size_before(getAggregateHardSizeMMLocked()); Container::iterator const it(m_items.lower_bound(page_id)); if (it == m_items.end() || page_id < it->pageId) { Item const item( page_id, m_defaultHardMarginsMM, m_invalidSize, alignment ); m_items.insert(it, item); } else { m_items.modify(it, ModifyAlignment(alignment)); } QSizeF const 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( PageId const& page_id, QSizeF const& content_size_mm) { QMutexLocker const locker(&m_mutex); QSizeF const agg_size_before(getAggregateHardSizeMMLocked()); Container::iterator const it(m_items.lower_bound(page_id)); if (it == m_items.end() || page_id < it->pageId) { Item const item( page_id, m_defaultHardMarginsMM, content_size_mm, m_defaultAlignment ); m_items.insert(it, item); } else { m_items.modify(it, ModifyContentSize(content_size_mm)); } QSizeF const agg_size_after(getAggregateHardSizeMMLocked()); if (agg_size_before == agg_size_after) { return AGGREGATE_SIZE_UNCHANGED; } else { return AGGREGATE_SIZE_CHANGED; } } void Settings::Impl::invalidateContentSize(PageId const& page_id) { QMutexLocker const locker(&m_mutex); Container::iterator const it(m_items.find(page_id)); if (it != m_items.end()) { m_items.modify(it, ModifyContentSize(m_invalidSize)); } } QSizeF Settings::Impl::getAggregateHardSizeMM() const { QMutexLocker const locker(&m_mutex); return getAggregateHardSizeMMLocked(); } QSizeF Settings::Impl::getAggregateHardSizeMMLocked() const { if (m_items.empty()) { return QSizeF(0.0, 0.0); } Item const& max_width_item = *m_descWidthOrder.begin(); Item const& max_height_item = *m_descHeightOrder.begin(); double const width = max_width_item.influenceHardWidthMM(); double const height = max_height_item.influenceHardHeightMM(); return QSizeF(width, height); } QSizeF Settings::Impl::getAggregateHardSizeMM( PageId const& page_id, QSizeF const& hard_size_mm, Alignment const& alignment) const { if (alignment.isNull()) { return getAggregateHardSizeMM(); } QMutexLocker const locker(&m_mutex); if (m_items.empty()) { return QSizeF(0.0, 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()) ); } } } 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); } } // namespace page_layout scantailor-RELEASE_0_9_12_2/filters/page_layout/Settings.h000066400000000000000000000114071271170121200234140ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_LAYOUT_SETTINGS_H_ #define PAGE_LAYOUT_SETTINGS_H_ #include "NonCopyable.h" #include "RefCountable.h" #include "Margins.h" #include class PageId; class Margins; class PageSequence; class AbstractRelinker; class QSizeF; namespace page_layout { class Params; class Alignment; class Settings : public RefCountable { DECLARE_NON_COPYABLE(Settings) public: enum AggregateSizeChanged { AGGREGATE_SIZE_UNCHANGED, AGGREGATE_SIZE_CHANGED }; static Margins defaultHardMarginsMM() { return Margins(10.0, 5.0, 10.0, 5.0); } Settings(); virtual ~Settings(); /** * \brief Removes all stored data. */ void clear(); void performRelinking(AbstractRelinker const& relinker); /** * \brief Removes all stored data for pages that are not in the provided list. */ void removePagesMissingFrom(PageSequence const& 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( PageSequence const& pages, PageId const* ignore = 0) const; /** * \brief Get all page parameters at once. * * May return a null auto_ptr if the specified page is unknown to us. */ std::auto_ptr getPageParams(PageId const& page_id) const; /** * \brief Set all page parameters at once. */ void setPageParams(PageId const& page_id, Params const& params); /** * \brief Updates content size and returns all parameters at once. */ Params updateContentSizeAndGetParams( PageId const& page_id, QSizeF const& content_size_mm, QSizeF* agg_hard_size_before = 0, QSizeF* agg_hard_size_after = 0); /** * \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(PageId const& 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(PageId const& page_id, Margins const& 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(PageId const& 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(PageId const& page_id, Alignment const& alignment); /** * \brief Sets content size in millimeters for the specified page. * * The content size comes from the "Select Content" filter. */ AggregateSizeChanged setContentSizeMM( PageId const& page_id, QSizeF const& content_size_mm); void invalidateContentSize(PageId const& 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( PageId const& page_id, QSizeF const& hard_size_mm, Alignment const& alignment) const; private: class Impl; class Item; class ModifyMargins; class ModifyAlignment; class ModifyContentSize; std::auto_ptr m_ptrImpl; }; } // namespace page_layout #endif scantailor-RELEASE_0_9_12_2/filters/page_layout/Task.cpp000066400000000000000000000134271271170121200230550ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Task.h" #include "Filter.h" #include "OptionsWidget.h" #include "Settings.h" #include "Margins.h" #include "Params.h" #include "Utils.h" #include "FilterUiInterface.h" #include "TaskStatus.h" #include "FilterData.h" #include "ImageView.h" #include "ImageTransformation.h" #include "PhysicalTransformation.h" #include "filters/output/Task.h" #include #include #include #include #include #include #include "CommandLine.h" namespace page_layout { class Task::UiUpdater : public FilterResult { public: UiUpdater(IntrusivePtr const& filter, IntrusivePtr const& settings, PageId const& page_id, QImage const& image, ImageTransformation const& xform, QRectF const& adapted_content_rect, bool agg_size_changed, bool batch); virtual void updateUI(FilterUiInterface* ui); virtual IntrusivePtr filter() { return m_ptrFilter; } private: IntrusivePtr m_ptrFilter; IntrusivePtr m_ptrSettings; PageId m_pageId; QImage m_image; QImage m_downscaledImage; ImageTransformation m_xform; QRectF m_adaptedContentRect; bool m_aggSizeChanged; bool m_batchProcessing; }; Task::Task(IntrusivePtr const& filter, IntrusivePtr const& next_task, IntrusivePtr const& settings, PageId const& page_id, bool batch, bool debug) : m_ptrFilter(filter), m_ptrNextTask(next_task), m_ptrSettings(settings), m_pageId(page_id), m_batchProcessing(batch) { } Task::~Task() { } FilterResultPtr Task::process( TaskStatus const& status, FilterData const& data, QRectF const& content_rect) { status.throwIfCancelled(); QSizeF const content_size_mm( Utils::calcRectSizeMM(data.xform(), content_rect) ); QSizeF agg_hard_size_before; QSizeF agg_hard_size_after; Params const params( m_ptrSettings->updateContentSizeAndGetParams( m_pageId, content_size_mm, &agg_hard_size_before, &agg_hard_size_after ) ); QRectF const adapted_content_rect( Utils::adaptContentRect(data.xform(), content_rect) ); if (m_ptrNextTask) { QPolygonF const content_rect_phys( data.xform().transformBack().map(adapted_content_rect) ); QPolygonF const page_rect_phys( Utils::calcPageRectPhys( data.xform(), content_rect_phys, params, agg_hard_size_after ) ); ImageTransformation new_xform(data.xform()); new_xform.setPostCropArea(new_xform.transform().map(page_rect_phys)); return m_ptrNextTask->process( status, FilterData(data, new_xform), content_rect_phys ); } else if (m_ptrFilter->optionsWidget() != 0) { return FilterResultPtr( new UiUpdater( m_ptrFilter, m_ptrSettings, m_pageId, data.origImage(), data.xform(), adapted_content_rect, agg_hard_size_before != agg_hard_size_after, m_batchProcessing ) ); } else { return FilterResultPtr(0); } } /*============================ Task::UiUpdater ==========================*/ Task::UiUpdater::UiUpdater( IntrusivePtr const& filter, IntrusivePtr const& settings, PageId const& page_id, QImage const& image, ImageTransformation const& xform, QRectF const& adapted_content_rect, bool const agg_size_changed, bool const batch) : m_ptrFilter(filter), m_ptrSettings(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) { } 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; } ImageView* 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(PageId const&)), opt_widget, SIGNAL(invalidateThumbnail(PageId const&)) ); QObject::connect( view, SIGNAL(invalidateAllThumbnails()), opt_widget, SIGNAL(invalidateAllThumbnails()) ); QObject::connect( view, SIGNAL(marginsSetLocally(Margins const&)), opt_widget, SLOT(marginsSetExternally(Margins const&)) ); QObject::connect( opt_widget, SIGNAL(marginsSetLocally(Margins const&)), view, SLOT(marginsSetExternally(Margins const&)) ); 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(Alignment const&)), view, SLOT(alignmentChanged(Alignment const&)) ); QObject::connect( opt_widget, SIGNAL(aggregateHardSizeChanged()), view, SLOT(aggregateHardSizeChanged()) ); } } // namespace page_layout scantailor-RELEASE_0_9_12_2/filters/page_layout/Task.h000066400000000000000000000033041271170121200225130ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_LAYOUT_TASK_H_ #define PAGE_LAYOUT_TASK_H_ #include "NonCopyable.h" #include "RefCountable.h" #include "FilterResult.h" #include "PageId.h" class TaskStatus; class FilterData; class ImageTransformation; class QRectF; namespace output { class Task; } namespace page_layout { class Filter; class Settings; class Task : public RefCountable { DECLARE_NON_COPYABLE(Task) public: Task(IntrusivePtr const& filter, IntrusivePtr const& next_task, IntrusivePtr const& settings, PageId const& page_id, bool batch, bool debug); virtual ~Task(); FilterResultPtr process( TaskStatus const& status, FilterData const& data, QRectF const& content_rect); private: class UiUpdater; IntrusivePtr m_ptrFilter; IntrusivePtr m_ptrNextTask; IntrusivePtr m_ptrSettings; PageId m_pageId; bool m_batchProcessing; }; } // namespace page_layout #endif scantailor-RELEASE_0_9_12_2/filters/page_layout/Thumbnail.cpp000066400000000000000000000065041271170121200240740ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Thumbnail.h" #include "Utils.h" #include "imageproc/PolygonUtils.h" #include #include #include #include #include #include using namespace imageproc; namespace page_layout { Thumbnail::Thumbnail( IntrusivePtr const& thumbnail_cache, QSizeF const& max_size, ImageId const& image_id, Params const& params, ImageTransformation const& xform, QPolygonF const& phys_content_rect) : ThumbnailBase(thumbnail_cache, max_size, image_id, xform), m_params(params), m_virtContentRect(xform.transform().map(phys_content_rect).boundingRect()), m_virtOuterRect(xform.resultingPostCropArea().boundingRect()) { setExtendedClipArea(true); } void Thumbnail::paintOverImage( QPainter& painter, QTransform const& image_to_display, QTransform const& thumb_to_display) { // We work in display coordinates because we want to be // pixel-accurate with what we draw. painter.setWorldTransform(QTransform()); QTransform const virt_to_display(virtToThumb() * thumb_to_display); QRectF const 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. QRectF const outer_rect( virt_to_display.map(m_virtOuterRect).boundingRect().adjusted(-1.0, -1.0, 1.0, 1.0) ); QPainterPath outer_outline; outer_outline.addPolygon(PolygonUtils::round(outer_rect)); QPainterPath content_outline; content_outline.addPolygon(PolygonUtils::round(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); } // Draw margins. painter.fillPath(outer_outline.subtracted(content_outline), bg_color); QPen pen(fg_color); pen.setCosmetic(true); pen.setWidthF(1.0); painter.setPen(pen); painter.setBrush(Qt::NoBrush); // toRect() is necessary because we turn off antialiasing. // For some reason, if we let Qt round the coordinates, // the result is slightly different. painter.drawRect(inner_rect.toRect()); } } // namespace page_layout scantailor-RELEASE_0_9_12_2/filters/page_layout/Thumbnail.h000066400000000000000000000030731271170121200235370ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_LAYOUT_THUMBNAIL_H_ #define PAGE_LAYOUT_THUMBNAIL_H_ #include "ThumbnailBase.h" #include "Params.h" #include "ImageTransformation.h" #include "IntrusivePtr.h" #include #include class ThumbnailPixmapCache; class ImageId; namespace page_layout { class Thumbnail : public ThumbnailBase { public: Thumbnail(IntrusivePtr const& thumbnail_cache, QSizeF const& max_size, ImageId const& image_id, Params const& params, ImageTransformation const& xform, QPolygonF const& phys_content_rect); virtual void paintOverImage( QPainter& painter, QTransform const& image_to_display, QTransform const& thumb_to_display); private: Params m_params; QRectF m_virtContentRect; QRectF m_virtOuterRect; }; } // namespace page_layout #endif scantailor-RELEASE_0_9_12_2/filters/page_layout/Utils.cpp000066400000000000000000000114071271170121200232470ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Utils.h" #include "Margins.h" #include "Alignment.h" #include "Params.h" #include "ImageTransformation.h" #include "PhysicalTransformation.h" #include #include #include #include #include #include namespace page_layout { QRectF Utils::adaptContentRect( ImageTransformation const& xform, QRectF const& content_rect) { if (!content_rect.isEmpty()) { return content_rect; } QPointF const center(xform.resultingRect().center()); QPointF const delta(0.01, 0.01); return QRectF(center - delta, center + delta); } QSizeF Utils::calcRectSizeMM( ImageTransformation const& xform, QRectF const& rect) { PhysicalTransformation const phys_xform(xform.origDpi()); QTransform const virt_to_mm(xform.transformBack() * phys_xform.pixelsToMM()); QLineF const hor_line(rect.topLeft(), rect.topRight()); QLineF const ver_line(rect.topLeft(), rect.bottomLeft()); double const width = virt_to_mm.map(hor_line).length(); double const height = virt_to_mm.map(ver_line).length(); return QSizeF(width, height); } void Utils::extendPolyRectWithMargins( QPolygonF& poly_rect, Margins const& margins) { QPointF const down_uv(getDownUnitVector(poly_rect)); QPointF const 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::calcSoftMarginsMM( QSizeF const& hard_size_mm, QSizeF const& aggregate_hard_size_mm, Alignment const& alignment) { 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; double const delta_width = aggregate_hard_size_mm.width() - hard_size_mm.width(); if (delta_width > 0.0) { switch (alignment.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; } } double const delta_height = aggregate_hard_size_mm.height() - hard_size_mm.height(); if (delta_height > 0.0) { switch (alignment.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; } } return Margins(left, top, right, bottom); } QPolygonF Utils::calcPageRectPhys( ImageTransformation const& xform, QPolygonF const& content_rect_phys, Params const& params, QSizeF const& aggregate_hard_size_mm) { PhysicalTransformation const phys_xform(xform.origDpi()); QPolygonF poly_mm(phys_xform.pixelsToMM().map(content_rect_phys)); extendPolyRectWithMargins(poly_mm, params.hardMarginsMM()); QSizeF const hard_size_mm( QLineF(poly_mm[0], poly_mm[1]).length(), QLineF(poly_mm[0], poly_mm[3]).length() ); Margins const soft_margins_mm( calcSoftMarginsMM( hard_size_mm, aggregate_hard_size_mm, params.alignment() ) ); extendPolyRectWithMargins(poly_mm, soft_margins_mm); return phys_xform.mmToPixels().map(poly_mm); } QPointF Utils::getRightUnitVector(QPolygonF const& poly_rect) { QPointF const top_left(poly_rect[0]); QPointF const top_right(poly_rect[1]); return QLineF(top_left, top_right).unitVector().p2() - top_left; } QPointF Utils::getDownUnitVector(QPolygonF const& poly_rect) { QPointF const top_left(poly_rect[0]); QPointF const bottom_left(poly_rect[3]); return QLineF(top_left, bottom_left).unitVector().p2() - top_left; } } // namespace page_layout scantailor-RELEASE_0_9_12_2/filters/page_layout/Utils.h000066400000000000000000000064231271170121200227160ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_LAYOUT_UTILS_H_ #define PAGE_LAYOUT_UTILS_H_ class QPolygonF; class QPointF; class QSizeF; class QRectF; class Margins; class ImageTransformation; namespace page_layout { 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( ImageTransformation const& xform, QRectF const& content_rect); /** * \brief Calculates the physical size of a rectangle in a transformed space. */ static QSizeF calcRectSizeMM( ImageTransformation const& xform, QRectF const& 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, Margins const& 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( QSizeF const& hard_size_mm, QSizeF const& aggregate_hard_size_mm, Alignment const& alignment); /** * \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( ImageTransformation const& xform, QPolygonF const& content_rect_phys, Params const& params, QSizeF const& aggregate_hard_size_mm); private: static QPointF getRightUnitVector(QPolygonF const& poly_rect); static QPointF getDownUnitVector(QPolygonF const& poly_rect); }; } // namespace page_layout #endif scantailor-RELEASE_0_9_12_2/filters/page_layout/ui/000077500000000000000000000000001271170121200220555ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/filters/page_layout/ui/PageLayoutApplyDialog.ui000066400000000000000000000163211271170121200266170ustar00rootroot00000000000000 PageLayoutApplyDialog Qt::WindowModal 0 0 363 316 Apply to This page only (already applied) true All pages This page and the following ones 0 0 Every other page Qt::Horizontal QSizePolicy::Fixed 30 0 7 The current page will be included. 0 0 Selected pages 6 Qt::Horizontal QSizePolicy::Fixed 30 0 7 Use Ctrl+Click / Shift+Click to select multiple pages. 0 0 Every other selected page Qt::Horizontal QSizePolicy::Fixed 30 0 7 The current page will be included. Qt::Vertical 20 0 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox rejected() PageLayoutApplyDialog reject() 316 260 286 274 scantailor-RELEASE_0_9_12_2/filters/page_layout/ui/PageLayoutOptionsWidget.ui000066400000000000000000000500201271170121200272030ustar00rootroot00000000000000 PageLayoutOptionsWidget 0 0 262 499 Form Margins Qt::Horizontal 1 1 Millimeters (mm) Inches (in) Qt::Horizontal 1 1 Qt::Horizontal 1 1 Top 24 48 ... :/icons/stock-vchain-broken-24.png :/icons/stock-vchain-24.png:/icons/stock-vchain-broken-24.png 9 24 true Bottom 1 999.000000000000000 Left 1 999.000000000000000 24 48 ... :/icons/stock-vchain-broken-24.png :/icons/stock-vchain-24.png:/icons/stock-vchain-broken-24.png 9 24 false true Right 1 999.000000000000000 1 999.000000000000000 Qt::Horizontal 1 1 Qt::Horizontal 1 1 Apply To ... Qt::Horizontal 1 1 Alignment 0 Qt::Horizontal 0 0 Match size with other pages true Qt::Horizontal 0 0 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 Qt::Horizontal 1 1 Apply To ... Qt::Horizontal 1 1 Qt::Vertical 1 1 unitsComboBox topBottomLink topMarginSpinBox bottomMarginSpinBox leftRightLink leftMarginSpinBox rightMarginSpinBox applyMarginsBtn alignWithOthersCB alignTopLeftBtn alignTopBtn alignTopRightBtn alignLeftBtn alignCenterBtn alignRightBtn alignBottomLeftBtn alignBottomBtn alignBottomRightBtn applyAlignmentBtn scantailor-RELEASE_0_9_12_2/filters/page_split/000077500000000000000000000000001271170121200212565ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/filters/page_split/CMakeLists.txt000066400000000000000000000020261271170121200240160ustar00rootroot00000000000000PROJECT("Page Split Filter") INCLUDE_DIRECTORIES(BEFORE "${CMAKE_CURRENT_BINARY_DIR}") FILE(GLOB ui_files "ui/*.ui") QT4_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 ) SOURCE_GROUP("Sources" FILES ${sources}) QT4_AUTOMOC(${sources}) ADD_LIBRARY(page_split STATIC ${sources} ${ui_sources}) TRANSLATION_SOURCES(scantailor ${sources} ${ui_files})scantailor-RELEASE_0_9_12_2/filters/page_split/CacheDrivenTask.cpp000066400000000000000000000056251271170121200247700ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "CacheDrivenTask.h" #include "Thumbnail.h" #include "IncompleteThumbnail.h" #include "Settings.h" #include "PageInfo.h" #include "ImageTransformation.h" #include "filter_dc/AbstractFilterDataCollector.h" #include "filter_dc/ThumbnailCollector.h" #include "filters/deskew/CacheDrivenTask.h" namespace page_split { CacheDrivenTask::CacheDrivenTask( IntrusivePtr const& settings, IntrusivePtr const& next_task) : m_ptrNextTask(next_task), m_ptrSettings(settings) { } CacheDrivenTask::~CacheDrivenTask() { } void CacheDrivenTask::process( PageInfo const& page_info, AbstractFilterDataCollector* collector, ImageTransformation const& xform) { Settings::Record const record( m_ptrSettings->getPageRecord(page_info.imageId()) ); OrthogonalRotation const pre_rotation(xform.preRotation()); Dependencies const deps( page_info.metadata().size(), pre_rotation, record.combinedLayoutType() ); Params const* params = record.params(); if (!params || !deps.compatibleWith(*params)) { if (ThumbnailCollector* thumb_col = dynamic_cast(collector)) { thumb_col->processThumbnail( std::auto_ptr( new IncompleteThumbnail( thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), page_info.imageId(), xform ) ) ); } return; } PageLayout layout(params->pageLayout()); if (layout.uncutOutline().isEmpty()) { // Backwards compatibility with versions < 0.9.9 layout.setUncutOutline(xform.resultingRect()); } if (m_ptrNextTask) { ImageTransformation new_xform(xform); new_xform.setPreCropArea(layout.pageOutline(page_info.id().subPage())); m_ptrNextTask->process(page_info, collector, new_xform); return; } if (ThumbnailCollector* thumb_col = dynamic_cast(collector)) { thumb_col->processThumbnail( std::auto_ptr( new Thumbnail( thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), page_info.imageId(), xform, layout, page_info.leftHalfRemoved(), page_info.rightHalfRemoved() ) ) ); } } } // namespace page_split scantailor-RELEASE_0_9_12_2/filters/page_split/CacheDrivenTask.h000066400000000000000000000031461271170121200244310ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_SPLIT_CACHEDRIVENTASK_H_ #define PAGE_SPLIT_CACHEDRIVENTASK_H_ #include "NonCopyable.h" #include "RefCountable.h" #include "IntrusivePtr.h" class QSizeF; class PageInfo; class AbstractFilterDataCollector; class ImageTransformation; namespace deskew { class CacheDrivenTask; } namespace page_split { class Settings; class CacheDrivenTask : public RefCountable { DECLARE_NON_COPYABLE(CacheDrivenTask) public: CacheDrivenTask( IntrusivePtr const& settings, IntrusivePtr const& next_task); virtual ~CacheDrivenTask(); void process( PageInfo const& page_info, AbstractFilterDataCollector* collector, ImageTransformation const& xform); private: IntrusivePtr m_ptrNextTask; IntrusivePtr m_ptrSettings; }; } // namespace page_split #endif scantailor-RELEASE_0_9_12_2/filters/page_split/Dependencies.cpp000066400000000000000000000052621271170121200243550ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Dependencies.h" #include "Params.h" #include "XmlMarshaller.h" #include "XmlUnmarshaller.h" #include #include #include #include namespace page_split { Dependencies::Dependencies() { } Dependencies::Dependencies(QDomElement const& 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()) ) ) { } Dependencies::Dependencies( QSize const& image_size, OrthogonalRotation const rotation, LayoutType const layout_type) : m_imageSize(image_size), m_rotation(rotation), m_layoutType(layout_type) { } bool Dependencies::compatibleWith(Params const& params) const { Dependencies const& 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, QString const& tag_name) const { if (isNull()) { return QDomElement(); } 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")); return el; } } // namespace page_split scantailor-RELEASE_0_9_12_2/filters/page_split/Dependencies.h000066400000000000000000000034351271170121200240220ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_SPLIT_DEPENDENCIES_H_ #define PAGE_SPLIT_DEPENDENCIES_H_ #include "OrthogonalRotation.h" #include "LayoutType.h" #include class QString; class QDomDocument; class QDomElement; namespace page_split { class Params; /** * \brief Dependencies of a page parameters. * * Once dependencies change, the stored page parameters are no longer valid. */ class Dependencies { // Member-wise copying is OK. public: Dependencies(); Dependencies(QDomElement const& el); Dependencies(QSize const& image_size, OrthogonalRotation rotation, LayoutType layout_type); void setLayoutType(LayoutType type) { m_layoutType = type; } OrthogonalRotation const& orientation() const { return m_rotation; } bool compatibleWith(Params const& params) const; bool isNull() const { return m_imageSize.isNull(); } QDomElement toXml(QDomDocument& doc, QString const& tag_name) const; private: QSize m_imageSize; OrthogonalRotation m_rotation; LayoutType m_layoutType; }; } // namespace page_split #endif scantailor-RELEASE_0_9_12_2/filters/page_split/Filter.cpp000066400000000000000000000144471271170121200232210ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Filter.h" #include "FilterUiInterface.h" #include "OptionsWidget.h" #include "Task.h" #include "Settings.h" #include "ProjectPages.h" #include "ProjectReader.h" #include "ProjectWriter.h" #include "PageId.h" #include "ImageId.h" #include "PageLayout.h" #include "LayoutType.h" #include "Params.h" #include "CacheDrivenTask.h" #include "OrthogonalRotation.h" #ifndef Q_MOC_RUN #include #include #endif #include #include #include #include #include #include "CommandLine.h" #include "OrderBySplitTypeProvider.h" namespace page_split { Filter::Filter(IntrusivePtr const& page_sequence, PageSelectionAccessor const& page_selection_accessor) : m_ptrPages(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; ProviderPtr const default_order; ProviderPtr const order_by_splitline(new OrderBySplitTypeProvider(m_ptrSettings)); m_pageOrderOptions.push_back(PageOrderOption(tr("Natural order"), default_order)); m_pageOrderOptions.push_back(PageOrderOption(tr("Order by split type"), order_by_splitline)); } Filter::~Filter() { } QString Filter::getName() const { return QCoreApplication::translate("page_split::Filter", "Split Pages"); } PageView Filter::getView() const { return IMAGE_VIEW; } void Filter::performRelinking(AbstractRelinker const& relinker) { m_ptrSettings->performRelinking(relinker); } void Filter::preUpdateUI(FilterUiInterface* ui, PageId const& page_id) { m_ptrOptionsWidget->preUpdateUI(page_id); ui->setOptionsWidget(m_ptrOptionsWidget.get(), ui->KEEP_OWNERSHIP); } QDomElement Filter::saveSettings( ProjectWriter const& writer, QDomDocument& doc) const { using namespace boost::lambda; QDomElement filter_el(doc.createElement("page-split")); filter_el.setAttribute( "defaultLayoutType", layoutTypeToString(m_ptrSettings->defaultLayoutType()) ); writer.enumImages( boost::lambda::bind( &Filter::writeImageSettings, this, boost::ref(doc), var(filter_el), boost::lambda::_1, boost::lambda::_2 ) ); return filter_el; } void Filter::loadSettings( ProjectReader const& reader, QDomElement const& filters_el) { m_ptrSettings->clear(); QDomElement const filter_el(filters_el.namedItem("page-split").toElement()); QString const default_layout_type( filter_el.attribute("defaultLayoutType") ); m_ptrSettings->setLayoutTypeForAllPages( layoutTypeFromString(default_layout_type) ); QString const 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; int const id = el.attribute("id").toInt(&ok); if (!ok) { continue; } ImageId const image_id(reader.imageId(id)); if (image_id.isNull()) { continue; } Settings::UpdateAction update; QString const 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); } } void Filter::pageOrientationUpdate( ImageId const& image_id, OrthogonalRotation const& orientation) { Settings::Record const record(m_ptrSettings->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_ptrPages->autoSetLayoutTypeFor(image_id, orientation); } void Filter::writeImageSettings( QDomDocument& doc, QDomElement& filter_el, ImageId const& image_id, int const numeric_id) const { Settings::Record const record(m_ptrSettings->getPageRecord(image_id)); QDomElement image_el(doc.createElement("image")); image_el.setAttribute("id", numeric_id); if (LayoutType const* layout_type = record.layoutType()) { image_el.setAttribute( "layoutType", layoutTypeToString(*layout_type) ); } if (Params const* params = record.params()) { image_el.appendChild(params->toXml(doc, "params")); filter_el.appendChild(image_el); } } IntrusivePtr Filter::createTask( PageInfo const& page_info, IntrusivePtr const& next_task, bool const batch_processing, bool const debug) { return IntrusivePtr( new Task( IntrusivePtr(this), m_ptrSettings, m_ptrPages, next_task, page_info, batch_processing, debug ) ); } IntrusivePtr Filter::createCacheDrivenTask( IntrusivePtr const& next_task) { return IntrusivePtr( new CacheDrivenTask(m_ptrSettings, next_task) ); } 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; } } // page_split scantailor-RELEASE_0_9_12_2/filters/page_split/Filter.h000066400000000000000000000057761271170121200226730ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_SPLIT_FILTER_H_ #define PAGE_SPLIT_FILTER_H_ #include "NonCopyable.h" #include "AbstractFilter.h" #include "PageView.h" #include "IntrusivePtr.h" #include "FilterResult.h" #include "SafeDeletingQObjectPtr.h" #include #include "PageOrderOption.h" #include class PageId; class ImageId; class PageInfo; class ProjectPages; class PageSelectionAccessor; class OrthogonalRotation; namespace deskew { class Task; class CacheDrivenTask; } namespace page_split { class OptionsWidget; class Task; class CacheDrivenTask; class Settings; class Params; class Filter : public AbstractFilter { DECLARE_NON_COPYABLE(Filter) Q_DECLARE_TR_FUNCTIONS(page_split::Filter) public: Filter(IntrusivePtr const& page_sequence, PageSelectionAccessor const& page_selection_accessor); virtual ~Filter(); virtual QString getName() const; virtual PageView getView() const; virtual void performRelinking(AbstractRelinker const& relinker); virtual void preUpdateUI(FilterUiInterface* ui, PageId const& page_id); virtual QDomElement saveSettings( ProjectWriter const& wirter, QDomDocument& doc) const; virtual void loadSettings( ProjectReader const& reader, QDomElement const& filters_el); IntrusivePtr createTask(PageInfo const& page_info, IntrusivePtr const& next_task, bool batch_processing, bool debug); IntrusivePtr createCacheDrivenTask( IntrusivePtr const& next_task); OptionsWidget* optionsWidget() { return m_ptrOptionsWidget.get(); } void pageOrientationUpdate( ImageId const& image_id, OrthogonalRotation const& orientation); Settings* getSettings() { return m_ptrSettings.get(); }; virtual std::vector pageOrderOptions() const; virtual int selectedPageOrder() const; virtual void selectPageOrder(int option); private: void writeImageSettings( QDomDocument& doc, QDomElement& filter_el, ImageId const& image_id, int const numeric_id) const; IntrusivePtr m_ptrPages; IntrusivePtr m_ptrSettings; SafeDeletingQObjectPtr m_ptrOptionsWidget; std::vector m_pageOrderOptions; int m_selectedPageOrder; }; } // namespace page_split #endif scantailor-RELEASE_0_9_12_2/filters/page_split/ImageView.cpp000066400000000000000000000301071271170121200236400ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ImageView.h" #include "ImageView.h.moc" #include "ImageTransformation.h" #include "ImagePresentation.h" #include "Proximity.h" #include "PageId.h" #include "ProjectPages.h" #include #include #include #include #include #include #ifndef Q_MOC_RUN #include #include #endif #include namespace page_split { ImageView::ImageView( QImage const& image, QImage const& downscaled_image, ImageTransformation const& xform, PageLayout const& layout, IntrusivePtr const& pages, ImageId const& image_id, bool left_half_removed, bool right_half_removed) : ImageViewBase( image, downscaled_image, ImagePresentation(xform.transform(), xform.resultingPreCropArea()) ), m_ptrPages(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() { } void ImageView::setupCuttersInteraction() { QString const tip(tr("Drag the line or the handles.")); double const hit_radius = std::max(0.5 * m_handlePixmap.width(), 15.0); int const 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]); } // 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(); } } void ImageView::pageLayoutSetExternally(PageLayout const& layout) { m_virtLayout = layout; setupCuttersInteraction(); update(); } void ImageView::onPaint(QPainter& painter, InteractionState const& interaction) { painter.setRenderHint(QPainter::Antialiasing, false); painter.setRenderHint(QPainter::SmoothPixmapTransform, true); painter.setPen(Qt::NoPen); QRectF const 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); int const num_cutters = m_virtLayout.numCutters(); for (int i = 0; i < num_cutters; ++i) { QLineF const 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); } } PageLayout ImageView::widgetLayout() const { return m_virtLayout.transformed(virtualToWidget()); } QLineF ImageView::widgetCutterLine(int const line_idx) const { QRectF const 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 { QRectF const virt_rect(virtualDisplayRect()); QRectF widget_rect(virtualToWidget().mapRect(virt_rect)); double const 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 { qreal const delta = 0.5 * m_handlePixmap.width(); return getOccupiedWidgetRect().adjusted(0.0, delta, 0.0, -delta); } /** * This implementation differs from PageLayout::inscribedCutterLine() in that * it forces the endpoints to lie on the top and bottom boundary lines. * Line's angle may change as a result. */ QLineF ImageView::customInscribedCutterLine(QLineF const& line, QRectF const& 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()); return QLineF(QPointF(middle_x, rect.top()), QPointF(middle_x, rect.bottom())); } QPointF top_pt; QPointF bottom_pt; line.intersect(QLineF(rect.topLeft(), rect.topRight()), &top_pt); line.intersect(QLineF(rect.bottomLeft(), rect.bottomRight()), &bottom_pt); double const top_x = qBound(rect.left(), top_pt.x(), rect.right()); double const bottom_x = qBound(rect.left(), bottom_pt.x(), rect.right()); return QLineF(QPointF(top_x, rect.top()), QPointF(bottom_x, rect.bottom())); } QPointF ImageView::handlePosition(int line_idx, int handle_idx) const { QLineF const line(widgetCutterLine(line_idx)); return handle_idx == 0 ? line.p1() : line.p2(); } void ImageView::handleMoveRequest(int line_idx, int handle_idx, QPointF const& pos) { QRectF const valid_area(getOccupiedWidgetRect()); double const x = qBound(valid_area.left(), pos.x(), valid_area.right()); QPointF const wpt(x, handle_idx == 0 ? valid_area.top() : valid_area.bottom()); QPointF const vpt(widgetToVirtual().map(wpt)); QLineF virt_line(virtualCutterLine(line_idx)); if (handle_idx == 0) { virt_line.setP1(vpt); } else { virt_line.setP2(vpt); } m_virtLayout.setCutterLine(line_idx, virt_line); update(); } QLineF ImageView::linePosition(int line_idx) const { return widgetCutterLine(line_idx); } void ImageView::lineMoveRequest(int line_idx, QLineF line) { // Intersect with top and bottom. QPointF p_top; QPointF p_bottom; QRectF const valid_area(getOccupiedWidgetRect()); line.intersect(QLineF(valid_area.topLeft(), valid_area.topRight()), &p_top); line.intersect(QLineF(valid_area.bottomLeft(), valid_area.bottomRight()), &p_bottom); // Limit movement. double const min_x = qMin(p_top.x(), p_bottom.x()); double const max_x = qMax(p_top.x(), p_bottom.x()); double const left = valid_area.left() - min_x; double const right = max_x - valid_area.right(); 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); } QPointF ImageView::leftPageCenter() const { QRectF left_rect(m_virtLayout.leftPageOutline().boundingRect()); QRectF right_rect(m_virtLayout.rightPageOutline().boundingRect()); double const 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()); } QPointF ImageView::rightPageCenter() const { QRectF left_rect(m_virtLayout.leftPageOutline().boundingRect()); QRectF right_rect(m_virtLayout.rightPageOutline().boundingRect()); double const 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()); } void ImageView::unremoveLeftPage() { PageInfo page_info( m_ptrPages->unremovePage(PageId(m_imageId, PageId::LEFT_PAGE)) ); m_leftUnremoveButton.unlink(); m_leftPageRemoved = false; 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); } void ImageView::unremoveRightPage() { PageInfo page_info( m_ptrPages->unremovePage(PageId(m_imageId, PageId::RIGHT_PAGE)) ); m_rightUnremoveButton.unlink(); m_rightPageRemoved = false; 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); } } // namespace page_split scantailor-RELEASE_0_9_12_2/filters/page_split/ImageView.h000066400000000000000000000072271271170121200233140ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_SPLIT_IMAGEVIEW_H_ #define PAGE_SPLIT_IMAGEVIEW_H_ #include "ImageViewBase.h" #include "DragHandler.h" #include "ZoomHandler.h" #include "ObjectDragHandler.h" #include "DraggablePoint.h" #include "DraggableLineSegment.h" #include "PageLayout.h" #include "UnremoveButton.h" #include "ImageId.h" #include "IntrusivePtr.h" #include class ImageTransformation; class ProjectPages; class PageInfo; class QPointF; class QRectF; class QLineF; namespace page_split { class ImageView : public ImageViewBase, private InteractionHandler { Q_OBJECT public: ImageView(QImage const& image, QImage const& downscaled_image, ImageTransformation const& xform, PageLayout const& layout, IntrusivePtr const& pages, ImageId const& image_id, bool left_half_removed, bool right_half_removed); virtual ~ImageView(); signals: void invalidateThumbnail(PageInfo const& page_info); void pageLayoutSetLocally(PageLayout const& layout); public slots: void pageLayoutSetExternally(PageLayout const& layout); protected: virtual void onPaint(QPainter& painter, InteractionState const& interaction); private: void setupCuttersInteraction(); QPointF handlePosition(int line_idx, int handle_idx) const; void handleMoveRequest(int line_idx, int handle_idx, QPointF const& pos); QLineF linePosition(int line_idx) const; void lineMoveRequest(int line_idx, QLineF line); void dragFinished(); QPointF leftPageCenter() const; QPointF rightPageCenter() const; void unremoveLeftPage(); void unremoveRightPage(); /** * \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 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; static QLineF customInscribedCutterLine(QLineF const& line, QRectF const& rect); IntrusivePtr 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; QPixmap m_handlePixmap; /** * Page layout in virtual image coordinates. */ PageLayout m_virtLayout; bool m_leftPageRemoved; bool m_rightPageRemoved; }; } // namespace page_split #endif scantailor-RELEASE_0_9_12_2/filters/page_split/LayoutType.cpp000066400000000000000000000027621271170121200241100ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "LayoutType.h" #include namespace page_split { QString layoutTypeToString(LayoutType const 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"); return QString(); } LayoutType layoutTypeFromString(QString const& 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 scantailor-RELEASE_0_9_12_2/filters/page_split/LayoutType.h000066400000000000000000000021671271170121200235540ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_SPLIT_LAYOUT_TYPE_H_ #define PAGE_SPLIT_LAYOUT_TYPE_H_ #include namespace page_split { enum LayoutType { AUTO_LAYOUT_TYPE, SINGLE_PAGE_UNCUT, PAGE_PLUS_OFFCUT, TWO_PAGES }; QString layoutTypeToString(LayoutType type); LayoutType layoutTypeFromString(QString const& layout_type); } // namespace page_split #endif scantailor-RELEASE_0_9_12_2/filters/page_split/OptionsWidget.cpp000066400000000000000000000223721271170121200245670ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "OptionsWidget.h" #include "OptionsWidget.h.moc" #include "Filter.h" #include "SplitModeDialog.h" #include "Settings.h" #include "Params.h" #include "LayoutType.h" #include "PageId.h" #include "ProjectPages.h" #include "ScopedIncDec.h" #include #ifndef Q_MOC_RUN #include #endif #include namespace page_split { OptionsWidget::OptionsWidget( IntrusivePtr const& settings, IntrusivePtr const& page_sequence, PageSelectionAccessor const& page_selection_accessor) : m_ptrSettings(settings), m_ptrPages(page_sequence), m_pageSelectionAccessor(page_selection_accessor), m_ignoreAutoManualToggle(0), m_ignoreLayoutTypeToggle(0) { setupUi(this); // Workaround for QTBUG-182 QButtonGroup* grp = new QButtonGroup(this); grp->addButton(autoBtn); grp->addButton(manualBtn); 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)) ); } OptionsWidget::~OptionsWidget() { } void OptionsWidget::preUpdateUI(PageId const& page_id) { ScopedIncDec guard1(m_ignoreAutoManualToggle); ScopedIncDec guard2(m_ignoreLayoutTypeToggle); m_pageId = page_id; Settings::Record const record(m_ptrSettings->getPageRecord(page_id.imageId())); LayoutType const 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); } void OptionsWidget::postUpdateUI(UiData const& ui_data) { ScopedIncDec guard1(m_ignoreAutoManualToggle); ScopedIncDec guard2(m_ignoreLayoutTypeToggle); m_uiData = ui_data; changeBtn->setEnabled(true); autoBtn->setEnabled(true); manualBtn->setEnabled(true); if (ui_data.splitLineMode() == MODE_AUTO) { autoBtn->setChecked(true); } else { manualBtn->setChecked(true); } PageLayout::Type const 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; } splitLineGroup->setVisible(layout_type != PageLayout::SINGLE_PAGE_UNCUT); if (ui_data.layoutTypeAutoDetected()) { scopeLabel->setText(tr("Auto detected")); } } void OptionsWidget::pageLayoutSetExternally(PageLayout const& page_layout) { ScopedIncDec guard(m_ignoreAutoManualToggle); m_uiData.setPageLayout(page_layout); m_uiData.setSplitLineMode(MODE_MANUAL); commitCurrentParams(); manualBtn->setChecked(true); emit invalidateThumbnail(m_pageId); } void OptionsWidget::layoutTypeButtonToggled(bool const 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; } 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_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); Params const new_params( new_layout, m_uiData.dependencies(), m_uiData.splitLineMode() ); update.setParams(new_params); m_ptrSettings->updatePage(m_pageId.imageId(), update); m_uiData.setPageLayout(new_layout); emit pageLayoutSetLocally(new_layout); emit invalidateThumbnail(m_pageId); } } void OptionsWidget::showChangeDialog() { Settings::Record const record(m_ptrSettings->getPageRecord(m_pageId.imageId())); Params const* params = record.params(); if (!params) { return; } SplitModeDialog* 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(std::set const&, bool, LayoutType)), this, SLOT(layoutTypeSet(std::set const&, bool, LayoutType)) ); dialog->show(); } void OptionsWidget::layoutTypeSet( std::set const& pages, bool all_pages, LayoutType const layout_type) { if (pages.empty()) { return; } ProjectPages::LayoutType const plt = (layout_type == TWO_PAGES) ? ProjectPages::TWO_PAGE_LAYOUT : ProjectPages::ONE_PAGE_LAYOUT; if (all_pages) { m_ptrSettings->setLayoutTypeForAllPages(layout_type); if (layout_type != AUTO_LAYOUT_TYPE) { m_ptrPages->setLayoutTypeForAllPages(plt); } } else { m_ptrSettings->setLayoutTypeFor(layout_type, pages); if (layout_type != AUTO_LAYOUT_TYPE) { BOOST_FOREACH(PageId const& page_id, pages) { m_ptrPages->setLayoutTypeFor(page_id.imageId(), plt); } } } if (all_pages) { emit invalidateAllThumbnails(); } else { BOOST_FOREACH(PageId const& 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")); } } void OptionsWidget::splitLineModeChanged(bool const auto_mode) { 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(); } } void OptionsWidget::commitCurrentParams() { Params const params( m_uiData.pageLayout(), m_uiData.dependencies(), m_uiData.splitLineMode() ); Settings::UpdateAction update; update.setParams(params); m_ptrSettings->updatePage(m_pageId.imageId(), update); } /*============================= Widget::UiData ==========================*/ OptionsWidget::UiData::UiData() : m_splitLineMode(MODE_AUTO), m_layoutTypeAutoDetected(false) { } OptionsWidget::UiData::~UiData() { } void OptionsWidget::UiData::setPageLayout(PageLayout const& layout) { m_pageLayout = layout; } PageLayout const& OptionsWidget::UiData::pageLayout() const { return m_pageLayout; } void OptionsWidget::UiData::setDependencies(Dependencies const& deps) { m_deps = deps; } Dependencies const& OptionsWidget::UiData::dependencies() const { return m_deps; } void OptionsWidget::UiData::setSplitLineMode(AutoManualMode const mode) { m_splitLineMode = mode; } AutoManualMode OptionsWidget::UiData::splitLineMode() const { return m_splitLineMode; } bool OptionsWidget::UiData::layoutTypeAutoDetected() const { return m_layoutTypeAutoDetected; } void OptionsWidget::UiData::setLayoutTypeAutoDetected(bool const val) { m_layoutTypeAutoDetected = val; } } // namespace page_split scantailor-RELEASE_0_9_12_2/filters/page_split/OptionsWidget.h000066400000000000000000000055111271170121200242300ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_SPLIT_OPTIONSWIDGET_H_ #define PAGE_SPLIT_OPTIONSWIDGET_H_ #include "ui_PageSplitOptionsWidget.h" #include "FilterOptionsWidget.h" #include "IntrusivePtr.h" #include "LayoutType.h" #include "PageLayout.h" #include "ImageId.h" #include "PageId.h" #include "PageSelectionAccessor.h" #include "Dependencies.h" #include "AutoManualMode.h" #include class ProjectPages; namespace page_split { class Settings; class OptionsWidget : public FilterOptionsWidget, private Ui::PageSplitOptionsWidget { Q_OBJECT public: class UiData { // Member-wise copying is OK. public: UiData(); ~UiData(); void setPageLayout(PageLayout const& layout); PageLayout const& pageLayout() const; void setDependencies(Dependencies const& deps); Dependencies const& dependencies() const; void setSplitLineMode(AutoManualMode mode); AutoManualMode splitLineMode() const; bool layoutTypeAutoDetected() const; void setLayoutTypeAutoDetected(bool val); private: PageLayout m_pageLayout; Dependencies m_deps; AutoManualMode m_splitLineMode; bool m_layoutTypeAutoDetected; }; OptionsWidget(IntrusivePtr const& settings, IntrusivePtr const& page_sequence, PageSelectionAccessor const& page_selection_accessor); virtual ~OptionsWidget(); void preUpdateUI(PageId const& page_id); void postUpdateUI(UiData const& ui_data); signals: void pageLayoutSetLocally(PageLayout const& page_layout); public slots: void pageLayoutSetExternally(PageLayout const& page_layout); private slots: void layoutTypeButtonToggled(bool checked); void showChangeDialog(); void layoutTypeSet( std::set const& pages, bool all_pages, LayoutType layout_type); void splitLineModeChanged(bool auto_mode); private: void commitCurrentParams(); IntrusivePtr m_ptrSettings; IntrusivePtr m_ptrPages; PageSelectionAccessor m_pageSelectionAccessor; PageId m_pageId; UiData m_uiData; int m_ignoreAutoManualToggle; int m_ignoreLayoutTypeToggle; }; } // namespace page_split #endif scantailor-RELEASE_0_9_12_2/filters/page_split/OrderBySplitTypeProvider.cpp000066400000000000000000000050221271170121200267200ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich Copyright (C) Vadim Kuznetsov ()DikBSD 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "OrderBySplitTypeProvider.h" #include "Params.h" #include "PageLayout.h" #include #include #include namespace page_split { OrderBySplitTypeProvider::OrderBySplitTypeProvider(IntrusivePtr const& settings) : m_ptrSettings(settings) { } bool OrderBySplitTypeProvider::precedes( PageId const& lhs_page, bool const lhs_incomplete, PageId const& rhs_page, bool const 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; } assert(lhs_incomplete == false); assert(rhs_incomplete == false); Settings::Record const lhs_record(m_ptrSettings->getPageRecord(lhs_page.imageId())); Settings::Record const rhs_record(m_ptrSettings->getPageRecord(rhs_page.imageId())); Params const* lhs_params = lhs_record.params(); Params const* 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 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; } } } // namespace page_split scantailor-RELEASE_0_9_12_2/filters/page_split/OrderBySplitTypeProvider.h000066400000000000000000000026651271170121200263770ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich Copyright (C) Vadim Kuznetsov ()DikBSD 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_SPLIT_ORDER_BY_SPLIT_TYPE_PROVIDER_H_ #define PAGE_SPLIT_ORDER_BY_SPLIT_TYPE_PROVIDER_H_ #include "Settings.h" #include "IntrusivePtr.h" #include "PageOrderProvider.h" namespace page_split { class OrderBySplitTypeProvider : public PageOrderProvider { public: OrderBySplitTypeProvider(IntrusivePtr const& settings); virtual bool precedes( PageId const& lhs_page, bool lhs_incomplete, PageId const& rhs_page, bool rhs_incomplete) const; private: IntrusivePtr m_ptrSettings; }; } // namespace page_split #endif //PAGE_SPLIT_ORDER_BY_SPLIT_TYPE_PROVIDER_H_ scantailor-RELEASE_0_9_12_2/filters/page_split/PageLayout.cpp000066400000000000000000000312421271170121200240360ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "PageLayout.h" #include "NumericTraits.h" #include "XmlMarshaller.h" #include "XmlUnmarshaller.h" #include "ToLineProjector.h" #include "imageproc/PolygonUtils.h" #include #include #include #include #include #include #ifndef Q_MOC_RUN #include #endif #include #include #include using namespace imageproc; namespace page_split { PageLayout::PageLayout() : m_type(SINGLE_PAGE_UNCUT) { } PageLayout::PageLayout(QRectF const& 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) { } PageLayout::PageLayout(QRectF const& full_rect, QLineF const& cutter1, QLineF const& cutter2) : m_uncutOutline(full_rect), m_cutter1(cutter1), m_cutter2(cutter2), m_type(SINGLE_PAGE_CUT) { } PageLayout::PageLayout(QRectF const full_rect, QLineF const& split_line) : m_uncutOutline(full_rect), m_cutter1(split_line), m_type(TWO_PAGES) { } PageLayout::PageLayout( QPolygonF const& outline, QLineF const& cutter1, QLineF const& cutter2, Type type) : m_uncutOutline(outline), m_cutter1(cutter1), m_cutter2(cutter2), m_type(type) { } PageLayout::PageLayout(QDomElement const& layout_el) : m_uncutOutline( XmlUnmarshaller::polygonF( layout_el.namedItem("outline").toElement() ) ), m_cutter1( XmlUnmarshaller::lineF( layout_el.namedItem("cutter1").toElement() ) ), m_cutter2( XmlUnmarshaller::lineF( layout_el.namedItem("cutter2").toElement() ) ) { QString const type(layout_el.attribute("type")); QDomElement const split_line_el(layout_el.namedItem("split-line").toElement()); if (split_line_el.isNull()) { m_type = typeFromString(type); } else { // Backwards compatibility with versions < 0.9.9 // Note that we fill in m_uncutOutline elsewhere, namely in Task::process(). QLineF const split_line(XmlUnmarshaller::lineF(split_line_el)); // Backwards compatibility with versions <= 0.9.1 bool const left_page = ( layout_el.attribute("leftPageValid") == "1" ); bool const right_page = ( layout_el.attribute("rightPageValid") == "1" ); if (type == "two-pages" || (left_page && right_page)) { m_type = TWO_PAGES; m_cutter1 = split_line; m_cutter2 = QLineF(); } else if (type == "left-page" || left_page) { m_type = SINGLE_PAGE_CUT; m_cutter1 = QLineF(0, 0, 0, 1); // A bit of a hack, but should work. m_cutter2 = split_line; } else if (type == "right-page" || right_page) { m_type = SINGLE_PAGE_CUT; m_cutter1 = split_line; m_cutter2 = QLineF(); // This one is a special case. We don't know the outline at this point, // so we make m_cutter2 null. We'll assign the correct value to it in // setUncutOutline(). } else { m_type = SINGLE_PAGE_UNCUT; m_cutter1 = QLineF(); m_cutter2 = QLineF(); } } } void PageLayout::setType(Type type) { m_type = type; if (type == TWO_PAGES) { m_cutter2 = m_cutter1; } } void PageLayout::setUncutOutline(QRectF const& outline) { m_uncutOutline = outline; if (m_uncutOutline.size() < 4) { // Empty rect? return; } if (m_type == SINGLE_PAGE_CUT && m_cutter2.isNull()) { // Backwards compatibility. See PageLayout(QDomElement const&); m_cutter2.setP1(m_uncutOutline[1]); m_cutter2.setP2(m_uncutOutline[2]); } } QLineF const& PageLayout::cutterLine(int idx) const { assert(idx >= 0 && idx < numCutters()); return idx == 0 ? m_cutter1 : m_cutter2; } void PageLayout::setCutterLine(int idx, QLineF const& 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; } 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; } int PageLayout::numSubPages() const { return m_type == TWO_PAGES ? 2 : 1; } QLineF PageLayout::inscribedCutterLine(int idx) const { assert(idx >= 0 && idx < numCutters()); if (m_uncutOutline.size() < 4) { return QLineF(); } QLineF const raw_line(cutterLine(idx)); QPointF const origin(raw_line.p1()); QPointF const 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) { QLineF const 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; } } QLineF res(min_pt, max_pt); ensureSameDirection(raw_line, res); return res; } 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(); } QLineF const line1(extendToCover(m_cutter1, m_uncutOutline)); QLineF line2(extendToCover(m_cutter2, m_uncutOutline)); ensureSameDirection(line1, line2); QLineF const reverse_line1(line1.p2(), line1.p1()); QLineF const 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; } QLineF const line1(m_uncutOutline[0], m_uncutOutline[3]); QLineF line2(extendToCover(m_cutter1, m_uncutOutline)); ensureSameDirection(line1, line2); QLineF const reverse_line1(line1.p2(), line1.p1()); QLineF const 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; } QLineF const line1(m_uncutOutline[1], m_uncutOutline[2]); QLineF line2(extendToCover(m_cutter1, m_uncutOutline)); ensureSameDirection(line1, line2); QLineF const reverse_line1(line1.p2(), line1.p1()); QLineF const 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(PageId::SubPage const 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(); } PageLayout PageLayout::transformed(QTransform const& xform) const { return PageLayout( xform.map(m_uncutOutline), xform.map(m_cutter1), xform.map(m_cutter2), m_type ); } QDomElement PageLayout::toXml(QDomDocument& doc, QString const& name) const { XmlMarshaller marshaller(doc); QDomElement el(doc.createElement(name)); el.setAttribute("type", typeToString(m_type)); el.appendChild(marshaller.polygonF(m_uncutOutline, "outline")); int const 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; } PageLayout::Type PageLayout::typeFromString(QString const& str) { 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(Type const type) { char const* str = 0; 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::fromAscii(str); } /** * Extends or shrinks a line segment in such a way that if you draw perpendicular * lines through its endpoints, the given polygon would be squeezed between these * two perpendiculars. This ensures that the resulting line segment intersects * all the polygon edges it can possibly intersect. */ QLineF PageLayout::extendToCover(QLineF const& line, QPolygonF const& poly) { if (poly.isEmpty()) { return line; } // Project every vertex of the polygon onto the line and take extremas. double min = NumericTraits::max(); double max = NumericTraits::min(); ToLineProjector const projector(line); BOOST_FOREACH(QPointF const& pt, poly) { double const scalar = projector.projectionScalar(pt); if (scalar < min) { min = scalar; } if (scalar > max) { max = scalar; } } return QLineF(line.pointAt(min), line.pointAt(max)); } /** * Flips \p line2 if that would make the angle between the two lines more acute. * The angle between lines is interpreted as an angle between vectors * (line1.p2() - line1.p1()) and (line2.p2() - line2.p1()). */ void PageLayout::ensureSameDirection(QLineF const& line1, QLineF& line2) { QPointF const v1(line1.p2() - line1.p1()); QPointF const v2(line2.p2() - line2.p1()); double const dot = v1.x() * v2.x() + v1.y() * v2.y(); if (dot < 0.0) { line2 = QLineF(line2.p2(), line2.p1()); } } /** * Add the intersection point between \p line1 and \p line2 * to \p poly, provided they intersect at all and the intersection * point is "between" line1.p1() and line2.p1(). We consider a point * to be between two other points by projecting it to the line between * those two points and checking if the projected point is between them. * When finding the intersection point, we treat \p line1 and \p line2 * as lines, not line segments. */ void PageLayout::maybeAddIntersectionPoint( QPolygonF& poly, QLineF const& line1, QLineF const& line2) { QPointF intersection; if (line1.intersect(line2, &intersection) == QLineF::NoIntersection) { return; } ToLineProjector const projector(QLineF(line1.p1(), line2.p1())); double const p = projector.projectionScalar(intersection); if (p > 0.0 && p < 1.0) { poly << intersection; } } } // namespace page_split scantailor-RELEASE_0_9_12_2/filters/page_split/PageLayout.h000066400000000000000000000132521271170121200235040ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGELAYOUT_H_ #define PAGELAYOUT_H_ #include "PageId.h" #include "LayoutType.h" #include #include #include class QRectF; class QTransform; class QDomElement; class QDomDocument; namespace page_split { /** * The page layout comprises the following: * \li A rectangular outline, possibly affine-transformed. * \li Layout type indicator. * \li Zero, 1 or 2 cutters. * * Cutters are lines with *arbitrary endpoints* that have different meaning * depending on layout type. The SINGLE_PAGE_UNCUT layout doesn't have any * cutters. The TWO_PAGES layout has one cutter that splits the outline into * two pages. The SINGLE_PAGE_CUT layout has two cutters that cut the outline * from two sides. They don't have a special identity like being a left or * a right cutter. Swapping them won't change the area they bound, and that * 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. */ PageLayout(QRectF const& full_rect); /** * \brief Constructs a SINGLE_PAGE_CUT layout. */ PageLayout(QRectF const& full_rect, QLineF const& cutter1, QLineF const& cutter2); /** * \brief Constructs a TWO_PAGES layout. */ PageLayout(QRectF const full_rect, QLineF const& split_line); /** * \brief Construct a page layout based on XML data. */ PageLayout(QDomElement const& layout_el); bool isNull() const { return m_uncutOutline.isEmpty(); } Type type() const { return m_type; } /** * \brief Sets layout type and ensures the internal state * is consistent with the new type. */ void setType(Type type); LayoutType toLayoutType() const; QPolygonF const& uncutOutline() const { return m_uncutOutline; } /** * 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(QRectF const& outline); /** * \brief Get a cutter line. * * \param idx Cutter index, from 0 inclusive to numCutters() exclusive. * \return The cutter line with *arbitrary* endpoints. */ QLineF const& 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, QLineF const& 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(QTransform const& xform) const; QDomElement toXml(QDomDocument& doc, QString const& name) const; private: PageLayout(QPolygonF const& outline, QLineF const& cutter1, QLineF const& cutter2, Type type); static Type typeFromString(QString const& str); static QString typeToString(Type type); static QLineF extendToCover(QLineF const& line, QPolygonF const& poly); static void ensureSameDirection(QLineF const& line1, QLineF& line2); static void maybeAddIntersectionPoint( QPolygonF& poly, QLineF const& line1, QLineF const& 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 scantailor-RELEASE_0_9_12_2/filters/page_split/PageLayoutEstimator.cpp000066400000000000000000000632111271170121200257270ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "PageLayoutEstimator.h" #include "PageLayout.h" #include "OrthogonalRotation.h" #include "VertLineFinder.h" #include "ContentSpanFinder.h" #include "ImageMetadata.h" #include "ProjectPages.h" #include "DebugImages.h" #include "Dpi.h" #include "ImageTransformation.h" #include "foundation/Span.h" #include "imageproc/Binarize.h" #include "imageproc/BinaryThreshold.h" #include "imageproc/BWColor.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/Constants.h" #include "imageproc/RasterOp.h" #include "imageproc/Shear.h" #include "imageproc/OrthogonalRotation.h" #include "imageproc/Scale.h" #include "imageproc/SlicedHistogram.h" #include "imageproc/Transform.h" #include "imageproc/Grayscale.h" #include "imageproc/GrayRasterOp.h" #include "imageproc/PolygonRasterizer.h" #ifndef Q_MOC_RUN #include #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace page_split { using namespace imageproc; namespace { double lineCenterX(QLineF const& line) { return 0.5 * (line.p1().x() + line.p2().x()); } struct CenterComparator { bool operator()(QLineF const& line1, QLineF const& line2) const { return lineCenterX(line1) < lineCenterX(line2); } }; /** * \brief Try to auto-detect a page layout for a single-page configuration. * * \param layout_type The requested layout type. The layout type of * SINGLE_PAGE_UNCUT is not handled here. * \param ltr_lines Folding line candidates sorted from left to right. * \param image_size The dimentions of the page image. * \param hor_shadows A downscaled grayscale image that contains * long enough and not too thin horizontal lines. * \param dbg An optional sink for debugging images. * \return The page layout detected or a null auto_ptr. */ std::auto_ptr autoDetectSinglePageLayout( LayoutType const layout_type, std::vector const& ltr_lines, QRectF const& virtual_image_rect, GrayImage const& gray_downscaled, QTransform const& out_to_downscaled, DebugImages* dbg) { double const 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()) { QLineF const& first_line = ltr_lines.front(); double const line_center = lineCenterX(first_line); if (fabs(image_center - line_center) > 0.65 * image_center) { break; } } if (ltr_lines.size() > 1) { QLineF const& last_line = ltr_lines.back(); double const line_center = lineCenterX(last_line); if (fabs(image_center - line_center) > 0.65 * image_center) { break; } } // Return a SINGLE_PAGE_UNCUT layout. return std::auto_ptr(new PageLayout(virtual_image_rect)); } while (false); if (ltr_lines.empty()) { // Impossible to detect the layout type. return std::auto_ptr(); } else if (ltr_lines.size() > 1) { return std::auto_ptr( new PageLayout(virtual_image_rect, ltr_lines.front(), ltr_lines.back()) ); } else { assert(ltr_lines.size() == 1); QLineF const& line = ltr_lines.front(); double const line_center = lineCenterX(line); if (line_center < image_center) { QLineF const right_line( virtual_image_rect.topRight(), virtual_image_rect.bottomRight() ); return std::auto_ptr( new PageLayout(virtual_image_rect, line, right_line) ); } else { QLineF const left_line( virtual_image_rect.topLeft(), virtual_image_rect.bottomLeft() ); return std::auto_ptr( new PageLayout(virtual_image_rect, left_line, line) ); } } } /** * \brief Try to auto-detect a page layout for a two-page configuration. * * \param ltr_lines Folding line candidates sorted from left to right. * \param image_size The dimentions of the page image. * \return The page layout detected or a null auto_ptr. */ std::auto_ptr autoDetectTwoPageLayout( std::vector const& ltr_lines, QRectF const& virtual_image_rect) { if (ltr_lines.empty()) { // Impossible to detect the page layout. return std::auto_ptr(); } else if (ltr_lines.size() == 1) { return std::auto_ptr( new PageLayout(virtual_image_rect, ltr_lines.front()) ); } // Find the line closest to the center. double const image_center = virtual_image_rect.center().x(); double min_distance = std::numeric_limits::max(); QLineF const* best_line = 0; BOOST_FOREACH (QLineF const& line, ltr_lines) { double const line_center = lineCenterX(line); double const distance = fabs(line_center - image_center); if (distance < min_distance) { min_distance = distance; best_line = &line; } } return std::auto_ptr( new PageLayout(virtual_image_rect, *best_line) ); } int numPages(LayoutType const layout_type, ImageTransformation const& pre_xform) { int num_pages = 0; switch (layout_type) { case AUTO_LAYOUT_TYPE: { QSize const 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; } return num_pages; } } // anonymous namespace PageLayout PageLayoutEstimator::estimatePageLayout( LayoutType const layout_type, QImage const& input, ImageTransformation const& pre_xform, BinaryThreshold const bw_threshold, DebugImages* const dbg) { if (layout_type == SINGLE_PAGE_UNCUT) { return PageLayout(pre_xform.resultingRect()); } std::auto_ptr layout( tryCutAtFoldingLine(layout_type, input, pre_xform, dbg) ); if (layout.get()) { return *layout; } return cutAtWhitespace(layout_type, input, pre_xform, bw_threshold, dbg); } namespace { class BadTwoPageSplitter { public: 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()(QLineF const& line) { double const dist = fabs(lineCenterX(line) - m_imageCenter); return dist > m_distFromCenterThreshold; } private: double m_imageCenter; double m_distFromCenterThreshold; }; } // anonymous namespace /** * \brief Attempts to find the folding line and cut the image there. * * \param layout_type The type of a layout to detect. If set to * something other than AUTO_LAYOUT_TYPE, the returned * layout will have the same type. The layout type of * SINGLE_PAGE_UNCUT is not handled here. * \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 dbg An optional sink for debugging images. * \return The detected page layout, or a null auto_ptr if page layout * could not be detected. */ std::auto_ptr PageLayoutEstimator::tryCutAtFoldingLine( LayoutType const layout_type, QImage const& input, ImageTransformation const& pre_xform, DebugImages* const dbg) { int const num_pages = numPages(layout_type, pre_xform); GrayImage gray_downscaled; QTransform out_to_downscaled; int const max_lines = 8; std::vector lines( VertLineFinder::findLines( input, pre_xform, max_lines, dbg, num_pages == 1 ? &gray_downscaled : 0, num_pages == 1 ? &out_to_downscaled : 0 ) ); std::sort(lines.begin(), lines.end(), CenterComparator()); QRectF const virtual_image_rect( pre_xform.transform().mapRect(input.rect()) ); QPointF const 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. QLineF const left_line(lines.front()); QLineF const right_line(lines.back()); double const 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 = fabs(left_dist); right_dist = 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 autoDetectTwoPageLayout(lines, virtual_image_rect); } } /** * \brief Attempts to find a suitable whitespace to draw a splitting line through. * * \param layout_type The type of a layout to detect. If set to * something other than AUTO_LAYOUT_TYPE, 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 Even if no suitable whitespace was found, this function * will return a PageLayout consistent with the layout_type requested. */ PageLayout PageLayoutEstimator::cutAtWhitespace( LayoutType const layout_type, QImage const& input, ImageTransformation const& pre_xform, BinaryThreshold const 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"); } 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. bool const left_offcut = checkForLeftOffcut(img); bool const 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. Skew const skew(skew_finder.findSkew(img)); if (skew.angle() != 0.0 && skew.confidence() >= Skew::GOOD_CONFIDENCE) { int const w = img.width(); int const h = img.height(); double const angle_deg = skew.angle(); double const tg = tan(angle_deg * constants::DEG2RAD); int const margin = (int)ceil(fabs(0.5 * h * tg)); int const 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; } } int const num_pages = numPages(layout_type, pre_xform); PageLayout const 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; } /** * \brief Attempts to find a suitable whitespace to draw a splitting line through. * * \param layout_type The type of a layout to detect. If set to * something other than AUTO_LAYOUT_TYPE, the returned * layout will have the same type. * \param num_pages The number of pages (1 or 2) in the layout. * \param input The black and white, 150 DPI input image. * \param left_offcut True if there seems to be garbage on the left side. * \param right_offcut True if there seems to be garbage on the right side. * \param dbg An optional sink for debugging images. * \return A PageLAyout consistent with the layout_type requested. */ PageLayout PageLayoutEstimator::cutAtWhitespaceDeskewed150( LayoutType const layout_type, int const num_pages, BinaryImage const& input, bool const left_offcut, bool const right_offcut, DebugImages* dbg) { using namespace boost::lambda; int const width = input.width(); int const 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"); } ContentSpanFinder span_finder; span_finder.setMinContentWidth(2); span_finder.setMinWhitespaceWidth(8); std::deque spans; SlicedHistogram hist(cc_img, SlicedHistogram::COLS); void (std::deque::*push_back) (const Span&) = &std::deque::push_back; span_finder.find(hist, boost::lambda::bind(push_back, var(spans), _1)); 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_refined"); } return processContentSpansTwoPages( layout_type, spans, width, height ); } } imageproc::BinaryImage PageLayoutEstimator::to300DpiBinary( QImage const& img, QTransform& xform, BinaryThreshold const binary_threshold) { double const xfactor = (300.0 * constants::DPI2DPM) / img.dotsPerMeterX(); double const yfactor = (300.0 * constants::DPI2DPM) / img.dotsPerMeterY(); if (fabs(xfactor - 1.0) < 0.1 && fabs(yfactor - 1.0) < 0.1) { return BinaryImage(img, binary_threshold); } QTransform scale_xform; scale_xform.scale(xfactor, yfactor); xform *= scale_xform; QSize const new_size( std::max(1, (int)ceil(xfactor * img.width())), std::max(1, (int)ceil(yfactor * img.height())) ); GrayImage const new_image(scaleToGray(GrayImage(img), new_size)); return BinaryImage(new_image, binary_threshold); } BinaryImage PageLayoutEstimator::removeGarbageAnd2xDownscale( BinaryImage const& 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; } bool PageLayoutEstimator::checkForLeftOffcut(BinaryImage const& image) { int const margin = 2; // Some scanners leave garbage near page borders. int const width = 3; QRect rect(margin, 0, width, image.height()); rect.adjust(0, margin, 0, -margin); return image.countBlackPixels(rect) != 0; } bool PageLayoutEstimator::checkForRightOffcut(BinaryImage const& image) { int const margin = 2; // Some scanners leave garbage near page borders. int const width = 3; QRect rect(image.width() - margin - width, 0, width, image.height()); rect.adjust(0, margin, 0, -margin); return image.countBlackPixels(rect) != 0; } void PageLayoutEstimator::visualizeSpans( DebugImages& dbg, std::deque const& spans, BinaryImage const& image, char const* label) { int const height = image.height(); QImage spans_img( image.toQImage().convertToFormat( QImage::Format_ARGB32_Premultiplied ) ); { QPainter painter(&spans_img); QBrush const brush(QColor(0xff, 0x00, 0x00, 0x50)); BOOST_FOREACH(Span const& span, spans) { QRect const rect(span.begin(), 0, span.width(), height); painter.fillRect(rect, brush); } } dbg.add(spans_img, label); } void PageLayoutEstimator::removeInsignificantEdgeSpans(std::deque& spans) { 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 (int i = gaps.size() - 1; i >= 0; --i) { sum += spans[i + 1].width(); gaps[i].second = sum; } int const total = sum + spans[0].width(); int may_be_removed = total / 15; do { Span const& first = spans.front(); Span const& 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()); } PageLayout PageLayoutEstimator::processContentSpansSinglePage( LayoutType const layout_type, std::deque const& spans, int const width, int const height, bool const left_offcut, bool const right_offcut) { assert(layout_type == AUTO_LAYOUT_TYPE || layout_type == PAGE_PLUS_OFFCUT); QRectF const 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); } } QLineF const right_line( virtual_image_rect.topRight(), virtual_image_rect.bottomRight() ); return PageLayout(virtual_image_rect, vertLine(x), right_line); } // 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); } } QLineF const left_line( virtual_image_rect.topLeft(), virtual_image_rect.bottomLeft() ); return PageLayout(virtual_image_rect, left_line, vertLine(x)); } if (layout_type == PAGE_PLUS_OFFCUT) { QLineF const line1(virtual_image_rect.topLeft(), virtual_image_rect.bottomLeft()); QLineF const 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); } } PageLayout PageLayoutEstimator::processContentSpansTwoPages( LayoutType const layout_type, std::deque const& spans, int const width, int const height) { assert(layout_type == AUTO_LAYOUT_TYPE || layout_type == TWO_PAGES); QRectF const 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; std::vector gaps(spans.size() - 1); #if 0 int sum = 0; for (unsigned i = 0; i < gaps.size(); ++i) { sum += spans[i].width(); gaps[i].first = sum; } sum = 0; for (int i = gaps.size() - 1; i >= 0; --i) { sum += spans[i + 1].width(); gaps[i].second = sum; } #else int const content_begin = spans.front().begin(); int const 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) { double const min = std::min(gaps[i].first, gaps[i].second); double const max = std::max(gaps[i].first, gaps[i].second); double const 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 ); } double const 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) { double const min = std::min(gaps[i].first, gaps[i].second); double const max = std::max(gaps[i].first, gaps[i].second); double const ratio = min / max; if (ratio < acceptable_ratio) { break; } int const width = Span(spans[i], spans[i + 1]).width(); if (width > max_width) { max_width = width; widest_gap = i; } } for (unsigned i = best_gap + 1; i < gaps.size(); ++i) { double const min = std::min(gaps[i].first, gaps[i].second); double const max = std::max(gaps[i].first, gaps[i].second); double const ratio = min / max; if (ratio < acceptable_ratio) { break; } int const width = Span(spans[i], spans[i + 1]).width(); if (width > max_width) { max_width = width; widest_gap = i; } } Span const gap(spans[widest_gap], spans[widest_gap + 1]); x = gap.center(); } return PageLayout(virtual_image_rect, vertLine(x)); } PageLayout PageLayoutEstimator::processTwoPagesWithSingleSpan(Span const& span, int width, int height) { QRectF const virtual_image_rect(0, 0, width, height); double const page_center = 0.5 * width; double const box_center = span.center(); double const box_half_width = 0.5 * span.width(); double const distance_to_page_center = fabs(page_center - box_center) - box_half_width; double x; if (distance_to_page_center > 15) { x = page_center; } else { Span const left_ws(0, span); Span const 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); } } return PageLayout(virtual_image_rect, vertLine(x)); } QLineF PageLayoutEstimator::vertLine(double x) { return QLineF(x, 0.0, x, 1.0); } } // namespace page_split scantailor-RELEASE_0_9_12_2/filters/page_split/PageLayoutEstimator.h000066400000000000000000000074321271170121200253770ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_SPLIT_PAGELAYOUTESTIMATOR_H_ #define PAGE_SPLIT_PAGELAYOUTESTIMATOR_H_ #include "foundation/VirtualFunction.h" #include "LayoutType.h" #include #include #include class QRect; class QPoint; class QImage; class QTransform; class ImageTransformation; class DebugImages; class Span; namespace imageproc { class BinaryImage; class BinaryThreshold; } 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, QImage const& input, ImageTransformation const& pre_xform, imageproc::BinaryThreshold bw_threshold, DebugImages* dbg = 0); private: static std::auto_ptr tryCutAtFoldingLine( LayoutType layout_type, QImage const& input, ImageTransformation const& pre_xform, DebugImages* dbg); static PageLayout cutAtWhitespace( LayoutType layout_type, QImage const& input, ImageTransformation const& pre_xform, imageproc::BinaryThreshold const bw_threshold, DebugImages* dbg); static PageLayout cutAtWhitespaceDeskewed150( LayoutType layout_type, int num_pages, imageproc::BinaryImage const& input, bool left_offcut, bool right_offcut, DebugImages* dbg); static imageproc::BinaryImage to300DpiBinary( QImage const& img, QTransform& xform, imageproc::BinaryThreshold threshold); static imageproc::BinaryImage removeGarbageAnd2xDownscale( imageproc::BinaryImage const& image, DebugImages* dbg); static bool checkForLeftOffcut(imageproc::BinaryImage const& image); static bool checkForRightOffcut(imageproc::BinaryImage const& image); static void visualizeSpans( DebugImages& dbg, std::deque const& spans, imageproc::BinaryImage const& image, char const* label); static void removeInsignificantEdgeSpans(std::deque& spans); static PageLayout processContentSpansSinglePage( LayoutType layout_type, std::deque const& spans, int width, int height, bool left_offcut, bool right_offcut); static PageLayout processContentSpansTwoPages( LayoutType layout_type, std::deque const& spans, int width, int height); static PageLayout processTwoPagesWithSingleSpan( Span const& span, int width, int height); static QLineF vertLine(double x); }; } // namespace page_split #endif scantailor-RELEASE_0_9_12_2/filters/page_split/Params.cpp000066400000000000000000000031671271170121200232140ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Params.h" #include #include namespace page_split { Params::Params(PageLayout const& layout, Dependencies const& deps, AutoManualMode const split_line_mode) : m_layout(layout), m_deps(deps), m_splitLineMode(split_line_mode) { } Params::Params(QDomElement const& el) : m_layout(el.namedItem("pages").toElement()), m_deps(el.namedItem("dependencies").toElement()), m_splitLineMode( el.attribute("mode") == "manual" ? MODE_MANUAL : MODE_AUTO ) { } Params::~Params() { } QDomElement Params::toXml(QDomDocument& doc, QString const& 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")); return el; } } // namespace page_split scantailor-RELEASE_0_9_12_2/filters/page_split/Params.h000066400000000000000000000031011271170121200226450ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_SPLIT_PARAMS_H_ #define PAGE_SPLIT_PARAMS_H_ #include "PageLayout.h" #include "Dependencies.h" #include "AutoManualMode.h" #include class QDomDocument; class QDomElement; namespace page_split { class Params { public: // Member-wise copying is OK. Params(PageLayout const& layout, Dependencies const& deps, AutoManualMode split_line_mode); Params(QDomElement const& el); ~Params(); PageLayout const& pageLayout() const { return m_layout; } Dependencies const& dependencies() const { return m_deps; } AutoManualMode splitLineMode() const { return m_splitLineMode; } QDomElement toXml(QDomDocument& doc, QString const& name) const; private: PageLayout m_layout; Dependencies m_deps; AutoManualMode m_splitLineMode; }; } // namespace page_split #endif scantailor-RELEASE_0_9_12_2/filters/page_split/Settings.cpp000066400000000000000000000174231271170121200235710ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Settings.h" #include "RelinkablePath.h" #include "AbstractRelinker.h" #include #ifndef Q_MOC_RUN #include #endif #include namespace page_split { Settings::Settings() : m_defaultLayoutType(AUTO_LAYOUT_TYPE) { } Settings::~Settings() { } void Settings::clear() { QMutexLocker locker(&m_mutex); m_perPageRecords.clear(); m_defaultLayoutType = AUTO_LAYOUT_TYPE; } void Settings::performRelinking(AbstractRelinker const& relinker) { QMutexLocker locker(&m_mutex); PerPageRecords new_records; BOOST_FOREACH(PerPageRecords::value_type const& kv, m_perPageRecords) { RelinkablePath const 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); } LayoutType Settings::defaultLayoutType() const { QMutexLocker locker(&m_mutex); return m_defaultLayoutType; } void Settings::setLayoutTypeForAllPages(LayoutType const layout_type) { QMutexLocker locker(&m_mutex); PerPageRecords::iterator it(m_perPageRecords.begin()); PerPageRecords::iterator const 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; } void Settings::setLayoutTypeFor(LayoutType const layout_type, std::set const& pages) { QMutexLocker locker(&m_mutex); UpdateAction action; action.setLayoutType(layout_type); BOOST_FOREACH(PageId const& page_id, pages) { updatePageLocked(page_id.imageId(), action); } } Settings::Record Settings::getPageRecord(ImageId const& image_id) const { QMutexLocker locker(&m_mutex); return getPageRecordLocked(image_id); } Settings::Record Settings::getPageRecordLocked(ImageId const& image_id) const { PerPageRecords::const_iterator 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(ImageId const& image_id, UpdateAction const& action) { QMutexLocker locker(&m_mutex); updatePageLocked(image_id, action); } void Settings::updatePageLocked(ImageId const& image_id, UpdateAction const& action) { PerPageRecords::iterator it(m_perPageRecords.lower_bound(image_id)); if (it == m_perPageRecords.end() || m_perPageRecords.key_comp()(image_id, it->first)) { // No record exists for this page. Record record(m_defaultLayoutType); record.update(action); 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(PerPageRecords::iterator const it, UpdateAction const& 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( ImageId const& image_id, UpdateAction const& action, bool* conflict) { QMutexLocker locker(&m_mutex); PerPageRecords::iterator it(m_perPageRecords.lower_bound(image_id)); if (it == m_perPageRecords.end() || m_perPageRecords.key_comp()(image_id, it->first)) { // No record exists for this page. Record record(m_defaultLayoutType); record.update(action); if (record.hasLayoutTypeConflict()) { if (conflict) { *conflict = true; } return Record(m_defaultLayoutType); } if (!record.isNull()) { m_perPageRecords.insert( it, PerPageRecords::value_type(image_id, record) ); } if (conflict) { *conflict = false; } return record; } else { // A record was found. Record record(it->second, m_defaultLayoutType); record.update(action); if (record.hasLayoutTypeConflict()) { if (conflict) { *conflict = true; } return Record(it->second, m_defaultLayoutType); } if (conflict) { *conflict = false; } if (record.isNull()) { m_perPageRecords.erase(it); return Record(m_defaultLayoutType); } else { it->second = record; return record; } } } /*======================= Settings::BaseRecord ======================*/ Settings::BaseRecord::BaseRecord() : m_params(PageLayout(), Dependencies(), MODE_AUTO), m_layoutType(AUTO_LAYOUT_TYPE), m_paramsValid(false), m_layoutTypeValid(false) { } void Settings::BaseRecord::setParams(Params const& params) { m_params = params; m_paramsValid = true; } void Settings::BaseRecord::setLayoutType(LayoutType const layout_type) { m_layoutType = layout_type; m_layoutTypeValid = true; } bool Settings::BaseRecord::hasLayoutTypeConflict(LayoutType const layout_type) const { if (!m_paramsValid) { // No data - no conflict. 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; } assert(!"Unreachable"); return false; } /*========================= Settings::Record ========================*/ Settings::Record::Record(LayoutType const default_layout_type) : m_defaultLayoutType(default_layout_type) { } Settings::Record::Record( BaseRecord const& base_record, LayoutType const default_layout_type) : BaseRecord(base_record), m_defaultLayoutType(default_layout_type) { } LayoutType Settings::Record::combinedLayoutType() const { return m_layoutTypeValid ? m_layoutType : m_defaultLayoutType; } void Settings::Record::update(UpdateAction const& 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; } } bool Settings::Record::hasLayoutTypeConflict() const { return BaseRecord::hasLayoutTypeConflict(combinedLayoutType()); } /*======================= Settings::UpdateAction ======================*/ void Settings::UpdateAction::setLayoutType(LayoutType const layout_type) { m_layoutType = layout_type; m_layoutTypeAction = SET; } void Settings::UpdateAction::clearLayoutType() { m_layoutTypeAction = CLEAR; } void Settings::UpdateAction::setParams(Params const& params) { m_params = params; m_paramsAction = SET; } void Settings::UpdateAction::clearParams() { m_paramsAction = CLEAR; } } // namespace page_split scantailor-RELEASE_0_9_12_2/filters/page_split/Settings.h000066400000000000000000000115611271170121200232330ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_SPLIT_SETTINGS_H_ #define PAGE_SPLIT_SETTINGS_H_ #include "RefCountable.h" #include "NonCopyable.h" #include "PageLayout.h" #include "LayoutType.h" #include "Params.h" #include "ImageId.h" #include "PageId.h" #include #include #include #include class AbstractRelinker; namespace page_split { class Settings : public RefCountable { DECLARE_NON_COPYABLE(Settings) private: class BaseRecord { // Member-wise copying is OK. friend class Settings; public: BaseRecord(); LayoutType const* layoutType() const { return m_layoutTypeValid ? &m_layoutType : 0; } Params const* params() const { return m_paramsValid ? &m_params : 0; } /** * \brief A record is considered null of it doesn't carry any * information. */ bool isNull() const { return !(m_paramsValid || m_layoutTypeValid); } protected: void setParams(Params const& params); void setLayoutType(LayoutType layout_type); void clearParams() { m_paramsValid = false; } void clearLayoutType() { m_layoutTypeValid = false; } /** * \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; }; public: class UpdateAction; class Record : public BaseRecord { // Member-wise copying is OK. public: Record(LayoutType default_layout_type); Record(BaseRecord const& base_record, LayoutType default_layout_type); LayoutType combinedLayoutType() const; void update(UpdateAction const& action); bool hasLayoutTypeConflict() const; private: LayoutType m_defaultLayoutType; }; class UpdateAction { friend class Settings::Record; public: UpdateAction() : m_params(PageLayout(), Dependencies(), MODE_AUTO), m_layoutType(AUTO_LAYOUT_TYPE), m_paramsAction(DONT_TOUCH), m_layoutTypeAction(DONT_TOUCH) {} void setLayoutType(LayoutType layout_type); void clearLayoutType(); void setParams(Params const& params); void clearParams(); private: enum Action { DONT_TOUCH, SET, CLEAR }; Params m_params; LayoutType m_layoutType; Action m_paramsAction; Action m_layoutTypeAction; }; Settings(); virtual ~Settings(); /** * \brief Reset all settings to their initial state. */ void clear(); void performRelinking(AbstractRelinker const& relinker); 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 specified pages, removing other page * parameters where they conflict with the new layout type. */ void setLayoutTypeFor(LayoutType layout_type, std::set const& pages); /** * \brief Returns all data related to a page as a single object. */ Record getPageRecord(ImageId const& 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(ImageId const& image_id, UpdateAction const& 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( ImageId const& image_id, UpdateAction const& action, bool* conflict = 0); private: typedef std::map PerPageRecords; Record getPageRecordLocked(ImageId const& image_id) const; void updatePageLocked(ImageId const& image_id, UpdateAction const& action); void updatePageLocked(PerPageRecords::iterator it, UpdateAction const& action); mutable QMutex m_mutex; PerPageRecords m_perPageRecords; LayoutType m_defaultLayoutType; }; } // namespace page_split #endif scantailor-RELEASE_0_9_12_2/filters/page_split/SplitLineObject.h000066400000000000000000000031311271170121200244570ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_SPLIT_SPLIT_LINE_OBJECT_H_ #define PAGE_SPLIT_SPLIT_LINE_OBJECT_H_ #include "DraggableObject.h" namespace page_split { class SplitLineObject : public DraggableObject { protected: virtual Proximity lineProximity (QPointF const& widget_mouse_pos, InteractionState const& interaction) const = 0; virtual QPointF linePosition(InteractionState const& interaction) const = 0; virtual void lineMoveRequest(QPointF const& widget_pos) = 0; virtual Proximity proximity( QPointF const& widget_mouse_pos, InteractionState const& interaction) { return lineProximity(widget_mouse_pos, interaction); } virtual QPointF position(InteractionState const& interaction) const { return linePosition(interaction); } virtual void moveRequest(QPointF const& widget_pos) { return lineMoveRequest(widget_pos); } }; } // namespace page_split #endif scantailor-RELEASE_0_9_12_2/filters/page_split/SplitModeDialog.cpp000066400000000000000000000102751271170121200250070ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "SplitModeDialog.h.moc" #include "PageSelectionAccessor.h" #include #include #include namespace page_split { SplitModeDialog::SplitModeDialog( QWidget* const parent, PageId const& cur_page, PageSelectionAccessor const& page_selection_accessor, LayoutType const layout_type, PageLayout::Type const auto_detected_layout_type, bool const 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(selectedPagesRB); if (m_selectedPages.size() <= 1) { selectedPagesWidget->setEnabled(false); } layoutTypeLabel->setPixmap(QPixmap(iconFor(m_layoutType))); if (m_layoutType == AUTO_LAYOUT_TYPE) { modeAuto->setChecked(true); } else { modeManual->setChecked(true); } connect(modeAuto, SIGNAL(pressed()), this, SLOT(autoDetectionSelected())); connect(modeManual, SIGNAL(pressed()), this, SLOT(manualModeSelected())); connect(buttonBox, SIGNAL(accepted()), this, SLOT(onSubmit())); } SplitModeDialog::~SplitModeDialog() { } void SplitModeDialog::autoDetectionSelected() { layoutTypeLabel->setPixmap(QPixmap(":/icons/layout_type_auto.png")); } void SplitModeDialog::manualModeSelected() { char const* resource = iconFor(combinedLayoutType()); layoutTypeLabel->setPixmap(QPixmap(resource)); } void SplitModeDialog::onSubmit() { 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); emit accepted(m_selectedPages, true, layout_type); accept(); } else if (thisPageAndFollowersRB->isChecked()) { m_pages.selectPagePlusFollowers(m_curPage).swap(pages); } else if (selectedPagesRB->isChecked()) { emit accepted(m_selectedPages, false, layout_type); accept(); return; } emit accepted(pages, false, layout_type); // We assume the default connection from accepted() to accept() // was removed. accept(); } LayoutType SplitModeDialog::combinedLayoutType() const { if (m_layoutType != AUTO_LAYOUT_TYPE) { return m_layoutType; } 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; } assert(!"Unreachable"); return AUTO_LAYOUT_TYPE; } char const* SplitModeDialog::iconFor(LayoutType const layout_type) { char const* 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 scantailor-RELEASE_0_9_12_2/filters/page_split/SplitModeDialog.h000066400000000000000000000040151271170121200244470ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_SPLIT_SPLITMODEDIALOG_H_ #define PAGE_SPLIT_SPLITMODEDIALOG_H_ #include "ui_PageSplitModeDialog.h" #include "LayoutType.h" #include "PageLayout.h" #include "PageId.h" #include "PageSequence.h" #include "IntrusivePtr.h" #include #include class ProjectPages; class PageSelectionAccessor; class QButtonGroup; namespace page_split { class SplitModeDialog : public QDialog, private Ui::PageSplitModeDialog { Q_OBJECT public: SplitModeDialog(QWidget* parent, PageId const& cur_page, PageSelectionAccessor const& page_selection_accessor, LayoutType layout_type, PageLayout::Type auto_detected_layout_type, bool auto_detected_layout_type_valid); virtual ~SplitModeDialog(); signals: void accepted(std::set const& pages, bool all_pages, LayoutType layout_type); private slots: void autoDetectionSelected(); void manualModeSelected(); void onSubmit(); private: LayoutType combinedLayoutType() const; static char const* 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; }; } // namespace page_split #endif scantailor-RELEASE_0_9_12_2/filters/page_split/Task.cpp000066400000000000000000000166411271170121200226740ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Task.h" #include "TaskStatus.h" #include "Filter.h" #include "OptionsWidget.h" #include "Settings.h" #include "LayoutType.h" #include "ProjectPages.h" #include "PageInfo.h" #include "PageId.h" #include "PageLayoutEstimator.h" #include "PageLayout.h" #include "Dependencies.h" #include "Params.h" #include "FilterData.h" #include "ImageMetadata.h" #include "Dpm.h" #include "Dpi.h" #include "OrthogonalRotation.h" #include "ImageTransformation.h" #include "filters/deskew/Task.h" #include "ImageView.h" #include "FilterUiInterface.h" #include "DebugImages.h" #include #include #include #include #include namespace page_split { using imageproc::BinaryThreshold; class Task::UiUpdater : public FilterResult { public: UiUpdater(IntrusivePtr const& filter, IntrusivePtr const& pages, std::auto_ptr dbg_img, QImage const& image, PageInfo const& page_info, ImageTransformation const& xform, OptionsWidget::UiData const& ui_data, bool batch_processing); virtual void updateUI(FilterUiInterface* ui); virtual IntrusivePtr filter() { return m_ptrFilter; } private: IntrusivePtr m_ptrFilter; IntrusivePtr m_ptrPages; std::auto_ptr m_ptrDbg; QImage m_image; QImage m_downscaledImage; PageInfo m_pageInfo; ImageTransformation m_xform; OptionsWidget::UiData m_uiData; bool m_batchProcessing; }; static ProjectPages::LayoutType toPageLayoutType(PageLayout const& 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"); return ProjectPages::ONE_PAGE_LAYOUT; } Task::Task( IntrusivePtr const& filter, IntrusivePtr const& settings, IntrusivePtr const& pages, IntrusivePtr const& next_task, PageInfo const& page_info, bool const batch_processing, bool const debug) : m_ptrFilter(filter), m_ptrSettings(settings), m_ptrPages(pages), m_ptrNextTask(next_task), m_pageInfo(page_info), m_batchProcessing(batch_processing) { if (debug) { m_ptrDbg.reset(new DebugImages); } } Task::~Task() { } FilterResultPtr Task::process(TaskStatus const& status, FilterData const& data) { status.throwIfCancelled(); Settings::Record record(m_ptrSettings->getPageRecord(m_pageInfo.imageId())); OrthogonalRotation const pre_rotation(data.xform().preRotation()); Dependencies const deps( data.origImage().size(), pre_rotation, record.combinedLayoutType() ); OptionsWidget::UiData ui_data; ui_data.setDependencies(deps); for (;;) { Params const* const params = record.params(); PageLayout new_layout; if (!params || !deps.compatibleWith(*params)) { new_layout = PageLayoutEstimator::estimatePageLayout( record.combinedLayoutType(), data.grayImage(), data.xform(), data.bwThreshold(), m_ptrDbg.get() ); status.throwIfCancelled(); } else if (params->pageLayout().uncutOutline().isEmpty()) { // Backwards compatibility with versions < 0.9.9 new_layout = params->pageLayout(); new_layout.setUncutOutline(data.xform().resultingRect()); } else { break; } Params const new_params(new_layout, deps, MODE_AUTO); Settings::UpdateAction update; 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. } #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; } PageLayout const& layout = record.params()->pageLayout(); ui_data.setLayoutTypeAutoDetected( record.combinedLayoutType() == AUTO_LAYOUT_TYPE ); ui_data.setPageLayout(layout); ui_data.setSplitLineMode(record.params()->splitLineMode()); m_ptrPages->setLayoutTypeFor(m_pageInfo.imageId(), toPageLayoutType(layout)); if (m_ptrNextTask) { ImageTransformation new_xform(data.xform()); new_xform.setPreCropArea(layout.pageOutline(m_pageInfo.id().subPage())); return m_ptrNextTask->process(status, FilterData(data, new_xform)); } else { return FilterResultPtr( new UiUpdater( m_ptrFilter, m_ptrPages, m_ptrDbg, data.origImage(), m_pageInfo, data.xform(), ui_data, m_batchProcessing ) ); } } /*============================ Task::UiUpdater =========================*/ Task::UiUpdater::UiUpdater( IntrusivePtr const& filter, IntrusivePtr const& pages, std::auto_ptr dbg_img, QImage const& image, PageInfo const& page_info, ImageTransformation const& xform, OptionsWidget::UiData const& ui_data, bool const batch_processing) : m_ptrFilter(filter), m_ptrPages(pages), m_ptrDbg(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; } ImageView* 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(PageInfo const&)), opt_widget, SIGNAL(invalidateThumbnail(PageInfo const&)) ); QObject::connect( view, SIGNAL(pageLayoutSetLocally(PageLayout const&)), opt_widget, SLOT(pageLayoutSetExternally(PageLayout const&)) ); QObject::connect( opt_widget, SIGNAL(pageLayoutSetLocally(PageLayout const&)), view, SLOT(pageLayoutSetExternally(PageLayout const&)) ); } } // namespace page_split scantailor-RELEASE_0_9_12_2/filters/page_split/Task.h000066400000000000000000000035741271170121200223420ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_SPLIT_TASK_H_ #define PAGE_SPLIT_TASK_H_ #include "NonCopyable.h" #include "RefCountable.h" #include "FilterResult.h" #include "IntrusivePtr.h" #include "PageInfo.h" #include class TaskStatus; class FilterData; class DebugImages; class ProjectPages; class QImage; namespace deskew { class Task; } namespace page_split { class Filter; class Settings; class PageLayout; class Task : public RefCountable { DECLARE_NON_COPYABLE(Task) public: Task( IntrusivePtr const& filter, IntrusivePtr const& settings, IntrusivePtr const& pages, IntrusivePtr const& next_task, PageInfo const& page_info, bool batch_processing, bool debug); virtual ~Task(); FilterResultPtr process(TaskStatus const& status, FilterData const& data); private: class UiUpdater; IntrusivePtr m_ptrFilter; IntrusivePtr m_ptrSettings; IntrusivePtr m_ptrPages; IntrusivePtr m_ptrNextTask; std::auto_ptr m_ptrDbg; PageInfo m_pageInfo; bool m_batchProcessing; }; } // namespace PageSplit #endif scantailor-RELEASE_0_9_12_2/filters/page_split/Thumbnail.cpp000066400000000000000000000076041271170121200237140ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Thumbnail.h" #include #include #include #include #include #include #include #include #include namespace page_split { Thumbnail::Thumbnail( IntrusivePtr const& thumbnail_cache, QSizeF const& max_size, ImageId const& image_id, ImageTransformation const& xform, PageLayout const& layout, bool left_half_removed, bool right_half_removed) : ThumbnailBase(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::paintOverImage( QPainter& painter, QTransform const& image_to_display, QTransform const& thumb_to_display) { QRectF const 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: QPolygonF const left_poly(m_layout.leftPageOutline()); QPolygonF const 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()); int const subpage_idx = m_leftHalfRemoved ? 0 : 1; QPointF const 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; } 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:; } } QPointF Thumbnail::subPageCenter( QPolygonF const& left_page, QPolygonF const& right_page, QTransform const& image_to_display, int subpage_idx) { QRectF rects[2]; rects[0] = left_page.boundingRect(); rects[1] = right_page.boundingRect(); double const 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()); } } // namespace page_split scantailor-RELEASE_0_9_12_2/filters/page_split/Thumbnail.h000066400000000000000000000034101271170121200233500ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_SPLIT_THUMBNAIL_H_ #define PAGE_SPLIT_THUMBNAIL_H_ #include "ThumbnailBase.h" #include "PageLayout.h" #include "IntrusivePtr.h" #include class QPointF; class QSizeF; class QPolygonF; class ThumbnailPixmapCache; class ImageId; class ImageTransformation; namespace page_split { class Thumbnail : public ThumbnailBase { public: Thumbnail(IntrusivePtr const& thumbnail_cache, QSizeF const& max_size, ImageId const& image_id, ImageTransformation const& xform, PageLayout const& layout, bool left_half_removed, bool right_half_removed); virtual void paintOverImage( QPainter& painter, QTransform const& image_to_display, QTransform const& thumb_to_display); private: QPointF subPageCenter( QPolygonF const& left_page, QPolygonF const& right_page, QTransform const& image_to_display, int subpage_idx); PageLayout m_layout; QPixmap m_trashPixmap; bool m_leftHalfRemoved; bool m_rightHalfRemoved; }; } // namespace page_split #endif scantailor-RELEASE_0_9_12_2/filters/page_split/UnremoveButton.cpp000066400000000000000000000045531271170121200247650ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "UnremoveButton.h" #include "Proximity.h" #include #include #include #include #include namespace page_split { UnremoveButton::UnremoveButton(PositionGetter const& 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.")); } void UnremoveButton::onPaint(QPainter& painter, InteractionState const& interaction) { QPixmap const& pixmap = interaction.proximityLeader(m_proximityInteraction) ? m_hoveredPixmap : m_defaultPixmap; QRectF rect(pixmap.rect()); rect.moveCenter(m_positionGetter()); painter.setWorldTransform(QTransform()); painter.drawPixmap(rect.topLeft(), pixmap); } void UnremoveButton::onProximityUpdate( QPointF const& screen_mouse_pos, InteractionState& interaction) { QRectF rect(m_defaultPixmap.rect()); rect.moveCenter(m_positionGetter()); bool const 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) ); } void UnremoveButton::onMousePressEvent(QMouseEvent* event, InteractionState& interaction) { if (!interaction.captured() && interaction.proximityLeader(m_proximityInteraction)) { event->accept(); m_clickCallback(); } } } // namespace page_split scantailor-RELEASE_0_9_12_2/filters/page_split/UnremoveButton.h000066400000000000000000000036231271170121200244270ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef UNREMOVE_BUTTON_H_ #define UNREMOVE_BUTTON_H_ #include "InteractionHandler.h" #include "InteractionState.h" #include "Proximity.h" #include #include #include #ifndef Q_MOC_RUN #include #endif namespace page_split { class UnremoveButton : public InteractionHandler { Q_DECLARE_TR_FUNCTIONS(page_split::UnremoveButton) public: typedef boost::function PositionGetter; typedef boost::function ClickCallback; UnremoveButton(PositionGetter const& position_getter); void setClickCallback(ClickCallback const& callback) { m_clickCallback = callback; } protected: virtual void onPaint(QPainter& painter, InteractionState const& interaction); virtual void onProximityUpdate(QPointF const& screen_mouse_pos, InteractionState& interaction); virtual void onMousePressEvent(QMouseEvent* event, InteractionState& interaction); private: static void noOp() {} PositionGetter m_positionGetter; ClickCallback m_clickCallback; InteractionState::Captor m_proximityInteraction; QPixmap m_defaultPixmap; QPixmap m_hoveredPixmap; bool m_wasHovered; }; } // namespace page_split #endif scantailor-RELEASE_0_9_12_2/filters/page_split/VertLineFinder.cpp000066400000000000000000000223571271170121200246530ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "VertLineFinder.h" #include "ImageTransformation.h" #include "Dpi.h" #include "DebugImages.h" #include "imageproc/Transform.h" #include "imageproc/GrayImage.h" #include "imageproc/Grayscale.h" #include "imageproc/GrayRasterOp.h" #include "imageproc/Morphology.h" #include "imageproc/MorphGradientDetect.h" #include "imageproc/HoughLineDetector.h" #include "imageproc/Constants.h" #ifndef Q_MOC_RUN #include #endif #include #include #include #include #include #include #include #include #include #include #include #include namespace page_split { using namespace imageproc; std::vector VertLineFinder::findLines( QImage const& image, ImageTransformation const& xform, int const max_lines, DebugImages* dbg, GrayImage* gray_downscaled, QTransform* out_to_downscaled) { int const 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); } GrayImage const 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)); if (dbg) { 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); #endif #if 0 GrayImage h_gradient(morphGradientDetectDarkSide(preprocessed, QSize(11, 1))); GrayImage v_gradient(morphGradientDetectDarkSide(preprocessed, QSize(1, 11))); if (dbg) { dbg->add(h_gradient, "h_gradient"); 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)); #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"); } GrayImage const raster_lines(closeGray(h_gradient, QSize(1, 19), 0x00)); h_gradient = GrayImage(); if (dbg) { dbg->add(raster_lines, "short_segments_removed"); } double const line_thickness = 5.0; double const max_angle = 7.0; // degrees double const angle_step = 0.25; int const angle_steps_to_max = (int)(max_angle / angle_step); int const total_angle_steps = angle_steps_to_max * 2 + 1; double const 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. double const margin_mm = 3.5; int const margin = (int)floor(0.5 + margin_mm * constants::MM2INCH * dpi); int const x_limit = raster_lines.width() - margin; int const height = raster_lines.height(); uint8_t const* line = raster_lines.data(); int const stride = raster_lines.stride(); for (int y = 0; y < height; ++y, line += stride) { for (int x = margin; x < x_limit; ++x) { unsigned const val = line[x]; if (val > 1) { line_detector.process(x, y, weight_table[val]); } } } unsigned const min_quality = (unsigned)(height * line_thickness * 1.8) + 1; if (dbg) { dbg->add(line_detector.visualizeHoughSpace(min_quality), "hough_space"); } std::vector const hough_lines(line_detector.findLines(min_quality)); typedef std::list LineGroups; LineGroups line_groups; BOOST_FOREACH (HoughLine const& hough_line, hough_lines) { QualityLine const new_line( hough_line.pointAtY(0.0), hough_line.pointAtY(height), hough_line.quality() ); LineGroup* home_group = 0; LineGroups::iterator it(line_groups.begin()); LineGroups::iterator const 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.push_back(LineGroup(new_line)); } } std::vector lines; BOOST_FOREACH (LineGroup const& 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 ) ); { QPainter painter(&visual); painter.setRenderHint(QPainter::Antialiasing); QPen pen(QColor(0xff, 0x00, 0x00, 0x80)); pen.setWidthF(3.0); painter.setPen(pen); BOOST_FOREACH (QLineF const& line, lines) { painter.drawLine(line); } } dbg->add(visual, "vector_lines"); } // Transform lines back into original coordinates. QTransform const undo_100dpi( xform_100dpi.transformBack() * xform.transform() ); BOOST_FOREACH (QLineF& line, lines) { line = undo_100dpi.map(line); } return lines; } GrayImage VertLineFinder::removeDarkVertBorders(GrayImage const& src) { GrayImage dst(src); selectVertBorders(dst); grayRasterOp > >(dst, src); return dst; } void VertLineFinder::selectVertBorders(GrayImage& image) { int const w = image.width(); int const h = image.height(); unsigned char* image_line = image.data(); int const image_stride = image.stride(); 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; } } } void VertLineFinder::buildWeightTable(unsigned weight_table[]) { int gray_level = 0; unsigned weight = 2; int segment = 2; int prev_segment = 1; while (gray_level < 256) { int const 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( QPointF const& top, QPointF const& bottom, unsigned const quality) : 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); } /*======================= VertLineFinder::LineGroup ========================*/ VertLineFinder::LineGroup::LineGroup(QualityLine const& line) : m_leader(line), m_left(line.left().x()), m_right(line.right().x()) { } bool VertLineFinder::LineGroup::belongsHere(QualityLine const& line) const { if (m_left > line.right().x()) { return false; } else if (m_right < line.left().x()) { return false; } else { return true; } } void VertLineFinder::LineGroup::add(QualityLine const& line) { m_left = std::min(qreal(m_left), line.left().x()); m_right = std::max(qreal(m_right), line.right().x()); if (line.quality() > m_leader.quality()) { m_leader = line; } } void VertLineFinder::LineGroup::merge(LineGroup const& 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(); } } } // namespace page_split scantailor-RELEASE_0_9_12_2/filters/page_split/VertLineFinder.h000066400000000000000000000044021271170121200243070ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAGE_SPLIT_VERTLINEFINDER_H_ #define PAGE_SPLIT_VERTLINEFINDER_H_ #include #include #include #include class QLineF; class QImage; class ImageTransformation; class DebugImages; namespace imageproc { class GrayImage; } namespace page_split { class VertLineFinder { public: static std::vector findLines( QImage const& image, ImageTransformation const& xform, int max_lines, DebugImages* dbg = 0, imageproc::GrayImage* gray_downscaled = 0, QTransform* out_to_downscaled = 0); private: class QualityLine { public: QualityLine( QPointF const& top, QPointF const& bottom, unsigned quality); QPointF const& left() const { return m_left; } QPointF const& right() const { return m_right; } unsigned quality() const { return m_quality; } QLineF toQLine() const; private: QPointF m_left; QPointF m_right; unsigned m_quality; }; class LineGroup { public: LineGroup(QualityLine const& line); bool belongsHere(QualityLine const& line) const; void add(QualityLine const& line); void merge(LineGroup const& other); QualityLine const& leader() const { return m_leader; } private: QualityLine m_leader; double m_left; double m_right; }; static imageproc::GrayImage removeDarkVertBorders(imageproc::GrayImage const& src); static void selectVertBorders(imageproc::GrayImage& image); static void buildWeightTable(unsigned weight_table[]); }; } // namespace page_split #endif scantailor-RELEASE_0_9_12_2/filters/page_split/ui/000077500000000000000000000000001271170121200216735ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/filters/page_split/ui/PageSplitModeDialog.ui000066400000000000000000000134171271170121200260550ustar00rootroot00000000000000 PageSplitModeDialog Qt::WindowModal 0 0 320 331 Split Pages Qt::Horizontal 40 20 QFrame::StyledPanel :/icons/layout_type_auto.png Qt::Horizontal 40 20 Mode Auto true Manual Scope This page only true All pages This page and the following ones 0 0 Selected pages 0 Qt::Horizontal QSizePolicy::Fixed 30 0 7 Use Ctrl+Click / Shift+Click to select multiple pages. Qt::Vertical 20 0 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox rejected() PageSplitModeDialog reject() 316 260 286 274 scantailor-RELEASE_0_9_12_2/filters/page_split/ui/PageSplitOptionsWidget.ui000066400000000000000000000174371271170121200266560ustar00rootroot00000000000000 PageSplitOptionsWidget 0 0 238 270 0 0 Form Page Layout QLayout::SetDefaultConstraint Qt::Horizontal 13 20 :/icons/single_page_uncut.png :/icons/single_page_uncut_selected.png:/icons/single_page_uncut.png 32 32 true false true :/icons/right_page_plus_offcut.png :/icons/right_page_plus_offcut_selected.png:/icons/right_page_plus_offcut.png 32 32 true false true :/icons/two_pages.png :/icons/two_pages_selected.png:/icons/two_pages.png 32 32 true true Qt::Horizontal 13 20 ? Qt::AlignCenter Qt::Horizontal 40 20 Change ... Qt::Horizontal 40 20 Split Line 9 9 9 Qt::Horizontal 1 20 Auto true true true Manual true true Qt::Horizontal 1 20 Qt::Vertical 20 52 scantailor-RELEASE_0_9_12_2/filters/select_content/000077500000000000000000000000001271170121200221405ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/filters/select_content/ApplyDialog.cpp000066400000000000000000000056331271170121200250600ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ApplyDialog.h" #include "ApplyDialog.h.moc" #include "PageSelectionAccessor.h" #include #include #include namespace select_content { ApplyDialog::ApplyDialog( QWidget* parent, PageId const& cur_page, PageSelectionAccessor const& 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(everyOtherSelectedRB); if (m_selectedPages.size() <= 1) { selectedPagesWidget->setEnabled(false); everyOtherSelectedWidget->setEnabled(false); everyOtherSelectedHint->setText(selectedPagesHint->text()); } else if (m_selectedRanges.size() > 1) { everyOtherSelectedWidget->setEnabled(false); everyOtherSelectedHint->setText(tr("Can't do: more than one group is selected.")); } connect(buttonBox, SIGNAL(accepted()), this, SLOT(onSubmit())); } ApplyDialog::~ApplyDialog() { } void ApplyDialog::onSubmit() { std::set pages; // thisPageOnlyRB is intentionally not handled. if (allPagesRB->isChecked()) { m_pages.selectAll().swap(pages); emit applySelection(pages); accept(); return; } else if (thisPageAndFollowersRB->isChecked()) { m_pages.selectPagePlusFollowers(m_curPage).swap(pages); } else if (selectedPagesRB->isChecked()) { emit applySelection(m_selectedPages); accept(); return; } else if (everyOtherRB->isChecked()) { m_pages.selectEveryOther(m_curPage).swap(pages); } else if (everyOtherSelectedRB->isChecked()) { assert(m_selectedRanges.size() == 1); PageRange const& range = m_selectedRanges.front(); range.selectEveryOther(m_curPage).swap(pages); } emit applySelection(pages); // We assume the default connection from accept() to accepted() was removed. accept(); } } // namespace select_content scantailor-RELEASE_0_9_12_2/filters/select_content/ApplyDialog.h000066400000000000000000000032171271170121200245210ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SELECT_CONTENT_APPLYDIALOG_H_ #define SELECT_CONTENT_APPLYDIALOG_H_ #include "ui_SelectContentApplyDialog.h" #include "PageId.h" #include "PageRange.h" #include "PageSequence.h" #include "IntrusivePtr.h" #include #include #include class PageSelectionAccessor; class QButtonGroup; namespace select_content { class Scope; class ApplyDialog : public QDialog, private Ui::SelectContentApplyDialog { Q_OBJECT public: ApplyDialog(QWidget* parent, PageId const& cur_page, PageSelectionAccessor const& page_selection_accessor); virtual ~ApplyDialog(); signals: void applySelection(std::set const& pages); private slots: void onSubmit(); private: PageSequence m_pages; std::set m_selectedPages; std::vector m_selectedRanges; PageId m_curPage; QButtonGroup* m_pBtnGroup; }; } // namespace select_content #endif scantailor-RELEASE_0_9_12_2/filters/select_content/CMakeLists.txt000066400000000000000000000017061271170121200247040ustar00rootroot00000000000000PROJECT("Select Content Filter") INCLUDE_DIRECTORIES(BEFORE "${CMAKE_CURRENT_BINARY_DIR}") FILE(GLOB ui_files "ui/*.ui") QT4_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 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}) QT4_AUTOMOC(${sources}) ADD_LIBRARY(select_content STATIC ${sources} ${ui_sources}) TRANSLATION_SOURCES(scantailor ${sources} ${ui_files}) scantailor-RELEASE_0_9_12_2/filters/select_content/CacheDrivenTask.cpp000066400000000000000000000052111271170121200256410ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "CacheDrivenTask.h" #include "Thumbnail.h" #include "IncompleteThumbnail.h" #include "ImageTransformation.h" #include "Settings.h" #include "PageInfo.h" #include "PageId.h" #include "filter_dc/AbstractFilterDataCollector.h" #include "filter_dc/ThumbnailCollector.h" #include "filter_dc/ContentBoxCollector.h" #include "filters/page_layout/CacheDrivenTask.h" namespace select_content { CacheDrivenTask::CacheDrivenTask( IntrusivePtr const& settings, IntrusivePtr const& next_task) : m_ptrSettings(settings), m_ptrNextTask(next_task) { } CacheDrivenTask::~CacheDrivenTask() { } void CacheDrivenTask::process( PageInfo const& page_info, AbstractFilterDataCollector* collector, ImageTransformation const& xform) { std::auto_ptr params(m_ptrSettings->getPageParams(page_info.id())); Dependencies const deps(xform.resultingPreCropArea()); if (!params.get() || !params->dependencies().matches(deps)) { if (ThumbnailCollector* thumb_col = dynamic_cast(collector)) { thumb_col->processThumbnail( std::auto_ptr( new IncompleteThumbnail( thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), page_info.imageId(), xform ) ) ); } return; } if (ContentBoxCollector* col = dynamic_cast(collector)) { col->process(xform, params->contentRect()); } if (m_ptrNextTask) { m_ptrNextTask->process(page_info, collector, xform, params->contentRect()); return; } if (ThumbnailCollector* thumb_col = dynamic_cast(collector)) { thumb_col->processThumbnail( std::auto_ptr( new Thumbnail( thumb_col->thumbnailCache(), thumb_col->maxLogicalThumbSize(), page_info.imageId(), xform, params->contentRect() ) ) ); } } } // namespace select_content scantailor-RELEASE_0_9_12_2/filters/select_content/CacheDrivenTask.h000066400000000000000000000032021271170121200253040ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SELECT_CONTENT_CACHEDRIVENTASK_H_ #define SELECT_CONTENT_CACHEDRIVENTASK_H_ #include "NonCopyable.h" #include "RefCountable.h" #include "IntrusivePtr.h" class QSizeF; class PageInfo; class AbstractFilterDataCollector; class ImageTransformation; namespace page_layout { class CacheDrivenTask; } namespace select_content { class Settings; class CacheDrivenTask : public RefCountable { DECLARE_NON_COPYABLE(CacheDrivenTask) public: CacheDrivenTask(IntrusivePtr const& settings, IntrusivePtr const& next_task); virtual ~CacheDrivenTask(); void process( PageInfo const& page_info, AbstractFilterDataCollector* collector, ImageTransformation const& xform); private: IntrusivePtr m_ptrSettings; IntrusivePtr m_ptrNextTask; }; } // namespace select_content #endif scantailor-RELEASE_0_9_12_2/filters/select_content/ContentBoxFinder.cpp000066400000000000000000001142571271170121200260710ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ContentBoxFinder.h" #include "TaskStatus.h" #include "DebugImages.h" #include "FilterData.h" #include "ImageTransformation.h" #include "Dpi.h" #include "Despeckle.h" #include "imageproc/BinaryImage.h" #include "imageproc/BinaryThreshold.h" #include "imageproc/Binarize.h" #include "imageproc/BWColor.h" #include "imageproc/Connectivity.h" #include "imageproc/ConnComp.h" #include "imageproc/ConnCompEraserExt.h" #include "imageproc/Transform.h" #include "imageproc/RasterOp.h" #include "imageproc/GrayRasterOp.h" #include "imageproc/SeedFill.h" #include "imageproc/Morphology.h" #include "imageproc/Grayscale.h" #include "imageproc/SlicedHistogram.h" #include "imageproc/PolygonRasterizer.h" #include "imageproc/MaxWhitespaceFinder.h" #include "imageproc/ConnectivityMap.h" #include "imageproc/InfluenceMap.h" #include "imageproc/SEDM.h" #ifndef Q_MOC_RUN #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "CommandLine.h" namespace select_content { using namespace imageproc; class ContentBoxFinder::Garbage { public: enum Type { HOR, VERT }; Garbage(Type type, BinaryImage const& garbage); void add(BinaryImage const& garbage, QRect const& rect); BinaryImage const& image() const { return m_garbage; } SEDM const& sedm(); private: imageproc::BinaryImage m_garbage; SEDM m_sedm; SEDM::Borders m_sedmBorders; bool m_sedmUpdatePending; }; namespace { struct PreferHorizontal { bool operator()(QRect const& lhs, QRect const& rhs) const { return lhs.width() * lhs.width() * lhs.height() < rhs.width() * rhs.width() * rhs.height(); } }; struct PreferVertical { bool operator()(QRect const& lhs, QRect const& rhs) const { return lhs.width() * lhs.height() * lhs.height() < rhs.width() * rhs.height() * rhs.height(); } }; } // anonymous namespace QRectF ContentBoxFinder::findContentBox( TaskStatus const& status, FilterData const& data, DebugImages* dbg) { ImageTransformation xform_150dpi(data.xform()); xform_150dpi.preScaleToDpi(Dpi(150, 150)); if (xform_150dpi.resultingRect().toRect().isEmpty()) { return QRectF(); } uint8_t const darkest_gray_level = darkestGrayLevel(data.grayImage()); QColor const 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"); } BinaryImage bw150(binarizeWolf(gray150, QSize(51, 51), 50)); if (dbg) { dbg->add(bw150, "bw150"); } 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(); CommandLine const& cli = CommandLine::get(); Despeckle::Level despeckleLevel = Despeckle::NORMAL; if (cli.hasContentRect()) { despeckleLevel = cli.getContentDetection(); } 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); int const 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); int const height_fraction = ws.height() / 5; ws.setTop(ws.top() + height_fraction); ws.setBottom(ws.bottom() - height_fraction); hor_ws_finder.addObstacle(ws); } } { 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); int const width_fraction = ws.width() / 5; ws.setLeft(ws.left() + width_fraction); ws.setRight(ws.right() - width_fraction); vert_ws_finder.addObstacle(ws); } } 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"); } trimContentBlocksInPlace(despeckled, content_blocks); if (dbg) { dbg->add(content_blocks, "more_trimming"); } 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); 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); 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"); } // 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"); } 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; 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 ); 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; } } // Transform back from 150dpi. QTransform combined_xform(xform_150dpi.transform().inverted()); combined_xform *= data.xform().transform(); return combined_xform.map(QRectF(content_rect)).boundingRect(); } 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) {} 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; } if (x > right) { right = x; } if (y < top) { top = y; } if (y > bottom) { bottom = y; } } }; } // anonymous namespace void ContentBoxFinder::trimContentBlocksInPlace( imageproc::BinaryImage const& content, imageproc::BinaryImage& content_blocks) { ConnectivityMap const cmap(content_blocks, CONN4); std::vector bounds(cmap.maxLabel() + 1); int width = content.width(); int height = content.height(); uint32_t const msb = uint32_t(1) << 31; uint32_t const* content_line = content.data(); int const content_stride = content.wordsPerLine(); uint32_t const* cmap_line = cmap.data(); int const cmap_stride = cmap.stride(); for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { uint32_t const 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; } uint32_t* cb_line = content_blocks.data(); int const cb_stride = content_blocks.wordsPerLine(); cmap_line = cmap.data(); for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { uint32_t const 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; } } 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. int const width = content_blocks.width(); int const height = content_blocks.height(); uint16_t const max_spread_dist = std::min(width, height) / 4; std::vector map((width + 2) * (height + 2), ~uint16_t(0)); uint32_t* cb_line = content_blocks.data(); int const cb_stride = content_blocks.wordsPerLine(); uint16_t* map_line = &map[0] + width + 3; int const 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; } 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 x = 0; x < width; ++x) { if (map_line[x] == 0) { map_line[x] = max_spread_dist; queue.push(&map_line[x]); } } if (queue.empty()) { // Common case optimization. return; } while (!queue.empty()) { uint16_t* cell = queue.front(); queue.pop(); assert(*cell != 0); uint16_t const new_dist = *cell - 1; 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); } } cb_line = content_blocks.data(); map_line = &map[0] + width + 3; uint32_t const 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; } } void ContentBoxFinder::segmentGarbage( imageproc::BinaryImage const& 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(); int const width = garbage.width(); int const height = garbage.height(); InfluenceMap::Cell* imap_line = imap.data(); int const imap_stride = imap.stride(); uint32_t* vg_line = vert_garbage.data(); int const vg_stride = vert_garbage.wordsPerLine(); uint32_t* hg_line = hor_garbage.data(); int const hg_stride = hor_garbage.wordsPerLine(); uint32_t const 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; } } 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); rasterOp >(hor_garbage, unconnected_garbage); rasterOp >(vert_garbage, unconnected_garbage); } imageproc::BinaryImage ContentBoxFinder::estimateTextMask( imageproc::BinaryImage const& content, imageproc::BinaryImage const& 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. BinaryImage const 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); int const min_text_height = 6; ConnCompEraserExt eraser(content_blocks, CONN4); for (;;) { ConnComp const 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); SlicedHistogram const hist(content_img, SlicedHistogram::ROWS); SlicedHistogram const block_hist(cc_img, SlicedHistogram::ROWS); assert(hist.size() != 0); typedef std::pair Range; std::vector ranges; std::vector splittable_ranges; splittable_ranges.push_back( Range(&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()) { int const* const first = splittable_ranges.back().first; int const* 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(); int const* best_split_pos = 0; assert(first != last); for (int const* p = first + 1; p != last; ++p) { int const peak1 = max_forward[p - (first + 1)]; int const peak2 = max_backwards[(last - 1) - p]; if (*p * 3.5 > 0.5 * (peak1 + peak2)) { continue; } int const shoulder1 = peak1 - *p; int const shoulder2 = peak2 - *p; if (shoulder1 <= 0 || shoulder2 <= 0) { continue; } if (std::min(shoulder1, shoulder2) * 20 < std::max(shoulder1, shoulder2)) { continue; } int const magnitude = shoulder1 + shoulder2; if (magnitude > best_magnitude) { best_magnitude = magnitude; best_split_pos = p; } } if (best_split_pos) { splittable_ranges.push_back( Range(first, best_split_pos - 1) ); splittable_ranges.push_back( Range(best_split_pos + 1, last) ); } else { ranges.push_back(Range(first, last)); } } BOOST_FOREACH (Range const range, ranges) { int const first = range.first - &hist[0]; int const last = 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) { int const val = hist[i]; weighted_y += val * i; total_weight += val; } if (total_weight == 0) { //qDebug() << "no black pixels at all"; continue; } double const min_fill_factor = 0.22; double const max_fill_factor = 0.65; int const center_y = (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) && abs((center_y - top) - (bottom - center_y)) <= 1) { int const new_top = (top > first) ? top - 1 : top; int const 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. int 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; } QRect ContentBoxFinder::trimLeft( imageproc::BinaryImage const& content, imageproc::BinaryImage const& content_blocks, imageproc::BinaryImage const& text, QRect const& area, Garbage& garbage, DebugImages* const dbg) { SlicedHistogram const 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. } first_ws += area.left(); first_non_ws += area.left(); QRect new_area(area); new_area.setLeft(first_non_ws); if (new_area.isEmpty()) { return area; } QRect removed_area(area); removed_area.setRight(first_ws - 1); if (removed_area.isEmpty()) { return new_area; } bool can_retry_grouped = false; QRect const 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; } QRect ContentBoxFinder::trimRight( imageproc::BinaryImage const& content, imageproc::BinaryImage const& content_blocks, imageproc::BinaryImage const& text, QRect const& area, Garbage& garbage, DebugImages* const dbg) { SlicedHistogram const hist(content_blocks, area, SlicedHistogram::COLS); int start = 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(); 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; } bool can_retry_grouped = false; QRect const 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; } QRect ContentBoxFinder::trimTop( imageproc::BinaryImage const& content, imageproc::BinaryImage const& content_blocks, imageproc::BinaryImage const& text, QRect const& area, Garbage& garbage, DebugImages* const dbg) { SlicedHistogram const 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. } first_ws += area.top(); first_non_ws += area.top(); QRect new_area(area); new_area.setTop(first_non_ws); if (new_area.isEmpty()) { return area; } QRect removed_area(area); removed_area.setBottom(first_ws - 1); if (removed_area.isEmpty()) { return new_area; } bool can_retry_grouped = false; QRect const 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; } QRect ContentBoxFinder::trimBottom( imageproc::BinaryImage const& content, imageproc::BinaryImage const& content_blocks, imageproc::BinaryImage const& text, QRect const& area, Garbage& garbage, DebugImages* const dbg) { SlicedHistogram const hist(content_blocks, area, SlicedHistogram::ROWS); int start = 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(); 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; } bool can_retry_grouped = false; QRect const 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; } QRect ContentBoxFinder::trim( imageproc::BinaryImage const& content, imageproc::BinaryImage const& content_blocks, imageproc::BinaryImage const& text, QRect const& area, QRect const& new_area, QRect const& removed_area, Garbage& garbage, bool& can_retry_grouped, DebugImages* const dbg) { 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) { 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; } int const content_pixels = content.countBlackPixels(removed_area); bool const 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; int const 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 double const min_text_influence = 0.2; double const max_text_influence = 1.0; int const 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) * log((double)num_text_pixels) / 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() ); SEDM const 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; uint32_t const* cb_line = content_blocks.data(); int const cb_stride = content_blocks.wordsPerLine(); uint32_t const msb = uint32_t(1) << 31; uint32_t const* dm_garbage_line = garbage.sedm().data(); uint32_t const* dm_others_line = dm_to_others.data(); int const 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 += sqrt((double)dm_garbage_line[x]); sum_dist_to_others += sqrt((double)dm_others_line[x]); ++count; } } cb_line += cb_stride; dm_garbage_line += dm_stride; dm_others_line += dm_stride; } //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; } } void ContentBoxFinder::filterShadows( TaskStatus const& 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. #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); 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(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); } rasterOp >(shadows, touching_shadows); #else // 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); #endif } /*====================== ContentBoxFinder::Garbage =====================*/ ContentBoxFinder::Garbage::Garbage( Type const type, BinaryImage const& garbage) : m_garbage(garbage), m_sedmBorders( type == VERT ? SEDM::DIST_TO_VERT_BORDERS : SEDM::DIST_TO_HOR_BORDERS ), m_sedmUpdatePending(true) { } void ContentBoxFinder::Garbage::add( BinaryImage const& garbage, QRect const& rect) { rasterOp >( m_garbage, rect, garbage, rect.topLeft() ); m_sedmUpdatePending = true; } SEDM const& ContentBoxFinder::Garbage::sedm() { if (m_sedmUpdatePending) { m_sedm = SEDM(m_garbage, SEDM::DIST_TO_BLACK, m_sedmBorders); } return m_sedm; } } // namespace select_content scantailor-RELEASE_0_9_12_2/filters/select_content/ContentBoxFinder.h000066400000000000000000000061421271170121200255270ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SELECT_CONTENT_CONTENTBOXFINDER_H_ #define SELECT_CONTENT_CONTENTBOXFINDER_H_ #include "imageproc/BinaryThreshold.h" class TaskStatus; class DebugImages; class FilterData; class QImage; class QRect; class QRectF; namespace imageproc { class BinaryImage; class ConnComp; class SEDM; } namespace select_content { class ContentBoxFinder { public: static QRectF findContentBox( TaskStatus const& status, FilterData const& data, DebugImages* dbg = 0); private: class Garbage; static void segmentGarbage( imageproc::BinaryImage const& garbage, imageproc::BinaryImage& hor_garbage, imageproc::BinaryImage& vert_garbage, DebugImages* dbg); static void trimContentBlocksInPlace( imageproc::BinaryImage const& content, imageproc::BinaryImage& content_blocks); static void inPlaceRemoveAreasTouchingBorders( imageproc::BinaryImage& content_blocks, DebugImages* dbg); static imageproc::BinaryImage estimateTextMask( imageproc::BinaryImage const& content, imageproc::BinaryImage const& content_blocks, DebugImages* dbg); static void filterShadows( TaskStatus const& status, imageproc::BinaryImage& shadows, DebugImages* dbg); static QRect trimLeft( imageproc::BinaryImage const& content, imageproc::BinaryImage const& content_blocks, imageproc::BinaryImage const& text_mask, QRect const& area, Garbage& garbage, DebugImages* dbg); static QRect trimRight( imageproc::BinaryImage const& content, imageproc::BinaryImage const& content_blocks, imageproc::BinaryImage const& text_mask, QRect const& area, Garbage& garbage, DebugImages* dbg); static QRect trimTop( imageproc::BinaryImage const& content, imageproc::BinaryImage const& content_blocks, imageproc::BinaryImage const& text_mask, QRect const& area, Garbage& garbage, DebugImages* dbg); static QRect trimBottom( imageproc::BinaryImage const& content, imageproc::BinaryImage const& content_blocks, imageproc::BinaryImage const& text_mask, QRect const& area, Garbage& garbage, DebugImages* dbg); static QRect trim( imageproc::BinaryImage const& content, imageproc::BinaryImage const& content_blocks, imageproc::BinaryImage const& text_mask, QRect const& area, QRect const& new_area, QRect const& removed_area, Garbage& garbage, bool& can_retry_grouped, DebugImages* dbg); }; } // namespace select_content #endif scantailor-RELEASE_0_9_12_2/filters/select_content/Dependencies.cpp000066400000000000000000000034541271170121200252400ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Dependencies.h" #include "XmlMarshaller.h" #include "XmlUnmarshaller.h" #include "imageproc/PolygonUtils.h" #include #include using namespace imageproc; namespace select_content { Dependencies::Dependencies() { } Dependencies::Dependencies(QPolygonF const& rotated_page_outline) : m_rotatedPageOutline(rotated_page_outline) { } Dependencies::Dependencies(QDomElement const& deps_el) : m_rotatedPageOutline( XmlUnmarshaller::polygonF( deps_el.namedItem("rotated-page-outline").toElement() ) ) { } Dependencies::~Dependencies() { } bool Dependencies::matches(Dependencies const& other) const { return PolygonUtils::fuzzyCompare( m_rotatedPageOutline, other.m_rotatedPageOutline ); } QDomElement Dependencies::toXml(QDomDocument& doc, QString const& name) const { XmlMarshaller marshaller(doc); QDomElement el(doc.createElement(name)); el.appendChild( marshaller.polygonF( m_rotatedPageOutline, "rotated-page-outline" ) ); return el; } } // namespace select_content scantailor-RELEASE_0_9_12_2/filters/select_content/Dependencies.h000066400000000000000000000030561271170121200247030ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SELECT_CONTENT_DEPENDENCIES_H_ #define SELECT_CONTENT_DEPENDENCIES_H_ #include class QDomDocument; class QDomElement; class QString; namespace select_content { /** * \brief Dependencies of the content box. * * Once dependencies change, the content box is no longer valid. */ class Dependencies { public: // Member-wise copying is OK. Dependencies(); Dependencies(QPolygonF const& rotated_page_outline); Dependencies(QDomElement const& deps_el); ~Dependencies(); QPolygonF const& rotatedPageOutline() const { return m_rotatedPageOutline; } bool matches(Dependencies const& other) const; QDomElement toXml(QDomDocument& doc, QString const& name) const; private: QPolygonF m_rotatedPageOutline; }; } // namespace select_content #endif scantailor-RELEASE_0_9_12_2/filters/select_content/Filter.cpp000066400000000000000000000117251271170121200240770ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Filter.h" #include "FilterUiInterface.h" #include "OptionsWidget.h" #include "Task.h" #include "PageId.h" #include "Settings.h" #include "Params.h" #include "ProjectReader.h" #include "ProjectWriter.h" #include "CacheDrivenTask.h" #include "OrderByWidthProvider.h" #include "OrderByHeightProvider.h" #ifndef Q_MOC_RUN #include #include #endif #include #include #include #include #include #include "CommandLine.h" namespace select_content { Filter::Filter( PageSelectionAccessor const& 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; ProviderPtr const default_order; ProviderPtr const order_by_width(new OrderByWidthProvider(m_ptrSettings)); ProviderPtr const order_by_height(new OrderByHeightProvider(m_ptrSettings)); m_pageOrderOptions.push_back(PageOrderOption(tr("Natural order"), default_order)); m_pageOrderOptions.push_back(PageOrderOption(tr("Order by increasing width"), order_by_width)); m_pageOrderOptions.push_back(PageOrderOption(tr("Order by increasing height"), order_by_height)); } Filter::~Filter() { } QString Filter::getName() const { return tr("Select Content"); } PageView Filter::getView() const { return PAGE_VIEW; } int Filter::selectedPageOrder() const { return m_selectedPageOrder; } void Filter::selectPageOrder(int option) { assert((unsigned)option < m_pageOrderOptions.size()); m_selectedPageOrder = option; } std::vector Filter::pageOrderOptions() const { return m_pageOrderOptions; } void Filter::performRelinking(AbstractRelinker const& relinker) { m_ptrSettings->performRelinking(relinker); } void Filter::preUpdateUI(FilterUiInterface* ui, PageId const& page_id) { m_ptrOptionsWidget->preUpdateUI(page_id); ui->setOptionsWidget(m_ptrOptionsWidget.get(), ui->KEEP_OWNERSHIP); } QDomElement Filter::saveSettings( ProjectWriter const& writer, QDomDocument& doc) const { using namespace boost::lambda; QDomElement filter_el(doc.createElement("select-content")); writer.enumPages( boost::lambda::bind( &Filter::writePageSettings, this, boost::ref(doc), var(filter_el), boost::lambda::_1, boost::lambda::_2 ) ); return filter_el; } void Filter::writePageSettings( QDomDocument& doc, QDomElement& filter_el, PageId const& page_id, int numeric_id) const { std::auto_ptr const params(m_ptrSettings->getPageParams(page_id)); if (!params.get()) { return; } QDomElement page_el(doc.createElement("page")); page_el.setAttribute("id", numeric_id); page_el.appendChild(params->toXml(doc, "params")); filter_el.appendChild(page_el); } void Filter::loadSettings(ProjectReader const& reader, QDomElement const& filters_el) { m_ptrSettings->clear(); QDomElement const filter_el( filters_el.namedItem("select-content").toElement() ); QString const 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; } QDomElement const el(node.toElement()); bool ok = true; int const id = el.attribute("id").toInt(&ok); if (!ok) { continue; } PageId const page_id(reader.pageId(id)); if (page_id.isNull()) { continue; } QDomElement const params_el(el.namedItem("params").toElement()); if (params_el.isNull()) { continue; } Params const params(params_el); m_ptrSettings->setPageParams(page_id, params); } } IntrusivePtr Filter::createTask( PageId const& page_id, IntrusivePtr const& next_task, bool batch, bool debug) { return IntrusivePtr( new Task( IntrusivePtr(this), next_task, m_ptrSettings, page_id, batch, debug ) ); } IntrusivePtr Filter::createCacheDrivenTask( IntrusivePtr const& next_task) { return IntrusivePtr( new CacheDrivenTask(m_ptrSettings, next_task) ); } } // namespace select_content scantailor-RELEASE_0_9_12_2/filters/select_content/Filter.h000066400000000000000000000054121271170121200235400ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SELECT_CONTENT_FILTER_H_ #define SELECT_CONTENT_FILTER_H_ #include "NonCopyable.h" #include "AbstractFilter.h" #include "PageView.h" #include "IntrusivePtr.h" #include "FilterResult.h" #include "SafeDeletingQObjectPtr.h" #include "PageOrderOption.h" #include #include class PageId; class PageSelectionAccessor; class QString; namespace page_layout { class Task; class CacheDrivenTask; } namespace select_content { class OptionsWidget; class Task; class CacheDrivenTask; class Settings; class Filter : public AbstractFilter { DECLARE_NON_COPYABLE(Filter) Q_DECLARE_TR_FUNCTIONS(select_content::Filter) public: Filter(PageSelectionAccessor const& page_selection_accessor); virtual ~Filter(); virtual QString getName() const; virtual PageView getView() const; virtual int selectedPageOrder() const; virtual void selectPageOrder(int option); virtual std::vector pageOrderOptions() const; virtual void performRelinking(AbstractRelinker const& relinker); virtual void preUpdateUI(FilterUiInterface* ui, PageId const& page_id); virtual QDomElement saveSettings( ProjectWriter const& writer, QDomDocument& doc) const; virtual void loadSettings( ProjectReader const& reader, QDomElement const& filters_el); IntrusivePtr createTask( PageId const& page_id, IntrusivePtr const& next_task, bool batch, bool debug); IntrusivePtr createCacheDrivenTask( IntrusivePtr const& next_task); OptionsWidget* optionsWidget() { return m_ptrOptionsWidget.get(); }; Settings* getSettings() { return m_ptrSettings.get(); }; private: void writePageSettings( QDomDocument& doc, QDomElement& filter_el, PageId const& page_id, int numeric_id) const; IntrusivePtr m_ptrSettings; SafeDeletingQObjectPtr m_ptrOptionsWidget; std::vector m_pageOrderOptions; int m_selectedPageOrder; }; } // namespace select_content #endif scantailor-RELEASE_0_9_12_2/filters/select_content/ImageView.cpp000066400000000000000000000175051271170121200245310ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ImageView.h" #include "ImageView.h.moc" #include "ImageTransformation.h" #include "ImagePresentation.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifndef Q_MOC_RUN #include #endif #include namespace select_content { ImageView::ImageView( QImage const& image, QImage const& downscaled_image, ImageTransformation const& xform, QRectF const& content_rect) : 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_minBoxSize(10.0, 10.0) { setMouseTracking(true); interactionState().setDefaultStatusTip( tr("Use the context menu to enable / disable the content box.") ); QString const drag_tip(tr("Drag lines or corners to resize the content box.")); // Setup corner drag handlers. static int const masks_by_corner[] = { TOP|LEFT, TOP|RIGHT, BOTTOM|RIGHT, BOTTOM|LEFT }; for (int i = 0; i < 4; ++i) { m_corners[i].setPositionCallback( boost::bind(&ImageView::cornerPosition, this, masks_by_corner[i]) ); m_corners[i].setMoveRequestCallback( boost::bind(&ImageView::cornerMoveRequest, this, masks_by_corner[i], _1) ); m_corners[i].setDragFinishedCallback( boost::bind(&ImageView::dragFinished, this) ); m_cornerHandlers[i].setObject(&m_corners[i]); m_cornerHandlers[i].setProximityStatusTip(drag_tip); Qt::CursorShape cursor = (i & 1) ? Qt::SizeBDiagCursor : Qt::SizeFDiagCursor; m_cornerHandlers[i].setProximityCursor(cursor); m_cornerHandlers[i].setInteractionCursor(cursor); makeLastFollower(m_cornerHandlers[i]); } // Setup edge drag handlers. static int const masks_by_edge[] = { TOP, RIGHT, BOTTOM, LEFT }; for (int i = 0; i < 4; ++i) { m_edges[i].setPositionCallback( boost::bind(&ImageView::edgePosition, this, masks_by_edge[i]) ); m_edges[i].setMoveRequestCallback( boost::bind(&ImageView::edgeMoveRequest, this, masks_by_edge[i], _1) ); m_edges[i].setDragFinishedCallback( boost::bind(&ImageView::dragFinished, this) ); m_edgeHandlers[i].setObject(&m_edges[i]); m_edgeHandlers[i].setProximityStatusTip(drag_tip); Qt::CursorShape cursor = (i & 1) ? Qt::SizeHorCursor : Qt::SizeVerCursor; m_edgeHandlers[i].setProximityCursor(cursor); m_edgeHandlers[i].setInteractionCursor(cursor); makeLastFollower(m_edgeHandlers[i]); } 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")); connect(create, SIGNAL(triggered(bool)), this, SLOT(createContentBox())); connect(remove, SIGNAL(triggered(bool)), this, SLOT(removeContentBox())); } ImageView::~ImageView() { } void ImageView::createContentBox() { if (!m_contentRect.isEmpty()) { return; } if (interactionState().captured()) { return; } QRectF const 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); } void ImageView::removeContentBox() { if (m_contentRect.isEmpty()) { return; } if (interactionState().captured()) { return; } m_contentRect = QRectF(); update(); emit manualContentRectSet(m_contentRect); } void ImageView::onPaint(QPainter& painter, InteractionState const& interaction) { if (m_contentRect.isNull()) { return; } painter.setRenderHints(QPainter::Antialiasing, true); // Draw the content bounding box. QPen pen(QColor(0x00, 0x00, 0xff)); pen.setWidth(1); pen.setCosmetic(true); painter.setPen(pen); painter.setBrush(QColor(0x00, 0x00, 0xff, 50)); // Pen strokes will be outside of m_contentRect - that's how drawRect() works. painter.drawRect(m_contentRect); } 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()); } } QPointF ImageView::cornerPosition(int edge_mask) const { QRectF const r(virtualToWidget().mapRect(m_contentRect)); 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 pt; } void ImageView::cornerMoveRequest(int edge_mask, QPointF const& pos) { QRectF r(virtualToWidget().mapRect(m_contentRect)); qreal const minw = m_minBoxSize.width(); qreal const 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)); } forceInsideImage(r, edge_mask); m_contentRect = widgetToVirtual().mapRect(r); update(); } QLineF ImageView::edgePosition(int const edge) const { QRectF const r(virtualToWidget().mapRect(m_contentRect)); if (edge == TOP) { return QLineF(r.topLeft(), r.topRight()); } else if (edge == BOTTOM) { return QLineF(r.bottomLeft(), r.bottomRight()); } else if (edge == LEFT) { return QLineF(r.topLeft(), r.bottomLeft()); } else { return QLineF(r.topRight(), r.bottomRight()); } } void ImageView::edgeMoveRequest(int const edge, QLineF const& line) { cornerMoveRequest(edge, line.p1()); } void ImageView::dragFinished() { emit manualContentRectSet(m_contentRect); } void ImageView::forceInsideImage(QRectF& widget_rect, int const edge_mask) const { qreal const minw = m_minBoxSize.width(); qreal const minh = m_minBoxSize.height(); QRectF const image_rect(getOccupiedWidgetRect()); 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)); } } } // namespace select_content scantailor-RELEASE_0_9_12_2/filters/select_content/ImageView.h000066400000000000000000000051721271170121200241730ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #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 class ImageTransformation; class QMenu; namespace select_content { class ImageView : public ImageViewBase, private InteractionHandler { Q_OBJECT public: /** * \p content_rect is in virtual image coordinates. */ ImageView( QImage const& image, QImage const& downscaled_image, ImageTransformation const& xform, QRectF const& content_rect); virtual ~ImageView(); signals: void manualContentRectSet(QRectF const& content_rect); private slots: void createContentBox(); void removeContentBox(); private: enum Edge { LEFT = 1, RIGHT = 2, TOP = 4, BOTTOM = 8 }; virtual void onPaint(QPainter& painter, InteractionState const& interaction); void onContextMenuEvent(QContextMenuEvent* event, InteractionState& interaction); QPointF cornerPosition(int edge_mask) const; void cornerMoveRequest(int edge_mask, QPointF const& pos); QLineF edgePosition(int edge) const; void edgeMoveRequest(int edge, QLineF const& line); void dragFinished(); void forceInsideImage(QRectF& widget_rect, int edge_mask) const; DraggablePoint m_corners[4]; ObjectDragHandler m_cornerHandlers[4]; DraggableLineSegment m_edges[4]; ObjectDragHandler m_edgeHandlers[4]; 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 exists a content box. */ QMenu* m_pHaveContentMenu; /** * Content box in virtual image coordinates. */ QRectF m_contentRect; QSizeF m_minBoxSize; }; } // namespace select_content #endif scantailor-RELEASE_0_9_12_2/filters/select_content/OptionsWidget.cpp000066400000000000000000000105551271170121200254510ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "OptionsWidget.h" #include "OptionsWidget.h.moc" #include "ApplyDialog.h" #include "Settings.h" #include "Params.h" #include "ScopedIncDec.h" #ifndef Q_MOC_RUN #include #endif namespace select_content { OptionsWidget::OptionsWidget( IntrusivePtr const& settings, PageSelectionAccessor const& page_selection_accessor) : m_ptrSettings(settings), m_pageSelectionAccessor(page_selection_accessor), m_ignoreAutoManualToggle(0) { setupUi(this); connect(autoBtn, SIGNAL(toggled(bool)), this, SLOT(modeChanged(bool))); connect(applyToBtn, SIGNAL(clicked()), this, SLOT(showApplyToDialog())); } OptionsWidget::~OptionsWidget() { } void OptionsWidget::preUpdateUI(PageId const& page_id) { ScopedIncDec guard(m_ignoreAutoManualToggle); m_pageId = page_id; autoBtn->setChecked(true); autoBtn->setEnabled(false); manualBtn->setEnabled(false); } void OptionsWidget::postUpdateUI(UiData const& ui_data) { m_uiData = ui_data; updateModeIndication(ui_data.mode()); autoBtn->setEnabled(true); manualBtn->setEnabled(true); } void OptionsWidget::manualContentRectSet(QRectF const& content_rect) { m_uiData.setContentRect(content_rect); m_uiData.setMode(MODE_MANUAL); updateModeIndication(MODE_MANUAL); commitCurrentParams(); emit invalidateThumbnail(m_pageId); } void OptionsWidget::modeChanged(bool const 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(); } } void OptionsWidget::updateModeIndication(AutoManualMode const mode) { ScopedIncDec guard(m_ignoreAutoManualToggle); if (mode == MODE_AUTO) { autoBtn->setChecked(true); } else { manualBtn->setChecked(true); } } void OptionsWidget::commitCurrentParams() { Params const params( m_uiData.contentRect(), m_uiData.contentSizeMM(), m_uiData.dependencies(), m_uiData.mode() ); m_ptrSettings->setPageParams(m_pageId, params); } void OptionsWidget::showApplyToDialog() { ApplyDialog* dialog = new ApplyDialog( this, m_pageId, m_pageSelectionAccessor ); dialog->setAttribute(Qt::WA_DeleteOnClose); connect( dialog, SIGNAL(applySelection(std::set const&)), this, SLOT(applySelection(std::set const&)) ); dialog->show(); } void OptionsWidget::applySelection(std::set const& pages) { if (pages.empty()) { return; } Params const params( m_uiData.contentRect(), m_uiData.contentSizeMM(), m_uiData.dependencies(), m_uiData.mode() ); BOOST_FOREACH(PageId const& page_id, pages) { m_ptrSettings->setPageParams(page_id, params); emit invalidateThumbnail(page_id); } } /*========================= OptionsWidget::UiData ======================*/ OptionsWidget::UiData::UiData() : m_mode(MODE_AUTO) { } OptionsWidget::UiData::~UiData() { } void OptionsWidget::UiData::setSizeCalc(PhysSizeCalc const& calc) { m_sizeCalc = calc; } void OptionsWidget::UiData::setContentRect(QRectF const& content_rect) { m_contentRect = content_rect; } QRectF const& OptionsWidget::UiData::contentRect() const { return m_contentRect; } QSizeF OptionsWidget::UiData::contentSizeMM() const { return m_sizeCalc.sizeMM(m_contentRect); } void OptionsWidget::UiData::setDependencies(Dependencies const& deps) { m_deps = deps; } Dependencies const& OptionsWidget::UiData::dependencies() const { return m_deps; } void OptionsWidget::UiData::setMode(AutoManualMode const mode) { m_mode = mode; } AutoManualMode OptionsWidget::UiData::mode() const { return m_mode; } } // namespace select_content scantailor-RELEASE_0_9_12_2/filters/select_content/OptionsWidget.h000066400000000000000000000051361271170121200251150ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SELECT_CONTENT_OPTIONSWIDGET_H_ #define SELECT_CONTENT_OPTIONSWIDGET_H_ #include "ui_SelectContentOptionsWidget.h" #include "FilterOptionsWidget.h" #include "IntrusivePtr.h" #include "AutoManualMode.h" #include "Dependencies.h" #include "PhysSizeCalc.h" #include "PageId.h" #include "PageSelectionAccessor.h" #include "Params.h" #include #include #include namespace select_content { class Settings; class OptionsWidget : public FilterOptionsWidget, private Ui::SelectContentOptionsWidget { Q_OBJECT public: class UiData { // Member-wise copying is OK. public: UiData(); ~UiData(); void setSizeCalc(PhysSizeCalc const& calc); void setContentRect(QRectF const& content_rect); QRectF const& contentRect() const; QSizeF contentSizeMM() const; void setDependencies(Dependencies const& deps); Dependencies const& dependencies() const; void setMode(AutoManualMode mode); AutoManualMode mode() const; private: QRectF m_contentRect; // In virtual image coordinates. PhysSizeCalc m_sizeCalc; Dependencies m_deps; AutoManualMode m_mode; }; OptionsWidget(IntrusivePtr const& settings, PageSelectionAccessor const& page_selection_accessor); virtual ~OptionsWidget(); void preUpdateUI(PageId const& page_id); void postUpdateUI(UiData const& ui_data); public slots: void manualContentRectSet(QRectF const& content_rect); private slots: void showApplyToDialog(); void applySelection(std::set const& pages); void modeChanged(bool auto_mode); private: void updateModeIndication(AutoManualMode const mode); void commitCurrentParams(); IntrusivePtr m_ptrSettings; UiData m_uiData; PageSelectionAccessor m_pageSelectionAccessor; PageId m_pageId; int m_ignoreAutoManualToggle; }; } // namespace select_content #endif scantailor-RELEASE_0_9_12_2/filters/select_content/OrderByHeightProvider.cpp000066400000000000000000000034521271170121200270620ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "OrderByHeightProvider.h" #include "Params.h" #include #include namespace select_content { OrderByHeightProvider::OrderByHeightProvider(IntrusivePtr const& settings) : m_ptrSettings(settings) { } bool OrderByHeightProvider::precedes( PageId const& lhs_page, bool const lhs_incomplete, PageId const& rhs_page, bool const rhs_incomplete) const { std::auto_ptr const lhs_params(m_ptrSettings->getPageParams(lhs_page)); std::auto_ptr const rhs_params(m_ptrSettings->getPageParams(rhs_page)); QSizeF lhs_size; if (lhs_params.get()) { lhs_size = lhs_params->contentSizeMM(); } QSizeF rhs_size; if (rhs_params.get()) { rhs_size = rhs_params->contentSizeMM(); } bool const lhs_valid = !lhs_incomplete && lhs_size.isValid(); bool const 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 scantailor-RELEASE_0_9_12_2/filters/select_content/OrderByHeightProvider.h000066400000000000000000000025131271170121200265240ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SELECT_CONTENT_ORDER_BY_HEIGHT_PROVIDER_H_ #define SELECT_CONTENT_ORDER_BY_HEIGHT_PROVIDER_H_ #include "Settings.h" #include "IntrusivePtr.h" #include "PageOrderProvider.h" namespace select_content { class OrderByHeightProvider : public PageOrderProvider { public: OrderByHeightProvider(IntrusivePtr const& settings); virtual bool precedes( PageId const& lhs_page, bool lhs_incomplete, PageId const& rhs_page, bool rhs_incomplete) const; private: IntrusivePtr m_ptrSettings; }; } // namespace select_content #endif scantailor-RELEASE_0_9_12_2/filters/select_content/OrderByWidthProvider.cpp000066400000000000000000000034441271170121200267320ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "OrderByWidthProvider.h" #include "Params.h" #include #include namespace select_content { OrderByWidthProvider::OrderByWidthProvider(IntrusivePtr const& settings) : m_ptrSettings(settings) { } bool OrderByWidthProvider::precedes( PageId const& lhs_page, bool const lhs_incomplete, PageId const& rhs_page, bool const rhs_incomplete) const { std::auto_ptr const lhs_params(m_ptrSettings->getPageParams(lhs_page)); std::auto_ptr const rhs_params(m_ptrSettings->getPageParams(rhs_page)); QSizeF lhs_size; if (lhs_params.get()) { lhs_size = lhs_params->contentSizeMM(); } QSizeF rhs_size; if (rhs_params.get()) { rhs_size = rhs_params->contentSizeMM(); } bool const lhs_valid = !lhs_incomplete && lhs_size.isValid(); bool const 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 scantailor-RELEASE_0_9_12_2/filters/select_content/OrderByWidthProvider.h000066400000000000000000000025071271170121200263760ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SELECT_CONTENT_ORDER_BY_WIDTH_PROVIDER_H_ #define SELECT_CONTENT_ORDER_BY_WIDTH_PROVIDER_H_ #include "Settings.h" #include "IntrusivePtr.h" #include "PageOrderProvider.h" namespace select_content { class OrderByWidthProvider : public PageOrderProvider { public: OrderByWidthProvider(IntrusivePtr const& settings); virtual bool precedes( PageId const& lhs_page, bool lhs_incomplete, PageId const& rhs_page, bool rhs_incomplete) const; private: IntrusivePtr m_ptrSettings; }; } // namespace select_content #endif scantailor-RELEASE_0_9_12_2/filters/select_content/Params.cpp000066400000000000000000000040711271170121200240710ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Params.h" #include "XmlMarshaller.h" #include "XmlUnmarshaller.h" #include #include namespace select_content { Params::Params( QRectF const& content_rect, QSizeF const& content_size_mm, Dependencies const& deps, AutoManualMode const mode) : m_contentRect(content_rect), m_contentSizeMM(content_size_mm), m_deps(deps), m_mode(mode) { } Params::Params(Dependencies const& deps) : m_deps(deps) { } Params::Params(QDomElement const& filter_el) : m_contentRect( XmlUnmarshaller::rectF( filter_el.namedItem("content-rect").toElement() ) ), m_contentSizeMM( XmlUnmarshaller::sizeF( filter_el.namedItem("content-size-mm").toElement() ) ), m_deps(filter_el.namedItem("dependencies").toElement()), m_mode(filter_el.attribute("mode") == "manual" ? MODE_MANUAL : MODE_AUTO) { } Params::~Params() { } QDomElement Params::toXml(QDomDocument& doc, QString const& name) const { XmlMarshaller marshaller(doc); QDomElement el(doc.createElement(name)); el.setAttribute("mode", m_mode == MODE_AUTO ? "auto" : "manual"); el.appendChild(marshaller.rectF(m_contentRect, "content-rect")); el.appendChild(marshaller.sizeF(m_contentSizeMM, "content-size-mm")); el.appendChild(m_deps.toXml(doc, "dependencies")); return el; } } // namespace content_rect scantailor-RELEASE_0_9_12_2/filters/select_content/Params.h000066400000000000000000000033201271170121200235320ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SELECT_CONTENT_PARAMS_H_ #define SELECT_CONTENT_PARAMS_H_ #include "Dependencies.h" #include "AutoManualMode.h" #include #include class QDomDocument; class QDomElement; class QString; namespace select_content { class Params { public: // Member-wise copying is OK. Params(QRectF const& rect, QSizeF const& size_mm, Dependencies const& deps, AutoManualMode mode); Params(Dependencies const& deps); Params(QDomElement const& filter_el); ~Params(); QRectF const& contentRect() const { return m_contentRect; } QSizeF const& contentSizeMM() const { return m_contentSizeMM; } Dependencies const& dependencies() const { return m_deps; } AutoManualMode mode() const { return m_mode; } QDomElement toXml(QDomDocument& doc, QString const& name) const; private: QRectF m_contentRect; QSizeF m_contentSizeMM; Dependencies m_deps; AutoManualMode m_mode; }; } // namespace select_content #endif scantailor-RELEASE_0_9_12_2/filters/select_content/PhysContentSizeCalc.cpp000066400000000000000000000026251271170121200265450ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "PhysSizeCalc.h" #include "ImageTransformation.h" #include "PhysicalTransformation.h" #include #include namespace select_content { PhysSizeCalc::PhysSizeCalc() { } PhysSizeCalc::PhysSizeCalc(ImageTransformation const& xform) : m_virtToPhys(xform.transformBack() * PhysicalTransformation(xform.origDpi()).pixelsToMM()) { } QSizeF PhysSizeCalc::sizeMM(QRectF const& rect_px) const { QPolygonF const poly_mm(m_virtToPhys.map(rect_px)); QSizeF const size_mm( QLineF(poly_mm[0], poly_mm[1]).length(), QLineF(poly_mm[1], poly_mm[2]).length() ); return size_mm; } } // namespace select_content scantailor-RELEASE_0_9_12_2/filters/select_content/PhysSizeCalc.cpp000066400000000000000000000026031271170121200252060ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "PhysSizeCalc.h" #include "ImageTransformation.h" #include "PhysicalTransformation.h" #include namespace select_content { PhysSizeCalc::PhysSizeCalc() { } PhysSizeCalc::PhysSizeCalc(ImageTransformation const& xform) : m_virtToPhys(xform.transformBack() * PhysicalTransformation(xform.origDpi()).pixelsToMM()) { } QSizeF PhysSizeCalc::sizeMM(QRectF const& rect_px) const { QPolygonF const poly_mm(m_virtToPhys.map(rect_px)); QSizeF const size_mm( QLineF(poly_mm[0], poly_mm[1]).length(), QLineF(poly_mm[1], poly_mm[2]).length() ); return size_mm; } } // namespace select_content scantailor-RELEASE_0_9_12_2/filters/select_content/PhysSizeCalc.h000066400000000000000000000023501271170121200246520ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SELECT_CONTENT_PHYS_SIZE_CALC_H_ #define SELECT_CONTENT_PHYS_SIZE_CALC_H_ #include #include #include class ImageTransformation; namespace select_content { class PhysSizeCalc { // Member-wise copying is OK. public: PhysSizeCalc(); explicit PhysSizeCalc(ImageTransformation const& xform); QSizeF sizeMM(QRectF const& rect_px) const; private: QTransform m_virtToPhys; }; } // namespace select_content #endif scantailor-RELEASE_0_9_12_2/filters/select_content/Settings.cpp000066400000000000000000000043301271170121200244440ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Settings.h" #include "Utils.h" #include "RelinkablePath.h" #include "AbstractRelinker.h" #include #ifndef Q_MOC_RUN #include #endif namespace select_content { Settings::Settings() { } Settings::~Settings() { } void Settings::clear() { QMutexLocker locker(&m_mutex); m_pageParams.clear(); } void Settings::performRelinking(AbstractRelinker const& relinker) { QMutexLocker locker(&m_mutex); PageParams new_params; BOOST_FOREACH(PageParams::value_type const& kv, m_pageParams) { RelinkablePath const 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); } void Settings::setPageParams(PageId const& page_id, Params const& params) { QMutexLocker locker(&m_mutex); Utils::mapSetValue(m_pageParams, page_id, params); } void Settings::clearPageParams(PageId const& page_id) { QMutexLocker locker(&m_mutex); m_pageParams.erase(page_id); } std::auto_ptr Settings::getPageParams(PageId const& page_id) const { QMutexLocker locker(&m_mutex); PageParams::const_iterator const it(m_pageParams.find(page_id)); if (it != m_pageParams.end()) { return std::auto_ptr(new Params(it->second)); } else { return std::auto_ptr(); } } } // namespace select_content scantailor-RELEASE_0_9_12_2/filters/select_content/Settings.h000066400000000000000000000030711271170121200241120ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SELECT_CONTENT_SETTINGS_H_ #define SELECT_CONTENT_SETTINGS_H_ #include "RefCountable.h" #include "NonCopyable.h" #include "PageId.h" #include "Params.h" #include #include #include class AbstractRelinker; namespace select_content { class Settings : public RefCountable { DECLARE_NON_COPYABLE(Settings) public: Settings(); virtual ~Settings(); void clear(); void performRelinking(AbstractRelinker const& relinker); void setPageParams(PageId const& page_id, Params const& params); void clearPageParams(PageId const& page_id); std::auto_ptr getPageParams(PageId const& page_id) const; private: typedef std::map PageParams; mutable QMutex m_mutex; PageParams m_pageParams; }; } // namespace select_content #endif scantailor-RELEASE_0_9_12_2/filters/select_content/Task.cpp000066400000000000000000000127131271170121200235520ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Task.h" #include "Filter.h" #include "FilterData.h" #include "DebugImages.h" #include "OptionsWidget.h" #include "AutoManualMode.h" #include "Dependencies.h" #include "Params.h" #include "Settings.h" #include "TaskStatus.h" #include "ContentBoxFinder.h" #include "FilterUiInterface.h" #include "ImageView.h" #include "OrthogonalRotation.h" #include "ImageTransformation.h" #include "PhysSizeCalc.h" #include "filters/page_layout/Task.h" #include #include #include namespace select_content { class Task::UiUpdater : public FilterResult { public: UiUpdater(IntrusivePtr const& filter, PageId const& page_id, std::auto_ptr dbg, QImage const& image, ImageTransformation const& xform, OptionsWidget::UiData const& ui_data, bool batch); virtual void updateUI(FilterUiInterface* ui); virtual IntrusivePtr filter() { return m_ptrFilter; } private: IntrusivePtr m_ptrFilter; PageId m_pageId; std::auto_ptr m_ptrDbg; QImage m_image; QImage m_downscaledImage; ImageTransformation m_xform; OptionsWidget::UiData m_uiData; bool m_batchProcessing; }; Task::Task(IntrusivePtr const& filter, IntrusivePtr const& next_task, IntrusivePtr const& settings, PageId const& page_id, bool const batch, bool const debug) : m_ptrFilter(filter), m_ptrNextTask(next_task), m_ptrSettings(settings), m_pageId(page_id), m_batchProcessing(batch) { if (debug) { m_ptrDbg.reset(new DebugImages); } } Task::~Task() { } FilterResultPtr Task::process(TaskStatus const& status, FilterData const& data) { status.throwIfCancelled(); Dependencies const deps(data.xform().resultingPreCropArea()); std::auto_ptr params(m_ptrSettings->getPageParams(m_pageId)); if (params.get() && !params->dependencies().matches(deps) && (params->mode() == MODE_AUTO)) { params.reset(); } OptionsWidget::UiData ui_data; ui_data.setSizeCalc(PhysSizeCalc(data.xform())); if (params.get()) { ui_data.setContentRect(params->contentRect()); ui_data.setDependencies(deps); ui_data.setMode(params->mode()); if (!params->dependencies().matches(deps)) { QRectF content_rect = ui_data.contentRect(); QPointF new_center= deps.rotatedPageOutline().boundingRect().center(); QPointF old_center = params->dependencies().rotatedPageOutline().boundingRect().center(); content_rect.translate(new_center - old_center); ui_data.setContentRect(content_rect); } if ((params->contentSizeMM().isEmpty() && !params->contentRect().isEmpty()) || !params->dependencies().matches(deps)) { // Backwards compatibilty: put the missing data where it belongs. Params const new_params( ui_data.contentRect(), ui_data.contentSizeMM(), deps, params->mode() ); m_ptrSettings->setPageParams(m_pageId, new_params); } } else { QRectF const content_rect( ContentBoxFinder::findContentBox( status, data, m_ptrDbg.get() ) ); ui_data.setContentRect(content_rect); ui_data.setDependencies(deps); ui_data.setMode(MODE_AUTO); Params const new_params( ui_data.contentRect(), ui_data.contentSizeMM(), deps, MODE_AUTO ); m_ptrSettings->setPageParams(m_pageId, new_params); } status.throwIfCancelled(); if (m_ptrNextTask) { return m_ptrNextTask->process( status, FilterData(data, data.xform()), ui_data.contentRect() ); } else { return FilterResultPtr( new UiUpdater( m_ptrFilter, m_pageId, m_ptrDbg, data.origImage(), data.xform(), ui_data, m_batchProcessing ) ); } } /*============================ Task::UiUpdater ==========================*/ Task::UiUpdater::UiUpdater( IntrusivePtr const& filter, PageId const& page_id, std::auto_ptr dbg, QImage const& image, ImageTransformation const& xform, OptionsWidget::UiData const& ui_data, bool const batch) : m_ptrFilter(filter), m_pageId(page_id), m_ptrDbg(dbg), m_image(image), m_downscaledImage(ImageView::createDownscaledImage(image)), m_xform(xform), 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; } ImageView* view = new ImageView( m_image, m_downscaledImage, m_xform, m_uiData.contentRect() ); ui->setImageWidget(view, ui->TRANSFER_OWNERSHIP, m_ptrDbg.get()); QObject::connect( view, SIGNAL(manualContentRectSet(QRectF const&)), opt_widget, SLOT(manualContentRectSet(QRectF const&)) ); } } // namespace select_content scantailor-RELEASE_0_9_12_2/filters/select_content/Task.h000066400000000000000000000034371271170121200232220ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SELECT_CONTENT_TASK_H_ #define SELECT_CONTENT_TASK_H_ #include "NonCopyable.h" #include "RefCountable.h" #include "FilterResult.h" #include "PageId.h" #include #include #include class TaskStatus; class FilterData; class DebugImages; class ImageTransformation; namespace page_layout { class Task; } namespace select_content { class Filter; class Settings; class Task : public RefCountable { DECLARE_NON_COPYABLE(Task) public: Task(IntrusivePtr const& filter, IntrusivePtr const& next_task, IntrusivePtr const& settings, PageId const& page_id, bool batch, bool debug); virtual ~Task(); FilterResultPtr process(TaskStatus const& status, FilterData const& data); private: class UiUpdater; IntrusivePtr m_ptrFilter; IntrusivePtr m_ptrNextTask; IntrusivePtr m_ptrSettings; std::auto_ptr m_ptrDbg; PageId m_pageId; bool m_batchProcessing; }; } // namespace select_content #endif scantailor-RELEASE_0_9_12_2/filters/select_content/Thumbnail.cpp000066400000000000000000000037511271170121200245750ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Thumbnail.h" #include #include #include #include #include #include #include namespace select_content { Thumbnail::Thumbnail( IntrusivePtr const& thumbnail_cache, QSizeF const& max_size, ImageId const& image_id, ImageTransformation const& xform, QRectF const& content_rect) : ThumbnailBase(thumbnail_cache, max_size, image_id, xform), m_contentRect(content_rect) { } void Thumbnail::paintOverImage( QPainter& painter, QTransform const& image_to_display, QTransform const& thumb_to_display) { if (m_contentRect.isNull()) { return; } painter.setRenderHint(QPainter::Antialiasing, false); QPen pen(QColor(0x00, 0x00, 0xff)); pen.setWidth(1); pen.setCosmetic(true); painter.setPen(pen); painter.setBrush(QColor(0x00, 0x00, 0xff, 50)); QRectF content_rect(virtToThumb().mapRect(m_contentRect)); // Adjust to compensate for pen width. content_rect.adjust(-1, -1, 1, 1); // toRect() is necessary because we turn off antialiasing. // For some reason, if we let Qt round the coordinates, // the result is slightly different. painter.drawRect(content_rect.toRect()); } } // namespace select_content scantailor-RELEASE_0_9_12_2/filters/select_content/Thumbnail.h000066400000000000000000000026761271170121200242470ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SELECT_CONTENT_THUMBNAIL_H_ #define SELECT_CONTENT_THUMBNAIL_H_ #include "ThumbnailBase.h" #include class QSizeF; class ThumbnailPixmapCache; class ImageId; class ImageTransformation; namespace select_content { class Thumbnail : public ThumbnailBase { public: Thumbnail(IntrusivePtr const& thumbnail_cache, QSizeF const& max_size, ImageId const& image_id, ImageTransformation const& xform, QRectF const& content_rect); virtual void paintOverImage( QPainter& painter, QTransform const& image_to_display, QTransform const& thumb_to_display); private: QRectF m_contentRect; }; } // namespace select_content #endif scantailor-RELEASE_0_9_12_2/filters/select_content/ui/000077500000000000000000000000001271170121200225555ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/filters/select_content/ui/SelectContentApplyDialog.ui000066400000000000000000000172531271170121200300240ustar00rootroot00000000000000 SelectContentApplyDialog Qt::WindowModal 0 0 364 316 Select Content Apply to This page only (already applied) true All pages This page and the following ones 0 0 0 0 Every other page Qt::Horizontal QSizePolicy::Fixed 30 0 7 The current page will be included. 0 0 Selected pages Qt::Horizontal QSizePolicy::Fixed 30 0 7 Use Ctrl+Click / Shift+Click to select multiple pages. 0 0 Every other selected page Qt::Horizontal QSizePolicy::Fixed 30 0 7 The current page will be included. Qt::Vertical 17 0 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox rejected() SelectContentApplyDialog reject() 319 252 286 256 scantailor-RELEASE_0_9_12_2/filters/select_content/ui/SelectContentOptionsWidget.ui000066400000000000000000000070141271170121200304100ustar00rootroot00000000000000 SelectContentOptionsWidget 0 0 218 202 Form Content Box Qt::Horizontal 1 20 Auto true true true Manual true true Qt::Horizontal 1 20 Scope Qt::Horizontal 40 20 Apply to ... Qt::Horizontal 40 20 Qt::Vertical 20 40 scantailor-RELEASE_0_9_12_2/foundation/000077500000000000000000000000001271170121200176255ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/foundation/AlignedArray.h000066400000000000000000000047171271170121200223510ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef ALIGNED_ARRAY_H_ #define ALIGNED_ARRAY_H_ #include "NonCopyable.h" #include #include /** * \brief An array of elements starting at address with a specified alignment. * * The alignment is specified not in terms of bytes, but in terms of units, * where bytes = units * sizeof(T) */ template class AlignedArray { DECLARE_NON_COPYABLE(AlignedArray) public: /** * \brief Constructs a null array. */ AlignedArray() : m_pAlignedData(0), m_pStorage(0) {} AlignedArray(size_t size); ~AlignedArray() { delete[] m_pStorage; } T* data() { return m_pAlignedData; } T const* data() const { return m_pAlignedData; } T& operator[](size_t idx) { return m_pAlignedData[idx]; } T const& operator[](size_t idx) const { return m_pAlignedData[idx]; } void swap(AlignedArray& other); private: T* m_pAlignedData; T* m_pStorage; }; template inline void swap(AlignedArray& o1, AlignedArray& o2) { o1.swap(o2); } template AlignedArray::AlignedArray(size_t size) { int const a = alignment_in_units > 1 ? alignment_in_units : 1; int const am1 = a - 1; m_pStorage = new T[size + am1]; m_pAlignedData = m_pStorage + ((a - ((uintptr_t(m_pStorage) / sizeof(T)) & am1)) & am1); } template void AlignedArray::swap(AlignedArray& other) { T* temp = m_pAlignedData; m_pAlignedData = other.m_pAlignedData; other.m_pAlignedData = temp; temp = m_pStorage; m_pStorage = other.m_pStorage; other.m_pStorage = temp; } #endif scantailor-RELEASE_0_9_12_2/foundation/AutoRemovingFile.cpp000066400000000000000000000032431271170121200235520ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "AutoRemovingFile.h" #include AutoRemovingFile::AutoRemovingFile() { } AutoRemovingFile::AutoRemovingFile(QString const& file_path) : m_file(file_path) { } AutoRemovingFile::AutoRemovingFile(AutoRemovingFile& other) : m_file(other.release()) { } AutoRemovingFile::AutoRemovingFile(CopyHelper other) : m_file(other.obj->release()) { } AutoRemovingFile::~AutoRemovingFile() { if (!m_file.isEmpty()) { QFile::remove(m_file); } } AutoRemovingFile& AutoRemovingFile::operator=(AutoRemovingFile& other) { m_file = other.release(); return *this; } AutoRemovingFile& AutoRemovingFile::operator=(CopyHelper other) { m_file = other.obj->release(); return *this; } void AutoRemovingFile::reset(QString const& file) { QString const old_file(file); m_file = file; if (!old_file.isEmpty()) { QFile::remove(old_file); } } QString AutoRemovingFile::release() { QString saved(m_file); m_file = QString(); return saved; } scantailor-RELEASE_0_9_12_2/foundation/AutoRemovingFile.h000066400000000000000000000031701271170121200232160ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef AUTO_REMOVING_FILE_H_ #define AUTO_REMOVING_FILE_H_ #include /** * \brief Provides auto_ptr semantics for files. * * Just like std::auto_ptr deleting its object when it goes out of scope, * this class deletes a file. auto_ptr's copying semantics is also preserved. */ class AutoRemovingFile { private: struct CopyHelper { AutoRemovingFile* obj; CopyHelper(AutoRemovingFile* o) : obj(o) {} }; public: AutoRemovingFile(); AutoRemovingFile(QString const& file_path); AutoRemovingFile(AutoRemovingFile& other); AutoRemovingFile(CopyHelper other); ~AutoRemovingFile(); AutoRemovingFile& operator=(AutoRemovingFile& other); AutoRemovingFile& operator=(CopyHelper other); operator CopyHelper() { return CopyHelper(this); } QString const& get() const { return m_file; } void reset(QString const& file); QString release(); private: QString m_file; }; #endif scantailor-RELEASE_0_9_12_2/foundation/CMakeLists.txt000066400000000000000000000014021271170121200223620ustar00rootroot00000000000000PROJECT("Foundation library") INCLUDE_DIRECTORIES("${CMAKE_CURRENT_BINARY_DIR}") SET( sources NonCopyable.h IntrusivePtr.h RefCountable.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 ) SOURCE_GROUP("Sources" FILES ${sources}) QT4_AUTOMOC(${sources}) ADD_LIBRARY(foundation STATIC ${sources}) scantailor-RELEASE_0_9_12_2/foundation/DynamicPool.h000066400000000000000000000061471271170121200222240ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DYNAMIC_POOL_H_ #define DYNAMIC_POOL_H_ #include "NonCopyable.h" #include #include #include /** * \brief Allocates objects from the heap. * * There is no way of freeing the allocated objects * besides destroying the whole pool. */ template class DynamicPool { DECLARE_NON_COPYABLE(DynamicPool) public: 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); private: enum { OVERALLOCATION_FACTOR = 3 }; /**< Allocate 3 times the requested size. */ enum { OVERALLOCATION_LIMIT = 256 }; /**< Don't overallocate too much. */ struct Chunk : public boost::intrusive::list_base_hook<> { boost::scoped_array storage; T* pData; size_t remainingElements; Chunk() : pData(0), remainingElements(0) {} 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; } }; typedef boost::intrusive::list > ChunkList; static size_t adviseChunkSize(size_t num_elements); ChunkList m_chunkList; }; template DynamicPool::~DynamicPool() { m_chunkList.clear_and_dispose(DeleteDisposer()); } template T* DynamicPool::alloc(size_t num_elements) { Chunk* chunk = 0; if (!m_chunkList.empty()) { chunk = &m_chunkList.back(); if (chunk->remainingElements < num_elements) { chunk = 0; } } if (!chunk) { // Create a new chunk. size_t const 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 size_t DynamicPool::adviseChunkSize(size_t num_elements) { size_t factor = OVERALLOCATION_LIMIT / num_elements; if (factor > (size_t)OVERALLOCATION_FACTOR) { factor = OVERALLOCATION_FACTOR; } return num_elements * (factor + 1); } #endif scantailor-RELEASE_0_9_12_2/foundation/FastQueue.h000066400000000000000000000076741271170121200217160ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef FAST_QUEUE_H_ #define FAST_QUEUE_H_ #include "NonCopyable.h" #include #include #include #include #include #include template class FastQueue { public: FastQueue() : m_chunkCapacity(defaultChunkCapacity()) {} FastQueue(FastQueue const& other); ~FastQueue() { m_chunkList.clear_and_dispose(ChunkDisposer()); } FastQueue& operator=(FastQueue const& other); bool const empty() const { return m_chunkList.empty(); } T& front() { return *m_chunkList.front().pBegin; } T const& front() const { return *m_chunkList.front().pBegin; } void push(T const& t); void pop(); void swap(FastQueue& other); private: struct Chunk : public boost::intrusive::list_base_hook<> { DECLARE_NON_COPYABLE(Chunk) public: Chunk(size_t capacity) { uintptr_t const p = (uintptr_t)(this + 1); size_t const 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< Chunk, boost::intrusive::constant_time_size > ChunkList; static size_t defaultChunkCapacity() { return (sizeof(T) >= 4096) ? 1 : 4096 / sizeof(T); } ChunkList m_chunkList; size_t m_chunkCapacity; }; template FastQueue::FastQueue(FastQueue const& other) : m_chunkCapacity(other.m_chunkCapacity) { BOOST_FOREACH(Chunk& chunk, other.m_chunkList) { for (T const* obj = chunk->pBegin; obj != chunk->pEnd; ++obj) { push(*obj); } } } template FastQueue& FastQueue::operator=(FastQueue const& other) { FastQueue(other).swap(*this); return *this; } template void FastQueue::push(T const& t) { Chunk* 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; } 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); } } template void FastQueue::swap(FastQueue& other) { m_chunkList.swap(other.m_chunkList); size_t const tmp = m_chunkCapacity; m_chunkCapacity = other.m_chunkCapacity; other.m_chunkCapacity = tmp; } template inline void swap(FastQueue& o1, FastQueue& o2) { o1.swap(o2); } #endif scantailor-RELEASE_0_9_12_2/foundation/FlagOps.h000066400000000000000000000027741271170121200213430ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef FLAGOPS_H_ #define FLAGOPS_H_ #ifdef HAVE_CONFIG_H #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; } #endif scantailor-RELEASE_0_9_12_2/foundation/Grid.h000066400000000000000000000113341271170121200206650ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef GRID_H_ #define GRID_H_ #include 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(Grid const& other); bool isNull() const { return m_width <= 0 || m_height <= 0; } void initPadding(Node const& padding_node); void initInterior(Node const& 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. */ Node const* 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. */ Node const* 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; }; template Grid::Grid() : m_pData(0), m_width(0), m_height(0), m_stride(0), m_padding(0) { } 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 Grid::Grid(Grid const& 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()) { int const len = m_stride * (m_height + m_padding * 2); for (int i = 0; i < len; ++i) { m_storage[i] = other.m_storage[i]; } } template void Grid::initPadding(Node const& padding_node) { 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; } 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; } } template void Grid::initInterior(Node const& 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; } } 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); } template void swap(Grid& o1, Grid& o2) { o1.swap(o2); } #endif scantailor-RELEASE_0_9_12_2/foundation/GridLineTraverser.cpp000066400000000000000000000035631271170121200237330ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "GridLineTraverser.h" #include "LineIntersectionScalar.h" #include #include GridLineTraverser::GridLineTraverser(QLineF const& line) { QPoint const p1(line.p1().toPoint()); QPoint const p2(line.p2().toPoint()); int h_spans, v_spans, num_spans; double s1 = 0.0, s2 = 0.0; if ((h_spans = abs(p1.x() - p2.x())) > (v_spans = 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() { QPointF const pt(m_line.pointAt(m_stopsDone * m_dt)); ++m_stopsDone; return pt.toPoint(); } scantailor-RELEASE_0_9_12_2/foundation/GridLineTraverser.h000066400000000000000000000023501271170121200233710ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef GRID_LINE_TRAVERSER_H_ #define GRID_LINE_TRAVERSER_H_ #include /** * \brief Traverses a grid along a line segment. * * Think about drawing a line on an image. */ class GridLineTraverser { // Member-wise copying is OK. public: GridLineTraverser(QLineF const& line); bool hasNext() const { return m_stopsDone < m_totalStops; } QPoint next(); private: QLineF m_line; double m_dt; int m_totalStops; int m_stopsDone; }; #endif scantailor-RELEASE_0_9_12_2/foundation/IntrusivePtr.h000066400000000000000000000076251271170121200224660ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef INTRUSIVEPTR_H_ #define INTRUSIVEPTR_H_ #ifdef HAVE_CONFIG_H #include #endif template class IntrusivePtr { private: struct BooleanTestHelper { int dataMember; }; typedef int BooleanTestHelper::*BooleanTest; public: IntrusivePtr() : m_pObj(0) {} explicit IntrusivePtr(T* obj); IntrusivePtr(IntrusivePtr const& other); template IntrusivePtr(IntrusivePtr const& other); ~IntrusivePtr(); IntrusivePtr& operator=(IntrusivePtr const& rhs); template IntrusivePtr& operator=(IntrusivePtr const& rhs); T& operator*() const { return *m_pObj; } T* operator->() const { return m_pObj; } T* get() const { return m_pObj; } void reset(T* obj = 0); void swap(IntrusivePtr& other); /** * Used for boolean tests, like: * \code * IntrusivePtr ptr = ...; * if (ptr) { * ... * } * if (!ptr) { * ... * } * \endcode * This implementation insures that the following expressions fail to compile: * \code * IntrusivePtr ptr = ...; * int i = ptr; * delete ptr; * \endcode */ inline operator BooleanTest() const; private: T* m_pObj; }; /** * \brief Default implementation of intrusive referencing. * * May be specialized or overloaded. */ template inline void intrusive_ref(T& obj) { obj.ref(); } /** * \brief Default implementation of intrusive unreferencing. * * May be specialized or overloaded. */ template inline void intrusive_unref(T& obj) { obj.unref(); } template inline IntrusivePtr::IntrusivePtr(T* obj) : m_pObj(obj) { if (obj) intrusive_ref(*obj); } template inline IntrusivePtr::IntrusivePtr(IntrusivePtr const& other) : m_pObj(other.m_pObj) { if (m_pObj) intrusive_ref(*m_pObj); } template template inline IntrusivePtr::IntrusivePtr(IntrusivePtr const& other) : m_pObj(other.get()) { if (m_pObj) intrusive_ref(*m_pObj); } template inline IntrusivePtr::~IntrusivePtr() { if (m_pObj) intrusive_unref(*m_pObj); } template inline IntrusivePtr& IntrusivePtr::operator=(IntrusivePtr const& rhs) { IntrusivePtr(rhs).swap(*this); return *this; } template template inline IntrusivePtr& IntrusivePtr::operator=(IntrusivePtr const& rhs) { IntrusivePtr(rhs).swap(*this); return *this; } template inline void IntrusivePtr::reset(T* obj) { IntrusivePtr(obj).swap(*this); } template inline void IntrusivePtr::swap(IntrusivePtr& other) { T* obj = other.m_pObj; other.m_pObj = m_pObj; m_pObj = obj; } template inline void swap(IntrusivePtr& o1, IntrusivePtr& o2) { o1.swap(o2); } template IntrusivePtr::operator BooleanTest() const { return m_pObj ? &BooleanTestHelper::dataMember : 0; } #define INTRUSIVE_PTR_OP(op) \ template \ inline bool operator op(IntrusivePtr const& lhs, IntrusivePtr const& rhs) \ { \ return lhs.get() op rhs.get(); \ } INTRUSIVE_PTR_OP(==) INTRUSIVE_PTR_OP(!=) INTRUSIVE_PTR_OP(<) INTRUSIVE_PTR_OP(>) INTRUSIVE_PTR_OP(<=) INTRUSIVE_PTR_OP(>=) #endif scantailor-RELEASE_0_9_12_2/foundation/MatMNT.h000066400000000000000000000056421271170121200211050ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef MAT_MNT_H_ #define MAT_MNT_H_ #include template class MatMNT; typedef MatMNT<2, 2, float> Mat22f; typedef MatMNT<2, 2, double> Mat22d; typedef MatMNT<3, 3, float> Mat33f; typedef MatMNT<3, 3, double> Mat33d; typedef MatMNT<4, 4, float> Mat44f; typedef MatMNT<4, 4, double> Mat44d; /** * \brief A matrix with pre-defined dimensions. * * \note The memory layout is always column-major, as that's what MatrixCalc uses. */ template class MatMNT { public: typedef T type; enum { ROWS = M, COLS = 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(OT const* data); /** * \brief Construction from a matrix of same dimensions but another type. * * Conversion is done by static casts. */ template MatMNT(MatMNT const& other); /** * \brief Assignment from a matrix of same dimensions but another type. * * Conversion is done by static casts. */ template MatMNT& operator=(MatMNT const& other); T const* data() const { return m_data; } T* data() { return m_data; } T const& 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 MatMNT::MatMNT() { size_t const len = ROWS*COLS; for (size_t i = 0; i < len; ++i) { m_data[i] = T(); } } template template MatMNT::MatMNT(OT const* data) { size_t const len = ROWS*COLS; for (size_t i = 0; i < len; ++i) { m_data[i] = static_cast(data[i]); } } template template MatMNT::MatMNT(MatMNT const& other) { OT const* data = other.data(); size_t const len = ROWS*COLS; for (size_t i = 0; i < len; ++i) { m_data[i] = static_cast(data[i]); } } #endif scantailor-RELEASE_0_9_12_2/foundation/MatT.h000066400000000000000000000133471271170121200206530ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef MAT_T_H_ #define MAT_T_H_ #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 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, OT const* data); /** * Ordinary copy-construction. */ MatT(MatT const& other); /** * \brief Construction from a matrix of a different type. * * Conversion is done by static casts. */ template MatT(MatT const& other); /** * \brief Ordinary assignment. */ MatT& operator=(MatT const& other); /** * \brief Assignment from a matrix of a different type. * * Conversion is done by static casts. */ template MatT& operator=(MatT const& other); MatT& operator+=(MatT const& rhs); MatT& operator-=(MatT const& rhs); MatT& operator*=(T scalar); size_t rows() const { return m_rows; } size_t cols() const { return m_cols; } T const* data() const { return m_data.get(); } T* data() { return m_data.get(); } T const& 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(T const& 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(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(). { } template MatT::MatT(size_t rows, size_t cols, T initial_value) : m_rows(rows), m_cols(cols), m_data(new T[rows*cols]) { size_t const len = rows*cols; for (size_t i = 0; i < len; ++i) { m_data[i] = initial_value; } } template template MatT::MatT(size_t rows, size_t cols, OT const* data) : m_rows(rows), m_cols(cols), m_data(new T[rows*cols]) { size_t const len = rows*cols; for (size_t i = 0; i < len; ++i) { m_data[i] = static_cast(data[i]); } } template MatT::MatT(MatT const& other) : m_rows(other.rows()), m_cols(other.cols()), m_data(new T[m_rows*m_cols]) { size_t const len = m_rows*m_cols; T const* other_data = other.data(); for (size_t i = 0; i < len; ++i) { m_data[i] = other_data[i]; } } template template MatT::MatT(MatT const& other) : m_rows(other.rows()), m_cols(other.cols()), m_data(new T[m_rows*m_cols]) { size_t const len = m_rows*m_cols; T const* other_data = other.data(); for (size_t i = 0; i < len; ++i) { m_data[i] = static_cast(other_data[i]); } } template MatT& MatT::operator=(MatT const& other) { MatT(other).swap(*this); return *this; } template template MatT& MatT::operator=(MatT const& other) { MatT(other).swap(*this); return *this; } template MatT& MatT::operator+=(MatT const& rhs) { assert(m_rows == rhs.m_rows && m_cols == rhs.m_cols); size_t const len = m_rows*m_cols; for (size_t i = 0; i < len; ++i) { m_data[i] += rhs.m_data[i]; } return *this; } template MatT& MatT::operator-=(MatT const& rhs) { assert(m_rows == rhs.m_rows && m_cols == rhs.m_cols); size_t const len = m_rows*m_cols; for (size_t i = 0; i < len; ++i) { m_data[i] -= rhs.m_data[i]; } return *this; } template MatT& MatT::operator*=(T const scalar) { size_t const len = m_rows*m_cols; for (size_t i = 0; i < len; ++i) { m_data[i] *= scalar; } return *this; } template void MatT::fill(T const& value) { size_t const len = m_rows*m_cols; for (size_t i = 0; i < len; ++i) { m_data[i] = value; } } template void MatT::swap(MatT& other) { 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; m_data.swap(other.m_data); } template void swap(MatT const& o1, MatT const& o2) { o1.swap(o2); } template MatT operator*(MatT const& mat, double scalar) { MatT res(mat); res *= scalar; return res; } template MatT operator*(double scalar, MatT const& mat) { MatT res(mat); res *= scalar; return res; } #endif scantailor-RELEASE_0_9_12_2/foundation/NonCopyable.h000066400000000000000000000020711271170121200222070ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef NONCOPYABLE_H_ #define NONCOPYABLE_H_ #ifdef HAVE_CONFIG_H #include #endif #define DECLARE_NON_COPYABLE(Class) \ private: \ /** \brief Copying is forbidden. */ \ Class(Class const&); \ /** \brief Copying is forbidden. */ \ Class& operator=(Class const&); #endif scantailor-RELEASE_0_9_12_2/foundation/NumericTraits.h000066400000000000000000000035461271170121200225770ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef NUMERIC_TRAITS_H_ #define NUMERIC_TRAITS_H_ #include namespace numeric_traits_impl { template struct IntegerSpecific; } // namespace numeric_traits_impl /** * This class exists mainly because std::numeric_values<>::min() has * inconsistent behaviour for integer vs floating point types. */ template class NumericTraits { 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< T, std::numeric_limits::is_integer >::min(); } private: }; namespace numeric_traits_impl { template struct IntegerSpecific { static T min() { return std::numeric_limits::min(); } }; template struct IntegerSpecific { static T min() { return -std::numeric_limits::max(); } }; } // namespace numeric_traits_impl #endif scantailor-RELEASE_0_9_12_2/foundation/PerformanceTimer.cpp000066400000000000000000000022671271170121200236020ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "PerformanceTimer.h" #include void PerformanceTimer::print(char const* prefix) { clock_t const now = clock(); double const 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"; } } scantailor-RELEASE_0_9_12_2/foundation/PerformanceTimer.h000066400000000000000000000020101271170121200232310ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PERFORMANCETIMER_H_ #define PERFORMANCETIMER_H_ #include class PerformanceTimer { public: PerformanceTimer() : m_start(clock()) {} void print(char const* prefix = ""); private: clock_t const m_start; }; #endif scantailor-RELEASE_0_9_12_2/foundation/PriorityQueue.h000066400000000000000000000141251271170121200226270ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PRIORITY_QUEUE_H_ #define PRIORITY_QUEUE_H_ #include #include #include #include /** * \brief A priority queue implemented as a binary heap. * * \tparam T The type of objects to be stored in the priority queue. * \param SubClass A sub class of this class that will be implementing the following: * \li void setIndex(T& obj, size_t heap_idx); * \li bool higherThan(T const& lhs, T const& rhs) const; * * Also note that this implementation will benefit from a standalone * \code * void swap(T& o1, T& o2); * \endcode * function in the same namespace as T. */ template class PriorityQueue { // Member-wise copying is OK. public: PriorityQueue() {} void reserve(size_t capacity) { m_index.reserve(capacity); } bool empty() const { return m_index.empty(); } 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(); } T const& front() const { return m_index.front(); } void push(T const& 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(); /** * 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); } protected: void erase(size_t idx); void reposition(size_t idx); 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 right(size_t idx) { return idx * 2 + 2; } SubClass* subClass() { return static_cast(this); } SubClass const* subClass() const { return static_cast(this); } size_t bubbleUp(size_t idx); size_t bubbleDown(size_t idx); std::vector m_index; }; template inline void swap(PriorityQueue& o1, PriorityQueue& o2) { o1.swap(o2); } template void PriorityQueue::push(T const& obj) { size_t const idx = m_index.size(); m_index.push_back(obj); subClass()->setIndex(m_index.back(), idx); bubbleUp(idx); } template void PriorityQueue::pushDestructive(T& obj) { using namespace std; size_t const idx = m_index.size(); m_index.push_back(T()); swap(m_index.back(), obj); subClass()->setIndex(m_index.back(), idx); bubbleUp(idx); } template void PriorityQueue::pop() { using namespace std; assert(!empty()); swap(m_index.front(), m_index.back()); subClass()->setIndex(m_index.front(), 0); m_index.pop_back(); if (!empty()) { bubbleDown(0); } } template void PriorityQueue::retrieveFront(T& obj) { using namespace std; assert(!empty()); 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); } } template void PriorityQueue::erase(size_t const idx) { using namespace std; swap(m_index[idx], m_index.back()); subClass()->setIndex(m_index[idx], idx); m_index.pop_back(); reposition(m_index[idx]); } template void PriorityQueue::reposition(size_t const idx) { bubbleUp(bubbleDown(idx)); } 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) { size_t const 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; } template size_t PriorityQueue::bubbleDown(size_t idx) { using namespace std; size_t const len = m_index.size(); assert(idx < len); // While any child is greater than the element itself, // swap it with the greatest child. for (;;) { size_t const lft = left(idx); size_t const 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; } #endif scantailor-RELEASE_0_9_12_2/foundation/Property.h000066400000000000000000000020731271170121200216240ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PROPERTY_H_ #define PROPERTY_H_ #include "RefCountable.h" #include "IntrusivePtr.h" class QDomDocument; class QDomElement; class Property : public RefCountable { public: virtual IntrusivePtr clone() const = 0; virtual QDomElement toXml(QDomDocument& doc, QString const& name) const = 0; }; #endif scantailor-RELEASE_0_9_12_2/foundation/PropertyFactory.cpp000066400000000000000000000023171271170121200235100ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "PropertyFactory.h" #include #include void PropertyFactory::registerProperty(QString const& property, PropertyConstructor constructor) { m_registry[property] = constructor; } IntrusivePtr PropertyFactory::construct(QDomElement const& el) const { Registry::const_iterator it(m_registry.find(el.attribute("type"))); if (it != m_registry.end()) { return (*it->second)(el); } else { return IntrusivePtr(); } } scantailor-RELEASE_0_9_12_2/foundation/PropertyFactory.h000066400000000000000000000024771271170121200231640ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PROPERTY_FACTORY_H_ #define PROPERTY_FACTORY_H_ #include "Property.h" #include "IntrusivePtr.h" #include #include class QDomElement; class PropertyFactory { // Member-wise copying is OK. public: virtual ~PropertyFactory() {} typedef IntrusivePtr (*PropertyConstructor)(QDomElement const& el); void registerProperty(QString const& property, PropertyConstructor constructor); IntrusivePtr construct(QDomElement const& el) const; private: typedef std::map Registry; Registry m_registry; }; #endif scantailor-RELEASE_0_9_12_2/foundation/PropertySet.cpp000066400000000000000000000041031271170121200226270ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "PropertySet.h" #include "PropertyFactory.h" #include #include #include #ifndef Q_MOC_RUN #include #endif PropertySet::PropertySet(QDomElement const& el, PropertyFactory const& factory) { QString const property_str("property"); QDomNode node(el.firstChild()); for (; !node.isNull(); node = node.nextSibling()) { if (!node.isElement()) { continue; } if (node.nodeName() != property_str) { continue; } QDomElement prop_el(node.toElement()); IntrusivePtr prop(factory.construct(prop_el)); if (prop.get()) { m_props.push_back(prop); } } } PropertySet::PropertySet(PropertySet const& other) { m_props.reserve(other.m_props.size()); BOOST_FOREACH(IntrusivePtr const& prop, other.m_props) { m_props.push_back(prop->clone()); } } PropertySet& PropertySet::operator=(PropertySet const& other) { PropertySet(other).swap(*this); return *this; } void PropertySet::swap(PropertySet& other) { m_props.swap(other.m_props); } QDomElement PropertySet::toXml(QDomDocument& doc, QString const& name) const { QString const property_str("property"); QDomElement props_el(doc.createElement(name)); BOOST_FOREACH(IntrusivePtr const& prop, m_props) { props_el.appendChild(prop->toXml(doc, property_str)); } return props_el; } scantailor-RELEASE_0_9_12_2/foundation/PropertySet.h000066400000000000000000000067161271170121200223100ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PROPERTY_SET_H_ #define PROPERTY_SET_H_ #include "RefCountable.h" #include "IntrusivePtr.h" #include "Property.h" #include class PropertyFactory; class QDomDocument; class QDomElement; class QString; class PropertySet : public RefCountable { public: PropertySet() {} /** * \brief Makes a deep copy of another property set. */ PropertySet(PropertySet const& other); PropertySet(QDomElement const& el, PropertyFactory const& factory); /** * \brief Makes a deep copy of another property set. */ PropertySet& operator=(PropertySet const& other); void swap(PropertySet& other); QDomElement toXml(QDomDocument& doc, QString const& 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 IntrusivePtr locate(); template IntrusivePtr 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 IntrusivePtr locateOrDefault(); template IntrusivePtr 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 IntrusivePtr locateOrCreate(); private: typedef std::vector > PropList; PropList m_props; }; template IntrusivePtr PropertySet::locate() { PropList::iterator it(m_props.begin()); PropList::iterator const end(m_props.end()); for (; it != end; ++it) { if (T* obj = dynamic_cast(it->get())) { return IntrusivePtr(obj); } } return IntrusivePtr(); } template IntrusivePtr PropertySet::locate() const { PropList::const_iterator it(m_props.begin()); PropList::const_iterator const end(m_props.end()); for (; it != end; ++it) { if (T const* obj = dynamic_cast(it->get())) { return IntrusivePtr(obj); } } return IntrusivePtr(); } template IntrusivePtr PropertySet::locateOrDefault() { IntrusivePtr obj(locate()); if (!obj.get()) { obj.reset(new T); } return obj; } template IntrusivePtr PropertySet::locateOrDefault() const { IntrusivePtr obj(locate()); if (!obj.get()) { obj.reset(new T); } return obj; } template IntrusivePtr PropertySet::locateOrCreate() { IntrusivePtr obj(locate()); if (!obj.get()) { obj.reset(new T); m_props.push_back(obj); } return obj; } inline void swap(PropertySet& o1, PropertySet& o2) { o1.swap(o2); } #endif scantailor-RELEASE_0_9_12_2/foundation/Proximity.cpp000066400000000000000000000045521271170121200223430ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Proximity.h" #include #include #include Proximity::Proximity(QPointF const& p1, QPointF const& p2) { double const dx = p1.x() - p2.x(); double const dy = p1.y() - p2.y(); m_sqDist = dx * dx + dy * dy; //dx * dy; } Proximity Proximity::pointAndLineSegment( QPointF const& pt, QLineF const& 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()); } QLineF perpendicular(segment.normalVector()); // Make the perpendicular pass through pt. perpendicular.translate(-perpendicular.p1()); perpendicular.translate(pt); // Calculate intersection. QPointF intersection; segment.intersect(perpendicular, &intersection); double const dx1 = segment.p1().x() - intersection.x(); double const dy1 = segment.p1().y() - intersection.y(); double const dx2 = segment.p2().x() - intersection.x(); double const dy2 = segment.p2().y() - intersection.y(); double const dx12 = dx1 * dx2; double const 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; } return Proximity(intersection, pt); } Proximity prx[2]; QPointF pts[2]; prx[0] = Proximity(segment.p1(), pt); prx[1] = Proximity(segment.p2(), pt); pts[0] = segment.p1(); pts[1] = segment.p2(); Proximity const* p_min_prx = std::min_element(prx, prx + 2); if (point_on_segment) { *point_on_segment = pts[p_min_prx - prx]; } return *p_min_prx; } scantailor-RELEASE_0_9_12_2/foundation/Proximity.h000066400000000000000000000036631271170121200220120ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PROXIMITY_H_ #define PROXIMITY_H_ #include #include class QPointF; class QLineF; class Proximity { public: Proximity() : m_sqDist(std::numeric_limits::max()) {} Proximity(QPointF const& p1, QPointF const& p2); static Proximity fromDist(double dist) { return Proximity(dist * dist); } static Proximity fromSqDist(double sqDist) { return Proximity(sqDist); } static Proximity pointAndLineSegment( QPointF const& pt, QLineF const& segment, QPointF* point_on_segment = 0); double dist() const { return sqrt(m_sqDist); } double sqDist() const { return m_sqDist; } bool operator==(Proximity const& rhs) const { return m_sqDist == rhs.m_sqDist; } bool operator!=(Proximity const& rhs) const { return m_sqDist != rhs.m_sqDist; } bool operator<(Proximity const& rhs) const { return m_sqDist < rhs.m_sqDist; } bool operator>(Proximity const& rhs) const { return m_sqDist > rhs.m_sqDist; } bool operator<=(Proximity const& rhs) const { return m_sqDist <= rhs.m_sqDist; } bool operator>=(Proximity const& rhs) const { return m_sqDist >= rhs.m_sqDist; } private: Proximity(double sqDist) : m_sqDist(sqDist) {} double m_sqDist; }; #endif scantailor-RELEASE_0_9_12_2/foundation/QtSignalForwarder.cpp000066400000000000000000000020761271170121200237340ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "QtSignalForwarder.h" #include "QtSignalForwarder.h.moc" QtSignalForwarder::QtSignalForwarder( QObject* emitter, char const* signal, boost::function const& slot) : QObject(emitter), m_slot(slot) { connect(emitter, signal, SLOT(handleSignal())); } void QtSignalForwarder::handleSignal() { m_slot(); } scantailor-RELEASE_0_9_12_2/foundation/QtSignalForwarder.h000066400000000000000000000033431271170121200233770ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef QT_SIGNAL_FORWARDER_H_ #define QT_SIGNAL_FORWARDER_H_ #include "NonCopyable.h" #include #ifndef Q_MOC_RUN #include #endif /** * \brief Connects to a Qt signal and forwards it to a boost::function. * * Useful when you need to bind additional parameters to a slot * 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, char const* signal, boost::function const& slot); private slots: void handleSignal(); private: boost::function m_slot; }; #endif scantailor-RELEASE_0_9_12_2/foundation/RefCountable.h000066400000000000000000000025711271170121200223540ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef REFCOUNTABLE_H_ #define REFCOUNTABLE_H_ #ifdef HAVE_CONFIG_H #include #endif #include class RefCountable { public: RefCountable() : m_refCounter(0) {} RefCountable(RefCountable const& other) { // don't copy the reference counter! } void operator=(RefCountable const& other) { // don't copy the reference counter! } virtual ~RefCountable() {} void ref() const { m_refCounter.fetchAndAddRelaxed(1); } void unref() const { if (m_refCounter.fetchAndAddRelease(-1) == 1) { delete this; } } private: mutable QAtomicInt m_refCounter; }; #endif scantailor-RELEASE_0_9_12_2/foundation/SafeDeletingQObjectPtr.h000066400000000000000000000031171271170121200242700ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SAFE_DELETING_QOBJECT_PTR_H_ #define SAFE_DELETING_QOBJECT_PTR_H_ #include "NonCopyable.h" template class SafeDeletingQObjectPtr { DECLARE_NON_COPYABLE(SafeDeletingQObjectPtr) public: SafeDeletingQObjectPtr(T* obj = 0) : m_pObj(obj) {} ~SafeDeletingQObjectPtr() { if (m_pObj) { m_pObj->disconnect(); m_pObj->deleteLater(); } } void reset(T* other) { SafeDeletingQObjectPtr(other).swap(*this); } T& operator*() const { return *m_pObj; } T* operator->() const { return m_pObj; } T* get() const { return m_pObj; } void swap(SafeDeletingQObjectPtr& other) { T* tmp = m_pObj; m_pObj = other.m_pObj; other.m_pObj = tmp; } private: T* m_pObj; }; template void swap(SafeDeletingQObjectPtr& o1, SafeDeletingQObjectPtr& o2) { o1.swap(o2); } #endif scantailor-RELEASE_0_9_12_2/foundation/ScopedDecInc.h000066400000000000000000000020631271170121200222620ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SCOPEDDECINC_H_ #define SCOPEDDECINC_H_ #ifdef HAVE_CONFIG_H #include #endif template class ScopedDecInc { public: ScopedDecInc(T& counter) : m_counter(counter) { --counter; } ~ScopedDecInc() { ++m_counter; } private: T& m_counter; }; #endif scantailor-RELEASE_0_9_12_2/foundation/ScopedIncDec.h000066400000000000000000000020631271170121200222620ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SCOPEDINCDEC_H_ #define SCOPEDINCDEC_H_ #ifdef HAVE_CONFIG_H #include #endif template class ScopedIncDec { public: ScopedIncDec(T& counter) : m_counter(counter) { ++counter; } ~ScopedIncDec() { --m_counter; } private: T& m_counter; }; #endif scantailor-RELEASE_0_9_12_2/foundation/Span.h000066400000000000000000000045341271170121200207050ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SPAN_H_ #define SPAN_H_ /** * \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, Span const& end) : m_begin(begin), m_end(end.begin()) {} /** * \brief Constructs a span between another span and a point. */ Span(Span const& begin, int end) : m_begin(begin.end()), m_end(end) {} /** * \brief Constructs a span between two other spans. */ Span(Span const& begin, Span const& 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==(Span const& other) const { return m_begin == other.m_begin && m_end == other.m_end; } bool operator!=(Span const& 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; }; #endif scantailor-RELEASE_0_9_12_2/foundation/StaticPool.h000066400000000000000000000043041271170121200220600ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef STATIC_POOL_H_ #define STATIC_POOL_H_ #include "NonCopyable.h" #include #include 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; }; /** * \brief Allocates objects from a statically sized pool. * * There is no way of releasing the allocated objects * besides destroying the whole pool. */ template class StaticPool : public StaticPoolBase { DECLARE_NON_COPYABLE(StaticPool) public: StaticPool() : StaticPoolBase(m_buf, S) {} private: T m_buf[S]; }; template T* StaticPoolBase::alloc(size_t num_elements) { if (num_elements > m_sizeRemaining) { throw std::runtime_error("StaticPool overflow"); } T* sequence = m_pNext; m_pNext += num_elements; m_sizeRemaining -= num_elements; return sequence; } #endif scantailor-RELEASE_0_9_12_2/foundation/ValueConv.h000066400000000000000000000034271271170121200217060ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef VALUE_CONV_H_ #define VALUE_CONV_H_ #include "NumericTraits.h" #include template class StaticCastValueConv { public: template ToType operator()(FromType val) const { return static_cast(val); } }; template class RoundAndClipValueConv { public: 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(floor(val + 0.5)); } } private: ToType m_min; ToType m_max; }; #endif scantailor-RELEASE_0_9_12_2/foundation/VecNT.h000066400000000000000000000176131271170121200207650ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef VEC_NT_H_ #define VEC_NT_H_ #include #include template class VecNT; typedef VecNT<1, float> Vec1f; typedef VecNT<1, double> Vec1d; typedef VecNT<2, float> Vec2f; typedef VecNT<2, double> Vec2d; typedef VecNT<3, float> Vec3f; typedef VecNT<3, double> Vec3d; typedef VecNT<4, float> Vec4f; typedef VecNT<4, double> Vec4d; template class VecNT { public: typedef T type; enum { SIZE = 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 explicit VecNT(OT const* data); /** * \brief Construction from a vector of same dimension but another type. * * Conversion is done by static casts. */ template VecNT(VecNT const& other); /** * \brief Construction from a one-less dimensional * vector and the last element value. */ template VecNT(VecNT const& 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(QPointF const& 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=(VecNT const& other); T& operator[](size_t idx) { return m_data[idx]; } T const& operator[](size_t idx) const { return m_data[idx]; } VecNT& operator+=(T scalar); VecNT& operator+=(VecNT const& other); VecNT& operator-=(T scalar); VecNT& operator-=(VecNT const& other); VecNT& operator*=(T scalar); VecNT& operator/=(T scalar); T const* data() const { return m_data; } T* data() { return m_data; } /** * \brief Sums elements in the vector. */ T sum() const; T dot(VecNT const& other) const; T squaredNorm() const { return dot(*this); } private: T m_data[N]; }; namespace vecnt { template struct SizeSpecific; template struct SizeSpecific<1, T> { static void assign(T* data, T x) { data[0] = x; } }; 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, QPointF const& pt) { data[0] = static_cast(pt.x()); data[1] = static_cast(pt.y()); } static QPointF toQPointF(T const* data) { return QPointF(static_cast(data[0]), static_cast(data[1])); } }; 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; } }; 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; } }; } // namespace vecnt template VecNT::VecNT() { for (size_t i = 0; i < N; ++i) { m_data[i] = T(); } } template template VecNT::VecNT(OT const* data) { for (size_t i = 0; i < N; ++i) { m_data[i] = static_cast(data[i]); } } template template VecNT::VecNT(VecNT const& other) { for (size_t i = 0; i < N; ++i) { m_data[i] = static_cast(other[i]); } } template template VecNT::VecNT(VecNT const& lesser, T last) { for (size_t i = 0; i < N-1; ++i) { m_data[i] = static_cast(lesser[i]); } m_data[N-1] = last; } template VecNT::VecNT(T x) { vecnt::SizeSpecific::assign(m_data, x); } template VecNT::VecNT(T x, T y) { vecnt::SizeSpecific::assign(m_data, x, y); } template VecNT::VecNT(T x, T y, T z) { vecnt::SizeSpecific::assign(m_data, x, y, z); } template VecNT::VecNT(T x, T y, T z, T w) { vecnt::SizeSpecific::assign(m_data, x, y, z, w); } template VecNT::VecNT(QPointF const& pt) { vecnt::SizeSpecific::assign(m_data, pt); } template VecNT::operator QPointF() const { return vecnt::SizeSpecific::toQPointF(m_data); } template template VecNT& VecNT::operator=(VecNT const& other) { for (size_t i = 0; i < N; ++i) { m_data[i] = static_cast(other[i]); } return *this; } template VecNT& VecNT::operator+=(T scalar) { for (size_t i = 0; i < N; ++i) { m_data[i] += scalar; } return *this; } template VecNT& VecNT::operator+=(VecNT const& other) { for (size_t i = 0; i < N; ++i) { m_data[i] += other[i]; } return *this; } template VecNT& VecNT::operator-=(T scalar) { for (size_t i = 0; i < N; ++i) { m_data[i] -= scalar; } return *this; } template VecNT& VecNT::operator-=(VecNT const& other) { for (size_t i = 0; i < N; ++i) { m_data[i] -= other[i]; } return *this; } template VecNT& VecNT::operator*=(T scalar) { for (size_t i = 0; i < N; ++i) { m_data[i] *= scalar; } return *this; } template VecNT& VecNT::operator/=(T scalar) { return (*this *= (T(1) / scalar)); } template T VecNT::sum() const { T sum = T(); for (size_t i = 0; i < N; ++i) { sum += m_data[i]; } return sum; } template T VecNT::dot(VecNT const& other) const { T sum = T(); for (size_t i = 0; i < N; ++i) { sum += m_data[i] * other[i]; } return sum; } template VecNT operator+(VecNT const& lhs, VecNT const& rhs) { VecNT res(lhs); res += rhs; return res; } template VecNT operator-(VecNT const& lhs, VecNT const& rhs) { VecNT res(lhs); res -= rhs; return res; } template VecNT operator-(VecNT const& vec) { VecNT res(vec); for (size_t i = 0; i < N; ++i) { res[i] = -res[i]; } return res; } template VecNT operator*(VecNT const& vec, T scalar) { VecNT res(vec); res *= scalar; return res; } template VecNT operator*(T scalar, VecNT const& vec) { VecNT res(vec); res *= scalar; return res; } #endif scantailor-RELEASE_0_9_12_2/foundation/VecT.h000066400000000000000000000116351271170121200206450ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef VEC_T_H_ #define VEC_T_H_ #include #include #include /** * \brief A (column) vector of elements of type T. */ 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, OT const* data); /** * Ordinary copy-construction. */ VecT(VecT const& other); /** * \brief Construction from a vector of a different type. * * Conversion is done by static casts. */ template VecT(VecT const& other); /** * \brief Ordinary assignment. */ VecT& operator=(VecT const& other); /** * \brief Assignment from a vector of a different type. * * Conversion is done by static casts. */ template VecT& operator=(VecT const& other); VecT& operator+=(VecT const& rhs); VecT& operator-=(VecT const& rhs); VecT& operator*=(T scalar); size_t size() const { return m_size; } T const* data() const { return m_data.get(); } T* data() { return m_data.get(); } T const& 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(T const& value); void swap(VecT& other); private: boost::scoped_array m_data; size_t m_size; }; template VecT::VecT() : m_size(0) { } template VecT::VecT(size_t size) : m_data(new T[size]()) // The "()" will cause elements to be initialized to T(). , m_size(size) { } 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; } } template template VecT::VecT(size_t size, OT const* data) : m_data(new T[size]) , m_size(size) { for (size_t i = 0; i < size; ++i) { m_data[i] = static_cast(data[i]); } } template VecT::VecT(VecT const& other) : m_data(new T[other.m_size]) , m_size(other.m_size) { T const* other_data = other.data(); for (size_t i = 0; i < m_size; ++i) { m_data[i] = other_data[i]; } } template template VecT::VecT(VecT const& other) : m_data(new T[other.m_size]) , m_size(other.m_size) { T const* other_data = other.data(); for (size_t i = 0; i < m_size; ++i) { m_data[i] = static_cast(other_data[i]); } } template VecT& VecT::operator=(VecT const& other) { VecT(other).swap(*this); return *this; } template template VecT& VecT::operator=(VecT const& other) { VecT(other).swap(*this); return *this; } template VecT& VecT::operator+=(VecT const& rhs) { assert(m_size == rhs.m_size); for (size_t i = 0; i < m_size; ++i) { m_data[i] += rhs.m_data[i]; } return *this; } template VecT& VecT::operator-=(VecT const& rhs) { assert(m_size == rhs.m_size); for (size_t i = 0; i < m_size; ++i) { m_data[i] -= rhs.m_data[i]; } return *this; } template VecT& VecT::operator*=(T const scalar) { for (size_t i = 0; i < m_size; ++i) { m_data[i] *= scalar; } return *this; } template void VecT::fill(T const& value) { for (size_t i = 0; i < m_size; ++i) { m_data[i] = value; } } 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); } template void swap(VecT const& o1, VecT const& o2) { o1.swap(o2); } template VecT operator*(VecT const& vec, double scalar) { VecT res(vec); res *= scalar; return res; } template VecT operator*(double scalar, VecT const& vec) { VecT res(vec); res *= scalar; return res; } #endif scantailor-RELEASE_0_9_12_2/foundation/VirtualFunction.h000066400000000000000000000043401271170121200231330ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef VIRTUALFUNCTION_H_ #define VIRTUALFUNCTION_H_ template class VirtualFunction1 { public: virtual ~VirtualFunction1() {} virtual R operator()(A1 arg1) = 0; }; template class ProxyFunction1 : public VirtualFunction1 { public: ProxyFunction1(Delegate delegate) : m_delegate(delegate) {} virtual R operator()(A1 arg1) { return m_delegate(arg1); } private: Delegate m_delegate; }; template class VirtualFunction2 { public: virtual ~VirtualFunction2() {} virtual R operator()(A1 arg1, A2 arg2) = 0; }; template class ProxyFunction2 : public VirtualFunction2 { public: ProxyFunction2(Delegate delegate) : m_delegate(delegate) {} virtual R operator()(A1 arg1, A2 arg2) { return m_delegate(arg1, arg2); } private: Delegate m_delegate; }; template class VirtualFunction3 { public: virtual ~VirtualFunction3() {} virtual R operator()(A1 arg1, A2 arg2, A3 arg3) = 0; }; template class ProxyFunction3 : public VirtualFunction3 { public: ProxyFunction3(Delegate delegate) : m_delegate(delegate) {} virtual R operator()(A1 arg1, A2 arg2, A3 arg3) { return m_delegate(arg1, arg2, arg3); } private: Delegate m_delegate; }; #endif scantailor-RELEASE_0_9_12_2/imageproc/000077500000000000000000000000001271170121200174255ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/imageproc/AdjustBrightness.cpp000066400000000000000000000060161271170121200234170ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "AdjustBrightness.h" #include #include #include namespace imageproc { void adjustBrightness( QImage& rgb_image, QImage const& brightness, double const wr, double const 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"); } uint32_t* rgb_line = reinterpret_cast(rgb_image.bits()); int const rgb_wpl = rgb_image.bytesPerLine() / 4; uint8_t const* br_line = brightness.bits(); int const br_bpl = brightness.bytesPerLine(); int const width = rgb_image.width(); int const height = rgb_image.height(); double const wg = 1.0 - wr - wb; double const wu = (1.0 - wb); double const wv = (1.0 - wr); double const r_wg = 1.0 / wg; double const r_wu = 1.0 / wu; double const 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]; double const R = (RGB >> 16) & 0xFF; double const G = (RGB >> 8) & 0xFF; double const B = RGB & 0xFF; double const Y = wr * R + wg * G + wb * B; double const U = (B - Y) * r_wu; double const 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; } } void adjustBrightnessYUV(QImage& rgb_image, QImage const& brightness) { adjustBrightness(rgb_image, brightness, 0.299, 0.114); } void adjustBrightnessGrayscale(QImage& rgb_image, QImage const& brightness) { adjustBrightness(rgb_image, brightness, 11.0/32.0, 5.0/32.0); } } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/AdjustBrightness.h000066400000000000000000000046071271170121200230700ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_ADJUST_BRIGHTNESS_H_ #define IMAGEPROC_ADJUST_BRIGHTNESS_H_ class QImage; namespace imageproc { /** * \brief Recalculates every pixel to make its brightness match the provided level. * * \param rgb_image The image to adjust brightness in. Must be * QImage::Format_RGB32 or QImage::Format_ARGB32. * The alpha channel won't be modified. * \param brightness A grayscale image representing the desired brightness * of each pixel. Must be QImage::Format_Indexed8, have a grayscale * palette and have the same size as \p rgb_image. * \param wr The weighting factor for red color component in the brightness * image. * \param wb The weighting factor for blue color component in the brightness * image. * * The brightness values are normally a weighted sum of red, green and blue * color components. The formula is: * \code * brightness = R * wr + G * wg + B * wb; * \endcode * This function takes wr and wb arguments, and calculates wg as 1.0 - wr - wb. */ void adjustBrightness( QImage& rgb_image, QImage const& brightness, double wr, double wb); /** * \brief A custom version of adjustBrightness(). * * Same as adjustBrightness(), but the weighting factors used in the YUV * color space are assumed. */ void adjustBrightnessYUV(QImage& rgb_image, QImage const& brightness); /** * \brief A custom version of adjustBrightness(). * * Same as adjustBrightness(), but the weighting factors used by * toGrayscale() and qGray() are assumed. */ void adjustBrightnessGrayscale(QImage& rgb_image, QImage const& brightness); } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/BWColor.h000066400000000000000000000017631271170121200211140ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef BWCOLOR_H_ #define BWCOLOR_H_ namespace imageproc { enum BWColor { WHITE = 0, BLACK = 1 }; inline BWColor operator!(BWColor c) { return static_cast(~c & 1); } } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/Binarize.cpp000066400000000000000000000157351271170121200217070ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Binarize.h" #include "BinaryImage.h" #include "BinaryThreshold.h" #include "Grayscale.h" #include "IntegralImage.h" #include #include #include #include #include #include #include #include #include #include namespace imageproc { BinaryImage binarizeOtsu(QImage const& src) { return BinaryImage(src, BinaryThreshold::otsuThreshold(src)); } BinaryImage binarizeMokji( QImage const& src, unsigned const max_edge_width, unsigned const min_edge_magnitude) { BinaryThreshold const threshold( BinaryThreshold::mokjiThreshold( src, max_edge_width, min_edge_magnitude ) ); return BinaryImage(src, threshold); } BinaryImage binarizeSauvola(QImage const& src, QSize const window_size) { if (window_size.isEmpty()) { throw std::invalid_argument("binarizeSauvola: invalid window_size"); } if (src.isNull()) { return BinaryImage(); } QImage const gray(toGrayscale(src)); int const w = gray.width(); int const h = gray.height(); IntegralImage integral_image(w, h); IntegralImage integral_sqimage(w, h); uint8_t const* gray_line = gray.bits(); int const 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) { uint32_t const pixel = gray_line[x]; integral_image.push(pixel); integral_sqimage.push(pixel * pixel); } } int const window_lower_half = window_size.height() >> 1; int const window_upper_half = window_size.height() - window_lower_half; int const window_left_half = window_size.width() >> 1; int const window_right_half = window_size.width() - window_left_half; BinaryImage bw_img(w, h); uint32_t* bw_line = bw_img.data(); int const bw_wpl = bw_img.wordsPerLine(); gray_line = gray.bits(); for (int y = 0; y < h; ++y) { int const top = std::max(0, y - window_lower_half); int const bottom = std::min(h, y + window_upper_half); // exclusive for (int x = 0; x < w; ++x) { int const left = std::max(0, x - window_left_half); int const right = std::min(w, x + window_right_half); // exclusive int const area = (bottom - top) * (right - left); assert(area > 0); // because window_size > 0 and w > 0 and h > 0 QRect const rect(left, top, right - left, bottom - top); double const window_sum = integral_image.sum(rect); double const window_sqsum = integral_sqimage.sum(rect); double const r_area = 1.0 / area; double const mean = window_sum * r_area; double const sqmean = window_sqsum * r_area; double const variance = sqmean - mean * mean; double const deviation = sqrt(fabs(variance)); double const k = 0.34; double const threshold = mean * (1.0 + k * (deviation / 128.0 - 1.0)); uint32_t const msb = uint32_t(1) << 31; uint32_t const 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; } BinaryImage binarizeWolf( QImage const& src, QSize const window_size, unsigned char const lower_bound, unsigned char const upper_bound) { if (window_size.isEmpty()) { throw std::invalid_argument("binarizeWolf: invalid window_size"); } if (src.isNull()) { return BinaryImage(); } QImage const gray(toGrayscale(src)); int const w = gray.width(); int const h = gray.height(); IntegralImage integral_image(w, h); IntegralImage integral_sqimage(w, h); uint8_t const* gray_line = gray.bits(); int const 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) { uint32_t const pixel = gray_line[x]; integral_image.push(pixel); integral_sqimage.push(pixel * pixel); min_gray_level = std::min(min_gray_level, pixel); } } int const window_lower_half = window_size.height() >> 1; int const window_upper_half = window_size.height() - window_lower_half; int const window_left_half = window_size.width() >> 1; int const 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) { int const top = std::max(0, y - window_lower_half); int const bottom = std::min(h, y + window_upper_half); // exclusive for (int x = 0; x < w; ++x) { int const left = std::max(0, x - window_left_half); int const right = std::min(w, x + window_right_half); // exclusive int const area = (bottom - top) * (right - left); assert(area > 0); // because window_size > 0 and w > 0 and h > 0 QRect const rect(left, top, right - left, bottom - top); double const window_sum = integral_image.sum(rect); double const window_sqsum = integral_sqimage.sum(rect); double const r_area = 1.0 / area; double const mean = window_sum * r_area; double const sqmean = window_sqsum * r_area; double const variance = sqmean - mean * mean; double const deviation = sqrt(fabs(variance)); max_deviation = std::max(max_deviation, deviation); means[w * y + x] = mean; deviations[w * y + x] = deviation; } } // TODO: integral images can be disposed at this point. BinaryImage bw_img(w, h); uint32_t* bw_line = bw_img.data(); int const 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) { float const mean = means[y * w + x]; float const deviation = deviations[y * w + x]; double const k = 0.3; double const a = 1.0 - deviation / max_deviation; double const threshold = mean - k * a * (mean - min_gray_level); uint32_t const msb = uint32_t(1) << 31; uint32_t const 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; } } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/Binarize.h000066400000000000000000000054351271170121200213500ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_BINARIZE_H_ #define IMAGEPROC_BINARIZE_H_ #include class QImage; namespace imageproc { class BinaryImage; /** * \brief Image binarization using Otsu's global thresholding method. * * N. Otsu (1979). "A threshold selection method from gray-level histograms". * http://en.wikipedia.org/wiki/Otsu%27s_method */ BinaryImage binarizeOtsu(QImage const& src); /** * \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 src 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. */ BinaryImage binarizeMokji( QImage const& src, unsigned max_edge_width = 3, unsigned min_edge_magnitude = 20); /** * \brief Image binarization using Sauvola's local thresholding method. * * Sauvola, J. and M. Pietikainen. 2000. "Adaptive document image binarization". * http://www.mediateam.oulu.fi/publications/pdf/24.pdf */ BinaryImage binarizeSauvola(QImage const& src, QSize window_size); /** * \brief Image binarization using Wolf's local thresholding method. * * C. Wolf, J.M. Jolion, F. Chassaing. "Text localization, enhancement and * binarization in multimedia documents." * http://liris.cnrs.fr/christian.wolf/papers/icpr2002v.pdf * * \param src The image to binarize. * \param window_size The dimensions of a pixel neighborhood to consider. * \param lower_bound The minimum possible gray level that can be made white. * \param upper_bound The maximum possible gray level that can be made black. */ BinaryImage binarizeWolf( QImage const& src, QSize window_size, unsigned char lower_bound = 1, unsigned char upper_bound = 254); } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/BinaryImage.cpp000066400000000000000000000727501271170121200223330ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "BinaryImage.h" #include "ByteOrder.h" #include "BitOps.h" #include #include #include #include #include #include #include #include #include #include #include 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; NumWords(size_t num_words) : numWords(num_words) {} }; public: static SharedData* create(size_t num_words) { return new(NumWords(num_words)) SharedData(); } uint32_t* data() { return m_data; } uint32_t const* data() const { return m_data; } bool isShared() const { return m_refCounter.fetchAndAddRelaxed(0) > 1; } void ref() const { m_refCounter.ref(); } void unref() const; static void* operator new(size_t size, NumWords num_words); static void operator delete(void* addr, NumWords num_words); private: SharedData() : m_refCounter(1) {} SharedData& operator=(SharedData const&); // forbidden mutable QAtomicInt m_refCounter; uint32_t m_data[1]; // more data follows }; BinaryImage::BinaryImage() : m_pData(0), m_width(0), m_height(0), m_wpl(0) { } BinaryImage::BinaryImage(int const width, int const 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"); } } BinaryImage::BinaryImage(QSize const 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"); } } BinaryImage::BinaryImage(int const width, int const height, BWColor const 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); } BinaryImage::BinaryImage(QSize const size, BWColor const 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); } BinaryImage::BinaryImage(int const width, int const height, SharedData* const data) : m_pData(data), m_width(width), m_height(height), m_wpl((width + 31) / 32) { } BinaryImage::BinaryImage(BinaryImage const& 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(); } } BinaryImage::BinaryImage(QImage const& image, BinaryThreshold const threshold) : m_pData(0), m_width(0), m_height(0), m_wpl(0) { QRect const 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( QImage const& image, QRect const& rect, BinaryThreshold const threshold) : m_pData(0), 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("Unsupported QImage format"); } } BinaryImage::~BinaryImage() { if (m_pData) { m_pData->unref(); } } BinaryImage& BinaryImage::operator=(BinaryImage const& other) { BinaryImage(other).swap(*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); } void BinaryImage::invert() { if (isNull()) { return; } size_t const num_words = m_height * m_wpl; 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); uint32_t const* 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; } m_pData->unref(); m_pData = new_data; } } BinaryImage BinaryImage::inverted() const { if (isNull()) { return BinaryImage(); } size_t const num_words = m_height * m_wpl; SharedData* new_data = SharedData::create(num_words); uint32_t const* 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; } return BinaryImage(m_width, m_height, new_data); } void BinaryImage::fill(BWColor const color) { if (isNull()) { throw std::logic_error("Attempt to fill a null BinaryImage!"); } int const pattern = (color == BLACK) ? ~0 : 0; memset(data(), pattern, m_height * m_wpl * 4); } void BinaryImage::fill(QRect const& rect, BWColor const color) { if (rect.isEmpty()) { return; } fillRectImpl(data(), rect.intersected(this->rect()), color); } void BinaryImage::fillExcept(QRect const& rect, BWColor const color) { if (isNull()) { throw std::logic_error("Attempt to fill a null BinaryImage!"); } if (rect.contains(this->rect())) { return; } QRect const bounded_rect(rect.intersected(this->rect())); if (bounded_rect.isEmpty()) { fill(color); return; } int const 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) { QRect const left_rect( 0, y_top, bounded_rect.left(), bounded_rect.height() ); fillRectImpl(data, left_rect, color); } int const x_left = bounded_rect.left() + bounded_rect.width(); if (x_left < m_width) { QRect const 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); } } void BinaryImage::fillFrame( QRect const& outer_rect, QRect const& inner_rect, BWColor const color) { if (isNull()) { throw std::logic_error("Attempt to fill a null BinaryImage!"); } QRect const bounded_outer_rect(outer_rect.intersected(this->rect())); QRect const 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); } } int BinaryImage::countBlackPixels() const { return countBlackPixels(rect()); } int BinaryImage::countWhitePixels() const { return countWhitePixels(rect()); } int BinaryImage::countBlackPixels(QRect const& rect) const { QRect const r(rect.intersected(this->rect())); if (r.isEmpty()) { return 0; } int const top = r.top(); int const bottom = r.bottom(); int const first_word_idx = r.left() >> 5; int const last_word_idx = r.right() >> 5; // r.right() is within rect uint32_t const first_word_mask = ~uint32_t(0) >> (r.left() & 31); int const last_word_unused_bits = (last_word_idx << 5) + 31 - r.right(); uint32_t const last_word_mask = ~uint32_t(0) << last_word_unused_bits; uint32_t const* 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 { uint32_t const 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; } int BinaryImage::countWhitePixels(QRect const& rect) const { QRect const r(rect.intersected(this->rect())); if (r.isEmpty()) { return 0; } return r.width() * r.height() - countBlackPixels(r); } QRect BinaryImage::contentBoundingBox(BWColor const content_color) const { if (isNull()) { return QRect(); } int const w = m_width; int const h = m_height; int const wpl = m_wpl; int const last_word_idx = (w - 1) >> 5; int const last_word_bits = w - (last_word_idx << 5); int const last_word_unused_bits = 32 - last_word_bits; uint32_t const last_word_mask = ~uint32_t(0) << last_word_unused_bits; uint32_t const modifier = (content_color == WHITE) ? ~uint32_t(0) : 0; uint32_t const* const data = this->data(); int bottom = -1; // inclusive uint32_t const* 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) { uint32_t const word = (line[last_word_idx] ^ modifier) >> last_word_unused_bits; if (word) { int const 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); } uint32_t* BinaryImage::data() { if (isNull()) { return 0; } copyIfShared(); return m_pData->data(); } uint32_t const* BinaryImage::data() const { if (isNull()) { return 0; } return m_pData->data(); } QImage BinaryImage::toQImage() const { if (isNull()) { return QImage(); } QImage dst(m_width, m_height, QImage::Format_Mono); assert(dst.bytesPerLine() % 4 == 0); dst.setNumColors(2); dst.setColor(0, 0xffffffff); dst.setColor(1, 0xff000000); int const dst_wpl = dst.bytesPerLine() / 4; uint32_t* dst_line = (uint32_t*)dst.bits(); uint32_t const* src_line = data(); int const 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(QColor const& color) const { if (isNull()) { return QImage(); } int const alpha = color.alpha(); int const red = (color.red() * alpha + 128) / 255; int const green = (color.green() * alpha + 128) / 255; int const blue = (color.blue() * alpha + 128) / 255; uint32_t const colors[] = { 0, // replaces white qRgba(red, green, blue, alpha) // replaces black }; int const width = m_width; int const height = m_height; QImage dst(width, height, QImage::Format_ARGB32_Premultiplied); assert(dst.bytesPerLine() % 4 == 0); int const dst_stride = dst.bytesPerLine() / 4; uint32_t* dst_line = (uint32_t*)dst.bits(); uint32_t const* src_line = data(); int const 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; } return dst; } void BinaryImage::copyIfShared() { assert(m_pData); if (!m_pData->isShared()) { return; } size_t const 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; } void BinaryImage::fillRectImpl(uint32_t* const data, QRect const& rect, BWColor const color) { uint32_t const 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); return; } uint32_t const first_word_idx = rect.left() >> 5; // Note: rect.right() == rect.left() + rect.width() - 1 uint32_t const last_word_idx = rect.right() >> 5; uint32_t const first_word_mask = ~uint32_t(0) >> (rect.left() & 31); uint32_t const 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; uint32_t const mask = first_word_mask & last_word_mask; for (int i = rect.height(); i > 0; --i, line += m_wpl) { *line = (*line & ~mask) | (pattern & mask); } return; } 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 BinaryImage::fromMono(QImage const& image) { int const width = image.width(); int const height = image.height(); assert(image.bytesPerLine() % 4 == 0); int const src_wpl = image.bytesPerLine() / 4; uint32_t const* src_line = (uint32_t const*)image.bits(); BinaryImage dst(width, height); int const dst_wpl = dst.wordsPerLine(); uint32_t* dst_line = dst.data(); uint32_t modifier = ~uint32_t(0); if (image.numColors() >= 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; } return dst; } BinaryImage BinaryImage::fromMono(QImage const& image, QRect const& rect) { int const width = rect.width(); int const height = rect.height(); assert(image.bytesPerLine() % 4 == 0); int const src_wpl = image.bytesPerLine() / 4; uint32_t const* src_line = (uint32_t const*)image.bits(); src_line += rect.top() * src_wpl; src_line += rect.left() >> 5; int const word1_unused_bits = rect.left() & 31; int const word2_unused_bits = 32 - word1_unused_bits; BinaryImage dst(width, height); int const dst_wpl = dst.wordsPerLine(); uint32_t* dst_line = dst.data(); int const dst_last_word_unused_bits = (dst_wpl << 5) - width; uint32_t modifier = ~uint32_t(0); if (image.numColors() >= 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 { int const 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) { uint32_t const this_word = next_word; next_word = ntohl(src_line[j + 1]); uint32_t const 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 BinaryImage::fromMonoLSB(QImage const& image) { return fromMono(image.convertToFormat(QImage::Format_Mono)); } BinaryImage BinaryImage::fromMonoLSB(QImage const& image, QRect const& rect) { return fromMono(image.convertToFormat(QImage::Format_Mono), rect); } BinaryImage BinaryImage::fromIndexed8( QImage const& image, QRect const& rect, int const threshold) { int const width = rect.width(); int const height = rect.height(); int const src_bpl = image.bytesPerLine(); uint8_t const* src_line = image.bits(); src_line += rect.top() * src_bpl + rect.left(); BinaryImage dst(width, height); int const dst_wpl = dst.wordsPerLine(); uint32_t* dst_line = dst.data(); int const last_word_idx = (width - 1) >> 5; int const last_word_bits = width - (last_word_idx << 5); int const last_word_unused_bits = 32 - last_word_bits; int const num_colors = image.numColors(); 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) { uint8_t const* 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. uint8_t const* 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; } static inline uint32_t thresholdRgb32(QRgb const c, int const threshold) { // gray = (R * 11 + G * 16 + B * 5) / 32; // return (gray < threshold) ? 1 : 0; int const sum = qRed(c)*11 + qGreen(c)*16 + qBlue(c)*5; return (sum < threshold * 32) ? 1 : 0; } BinaryImage BinaryImage::fromRgb32( QImage const& image, QRect const& rect, int const threshold) { int const width = rect.width(); int const height = rect.height(); assert(image.bytesPerLine() % 4 == 0); int const src_wpl = image.bytesPerLine() / 4; QRgb const* src_line = (QRgb const*)image.bits(); src_line += rect.top() * src_wpl + rect.left(); BinaryImage dst(width, height); int const dst_wpl = dst.wordsPerLine(); uint32_t* dst_line = dst.data(); int const last_word_idx = (width - 1) >> 5; int const last_word_bits = width - (last_word_idx << 5); int const last_word_unused_bits = 32 - last_word_bits; for (int i = height; i > 0; --i) { for (int j = 0; j < last_word_idx; ++j) { QRgb const* 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. QRgb const* 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; } static inline uint32_t thresholdArgbPM(QRgb const pm, int const threshold) { int const 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; int const sum = qRed(pm)*(255*11) + qGreen(pm)*(255*16) + qBlue(pm)*(255*5); return (sum < alpha * threshold * 32) ? 1 : 0; } BinaryImage BinaryImage::fromArgb32Premultiplied( QImage const& image, QRect const& rect, int const threshold) { int const width = rect.width(); int const height = rect.height(); assert(image.bytesPerLine() % 4 == 0); int const src_wpl = image.bytesPerLine() / 4; QRgb const* src_line = (QRgb const*)image.bits(); src_line += rect.top() * src_wpl + rect.left(); BinaryImage dst(width, height); int const dst_wpl = dst.wordsPerLine(); uint32_t* dst_line = dst.data(); int const last_word_idx = (width - 1) >> 5; int const last_word_bits = width - (last_word_idx << 5); int const last_word_unused_bits = 32 - last_word_bits; for (int i = height; i > 0; --i) { for (int j = 0; j < last_word_idx; ++j) { QRgb const* 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. QRgb const* 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; } static inline uint32_t thresholdRgb16(uint16_t const c16, int const threshold) { int const c = c16; // rgb16: RRRRR GGGGGG BBBBB // 43210 543210 43210 // r8 = r5 * 8 + r5 / 4 = RRRRR RRR // 43210 432 int const r8 = ((c >> 8) & 0xF8) | ((c >> 13) & 0x07); // g8 = g6 * 4 + g6 / 16 = GGGGGG GG // 543210 54 int const g8 = ((c >> 3) & 0xFC) | ((c >> 9) & 0x03); // b8 = b5 * 8 + b5 / 4 = BBBBB BBB // 43210 432 int const b8 = ((c << 3) & 0xF8) | ((c >> 2) & 0x07); // gray = (R * 11 + G * 16 + B * 5) / 32; // return (gray < threshold) ? 1 : 0; int const sum = r8*11 + g8*16 + b8*5; return (sum < threshold * 32) ? 1 : 0; } BinaryImage BinaryImage::fromRgb16( QImage const& image, QRect const& rect, int const threshold) { int const width = rect.width(); int const height = rect.height(); assert(image.bytesPerLine() % 4 == 0); int const src_wpl = image.bytesPerLine() / 2; uint16_t const* src_line = (uint16_t const*)image.bits(); BinaryImage dst(width, height); int const dst_wpl = dst.wordsPerLine(); uint32_t* dst_line = dst.data(); int const last_word_idx = (width - 1) >> 5; int const last_word_bits = width - (last_word_idx << 5); for (int i = height; i > 0; --i) { for (int j = 0; j < last_word_idx; ++j) { uint16_t const* 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. uint16_t const* 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; } /** * \brief Determines if the line is either completely black or completely white. * * \param line The line. * \param last_word_idx Index of the last (possibly incomplete) word. * \param last_word_mask The mask to by applied to the last word. * \param modifier If 0, this function check if the line is completely black. * If ~uint32_t(0), this function checks if the line is completely white. */ bool BinaryImage::isLineMonotone( uint32_t const* const line, int const last_word_idx, uint32_t const last_word_mask, uint32_t const modifier) { for (int i = 0; i < last_word_idx; ++i) { uint32_t const word = line[i] ^ modifier; if (word) { return false; } } // The last (possibly incomplete) word. int const word = (line[last_word_idx] ^ modifier) & last_word_mask; if (word) { return false; } return true; } int BinaryImage::leftmostBitOffset( uint32_t const* const line, int const offset_limit, uint32_t const modifier) { int const num_words = (offset_limit + 31) >> 5; int bit_offset = offset_limit; uint32_t const* pword = line; for (int i = 0; i < num_words; ++i, ++pword) { uint32_t const word = *pword ^ modifier; if (word) { bit_offset = (i << 5) + countMostSignificantZeroes(word); break; } } return std::min(bit_offset, offset_limit); } int BinaryImage::rightmostBitOffset( uint32_t const* const line, int const offset_limit, uint32_t const modifier) { int const num_words = (offset_limit + 31) >> 5; int bit_offset = offset_limit; uint32_t const* pword = line - 1; // line points to last_word_idx, which we skip for (int i = 0; i < num_words; ++i, --pword) { uint32_t const word = *pword ^ modifier; if (word) { bit_offset = (i << 5) + countLeastSignificantZeroes(word); break; } } return std::min(bit_offset, offset_limit); } bool operator==(BinaryImage const& lhs, BinaryImage const& 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. return false; } uint32_t const* lhs_line = lhs.data(); uint32_t const* rhs_line = rhs.data(); int const lhs_wpl = lhs.wordsPerLine(); int const rhs_wpl = rhs.wordsPerLine(); int const last_bit_idx = lhs.width() - 1; int const last_word_idx = last_bit_idx / 32; uint32_t const 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; } return true; } /*====================== BinaryIamge::SharedData ========================*/ void BinaryImage::SharedData::unref() const { if (!m_refCounter.deref()) { this->~SharedData(); free((void*)this); } } void* BinaryImage::SharedData::operator new(size_t, NumWords const num_words) { SharedData* sd = 0; void* addr = malloc(((char*)&sd->m_data[0] - (char*)sd) + num_words.numWords * 4); if (!addr) { throw std::bad_alloc(); } return addr; } void BinaryImage::SharedData::operator delete(void* addr, NumWords) { free(addr); } } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/BinaryImage.h000066400000000000000000000211311271170121200217630ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_BINARYIMAGE_H_ #define IMAGEPROC_BINARYIMAGE_H_ #include "BWColor.h" #include "BinaryThreshold.h" #include #include #include #include class QImage; namespace imageproc { /** * \brief An image consisting of black and white pixels. * * The reason for having a separate image class instead of just using * QImage is convenience and efficiency concerns. BinaryImage is a * sequence of 32bit words with bytes and bits arranged in such a way * that * \code * word << x * word >> x * \endcode * are equivalent to shifting a group of pixels to the left and to the * right respectively.\n * Additionally, unlinke QImage, BinaryImage doesn't have a palette, * 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(). */ 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(BinaryImage const& 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( QImage const& 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( QImage const& image, QRect const& 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=(BinaryImage const& 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(QRect const& 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(QRect const& 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(QRect const& outer_rect, QRect const& 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(QRect const& 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(QRect const& rect) const; /** * \brief Calculates the bounding box of either black or white content. */ QRect contentBoundingBox(BWColor content_color = BLACK) const; 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. */ uint32_t const* 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(QColor const& color) const; private: class SharedData; BinaryImage(int width, int height, SharedData* data); void copyIfShared(); void fillRectImpl(uint32_t* data, QRect const& rect, BWColor color); static BinaryImage fromMono(QImage const& image); static BinaryImage fromMono(QImage const& image, QRect const& rect); static BinaryImage fromMonoLSB(QImage const& image); static BinaryImage fromMonoLSB(QImage const& image, QRect const& rect); static BinaryImage fromIndexed8( QImage const& image, QRect const& rect, int threshold); static BinaryImage fromRgb32( QImage const& image, QRect const& rect, int threshold); static BinaryImage fromArgb32Premultiplied( QImage const& image, QRect const& rect, int threshold); static BinaryImage fromRgb16( QImage const& image, QRect const& rect, int threshold); static bool isLineMonotone( uint32_t const* line, int last_word_idx, uint32_t last_word_mask, uint32_t modifier); static int leftmostBitOffset( uint32_t const* line, int offset_limit, uint32_t modifier); static int rightmostBitOffset( uint32_t const* line, int offset_limit, uint32_t modifier); SharedData* m_pData; int m_width; int m_height; int m_wpl; // words per line }; inline void swap(BinaryImage& o1, BinaryImage& o2) { o1.swap(o2); } inline BinaryImage BinaryImage::release() { BinaryImage new_img; new_img.swap(*this); return new_img; } /** * \brief Compares image data. */ bool operator==(BinaryImage const& lhs, BinaryImage const& rhs); /** * \brief Compares image data. */ inline bool operator!=(BinaryImage const& lhs, BinaryImage const& rhs) { return !(lhs == rhs); } } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/BinaryThreshold.cpp000066400000000000000000000115101271170121200232300ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "BinaryThreshold.h" #include "Grayscale.h" #include "Morphology.h" #include #include #include #include #include #include namespace imageproc { BinaryThreshold BinaryThreshold::otsuThreshold(QImage const& image) { return otsuThreshold(GrayscaleHistogram(image)); } BinaryThreshold BinaryThreshold::otsuThreshold(GrayscaleHistogram const& 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; } int const total_pixels = pixels_by_threshold[255]; int64_t const 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) { int const pixels_below = pixels_by_threshold[i]; int const pixels_above = total_pixels - pixels_below; if (pixels_below > 0 && pixels_above > 0) { // prevent division by zero int64_t const moment_below = moment_by_threshold[i]; int64_t const moment_above = total_moment - moment_below; double const mean_below = (double)moment_below / pixels_below; double const mean_above = (double)moment_above / pixels_above; double const mean_diff = mean_below - mean_above; double const 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; // The middle between the two. return BinaryThreshold((first_best_threshold + last_best_threshold) >> 1); } BinaryThreshold BinaryThreshold::mokjiThreshold( QImage const& image, unsigned const max_edge_width, unsigned const 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"); } GrayImage const gray(image); int const 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)); int const w = image.width(); int const h = image.height(); unsigned char const* src_line = gray.data(); int const src_stride = gray.stride(); unsigned char const* dilated_line = dilated.data(); int const 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) { unsigned const pixel = src_line[x]; unsigned const darkest_neighbor = dilated_line[x]; assert(darkest_neighbor <= pixel); ++matrix[darkest_neighbor][pixel]; } 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); unsigned const val = matrix[m][n]; nominator += (m + n) * val; denominator += val; } } if (denominator == 0) { return BinaryThreshold(128); } double const threshold = 0.5 * nominator / denominator; return BinaryThreshold((int)(threshold + 0.5)); } } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/BinaryThreshold.h000066400000000000000000000050101271170121200226730ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_BINARYTHRESHOLD_H_ #define IMAGEPROC_BINARYTHRESHOLD_H_ #include "BWColor.h" class QImage; namespace imageproc { class GrayscaleHistogram; /** * \brief Defines the gray level threshold that separates black from white. * * Gray levels in range of [0, threshold) are considered black, while * levels in range of [threshold, 255] are considered white. The threshold * 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(QImage const& image); /** * \brief Finds the threshold using Otsu’s thresholding method. */ static BinaryThreshold otsuThreshold(GrayscaleHistogram const& 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( QImage const& image, unsigned max_edge_width = 3, unsigned min_edge_magnitude = 20); explicit 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 scantailor-RELEASE_0_9_12_2/imageproc/BitOps.cpp000066400000000000000000000064251271170121200213400ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "BitOps.h" namespace imageproc { namespace detail { unsigned char const 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 }; unsigned char const 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 }; } // namespace detail } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/BitOps.h000066400000000000000000000113741271170121200210040ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_BITOPS_H_ #define IMAGEPROC_BITOPS_H_ namespace imageproc { namespace detail { extern unsigned char const bitCounts[256]; extern unsigned char const reversedBits[256]; template class NonZeroBits { public: static unsigned char count(T val) { return bitCounts[static_cast(val)] + NonZeroBits::count(val >> 8); } }; template class NonZeroBits { public: static unsigned char count(T val) { return bitCounts[static_cast(val)]; } }; template struct ReverseBytes { static T result(T const val) { int const left_shift = (TotalBytes - Offset - 1) * 8; int const right_shift = Offset * 8; typedef unsigned char Byte; Byte const left_byte = static_cast(val >> left_shift); Byte const right_byte = static_cast(val >> right_shift); T res( ReverseBytes< T, TotalBytes, Offset + 1, Offset == TotalBytes / 2 >::result(val) ); res |= T(reversedBits[left_byte]) << right_shift; res |= T(reversedBits[right_byte]) << left_shift; return res; } }; template struct ReverseBytes { static T result(T) { return T(); } }; template struct ReverseBytes { static T result(T const val) { typedef unsigned char Byte; return T(reversedBits[static_cast(val)]); } }; template struct StripedMaskMSB1 { static T const value = (((T(1) << STRIPE_LEN) - 1) << (BITS_DONE + STRIPE_LEN)) | StripedMaskMSB1< T, STRIPE_LEN, BITS_DONE + STRIPE_LEN * 2, BITS_DONE + STRIPE_LEN * 2 < sizeof(T) * 8 >::value; }; template struct StripedMaskMSB1 { static T const value = 0; }; template struct StripedMaskLSB1 { static T const value = (((T(1) << STRIPE_LEN) - 1) << BITS_DONE) | StripedMaskLSB1< T, STRIPE_LEN, BITS_DONE + STRIPE_LEN * 2, BITS_DONE + STRIPE_LEN * 2 < sizeof(T) * 8 >::value; }; template struct StripedMaskLSB1 { static T const value = 0; }; 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); } }; template struct MostSignificantZeroes { static int reduce(T val, int count) { return count - 1; } }; 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); } }; template struct LeastSignificantZeroes { static int reduce(T val, int count) { return count - 1; } }; } // namespace detail template int countNonZeroBits(T const val) { return detail::NonZeroBits::count(val); } template T reverseBits(T const val) { return detail::ReverseBytes::result(val); } template int countMostSignificantZeroes(T const val) { static int const total_bits = sizeof(T) * 8; int zeroes = total_bits; if (val) { zeroes = detail::MostSignificantZeroes::reduce( val, zeroes ); } return zeroes; } template int countLeastSignificantZeroes(T const val) { static int const total_bits = sizeof(T) * 8; int zeroes = total_bits; if (val) { zeroes = detail::LeastSignificantZeroes::reduce( val, zeroes ); } return zeroes; } } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/ByteOrder.h000066400000000000000000000016551271170121200215040ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_BYTEORDER_H_ #define IMAGEPROC_BYTEORDER_H_ #ifdef _WIN32 #include #else #include #endif #endif scantailor-RELEASE_0_9_12_2/imageproc/CMakeLists.txt000066400000000000000000000031731271170121200221710ustar00rootroot00000000000000PROJECT(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 DentFinder.cpp DentFinder.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 LeastSquaresFit.cpp LeastSquaresFit.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 ) SOURCE_GROUP(Sources FILES ${sources}) ADD_LIBRARY(imageproc STATIC ${sources}) ADD_SUBDIRECTORY(tests) scantailor-RELEASE_0_9_12_2/imageproc/ColorForId.h000066400000000000000000000030571271170121200216050ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_COLOR_FOR_ID_H_ #define IMAGEPROC_COLOR_FOR_ID_H_ #include "BitOps.h" #include namespace imageproc { /** * \brief Generates a color corresponding to a particular numeric ID. * * Colors for IDs that are numerically close will tend to be significantly * different. Positive IDs are handled better. */ template QColor colorForId(T id) { int const bits_unused = countMostSignificantZeroes(id); int const bits_used = sizeof(T) * 8 - bits_unused; T const reversed = reverseBits(id) >> bits_unused; T const mask = (T(1) << bits_used) - 1; double const H = 0.99 * double(reversed + 1) / (mask + 1); double const S = 1.0; double const V = 1.0; QColor color; color.setHsvF(H, S, V); return color; } } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/ColorInterpolation.cpp000066400000000000000000000024531271170121200237630ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ColorInterpolation.h" #include namespace imageproc { QColor colorInterpolation(QColor const& from, QColor const& to, double dist) { 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 const r = r1 + (r2 - r1) * dist; qreal const g = g1 + (g2 - g1) * dist; qreal const b = b1 + (b2 - b1) * dist; qreal const a = a1 + (a2 - a1) * dist; return QColor::fromRgbF(r, g, b, a); } } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/ColorInterpolation.h000066400000000000000000000023251271170121200234260ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_COLORINTERPOLATION_H_ #define IMAGEPROC_COLORINTERPOLATION_H_ #include namespace imageproc { /** * \brief Returns a color between the provided two. * * Returns a color between \p from and \p to according to \p dist. * \p dist 0 corresponds to \p from, while \p dist 1 corresponds to \p to. */ QColor colorInterpolation(QColor const& from, QColor const& to, double dist); } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/ColorMixer.h000066400000000000000000000133421271170121200216640ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_COLOR_MIXER_H_ #define IMAGEPROC_COLOR_MIXER_H_ #include #include namespace imageproc { namespace color_mixer_impl { template struct Switcher { typedef typename Mixer::accum_type accum_type; typedef typename Mixer::result_type result_type; static result_type mix(Mixer const* mixer, accum_type total_weight) { return mixer->nonIntegerMix(total_weight); } }; template struct Switcher { typedef typename Mixer::accum_type accum_type; typedef typename Mixer::result_type result_type; static result_type mix(Mixer const* mixer, accum_type total_weight) { return mixer->integerMix(total_weight); } }; } // namespace color_mixer_impl template class GrayColorMixer { template friend struct color_mixer_impl::Switcher; public: typedef AccumType accum_type; typedef uint8_t result_type; GrayColorMixer() : m_accum() {} void add(uint8_t gray_level, AccumType weight) { m_accum += AccumType(gray_level) * weight; } result_type mix(AccumType total_weight) const { 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 { return static_cast(m_accum / total_weight + AccumType(0.5)); } uint8_t integerMix(AccumType total_weight) const { AccumType const half_weight = total_weight >> 1; AccumType const mixed = (m_accum + half_weight) / total_weight; return static_cast(mixed); } AccumType m_accum; }; 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() {} 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; } result_type mix(AccumType total_weight) const { 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 { AccumType const scale = 1 / total_weight; uint32_t const r = uint32_t(AccumType(0.5) + m_redAccum * scale); uint32_t const g = uint32_t(AccumType(0.5) + m_greenAccum * scale); uint32_t const b = uint32_t(AccumType(0.5) + m_blueAccum * scale); return (r << 16) | (g << 8) | b; }; uint32_t integerMix(AccumType total_weight) const { AccumType const half_weight = total_weight >> 1; uint32_t const r = uint32_t((m_redAccum + half_weight) / total_weight); uint32_t const g = uint32_t((m_greenAccum + half_weight) / total_weight); uint32_t const b = uint32_t((m_blueAccum + half_weight) / total_weight); return (r << 16) | (g << 8) | b; }; AccumType m_redAccum; AccumType m_greenAccum; AccumType m_blueAccum; }; 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() {} void add(uint32_t argb, AccumType weight) { m_alphaAccum += AccumType((argb >> 24) & 0xFF) * weight; m_redAccum += AccumType((argb >> 16) & 0xFF) * weight; m_greenAccum += AccumType((argb >> 8) & 0xFF) * weight; m_blueAccum += AccumType(argb & 0xFF) * weight; } result_type mix(AccumType total_weight) const { 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 { AccumType const scale = 1 / total_weight; uint32_t const a = uint32_t(AccumType(0.5) + m_alphaAccum * scale); uint32_t const r = uint32_t(AccumType(0.5) + m_redAccum * scale); uint32_t const g = uint32_t(AccumType(0.5) + m_greenAccum * scale); uint32_t const b = uint32_t(AccumType(0.5) + m_blueAccum * scale); return (a << 24) | (r << 16) | (g << 8) | b; }; uint32_t integerMix(AccumType total_weight) const { AccumType const half_weight = total_weight >> 1; uint32_t const a = uint32_t((m_alphaAccum + half_weight) / total_weight); uint32_t const r = uint32_t((m_redAccum + half_weight) / total_weight); uint32_t const g = uint32_t((m_greenAccum + half_weight) / total_weight); uint32_t const b = uint32_t((m_blueAccum + half_weight) / total_weight); return (a << 24) | (r << 16) | (g << 8) | b; }; AccumType m_alphaAccum; AccumType m_redAccum; AccumType m_greenAccum; AccumType m_blueAccum; }; } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/ConnComp.h000066400000000000000000000032201271170121200213070ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_CONNCOMP_H_ #define IMAGEPROC_CONNCOMP_H_ #include namespace imageproc { /** * \brief Represents a connected group of pixels. */ class ConnComp { public: ConnComp() : m_pixCount(0) {} ConnComp(QPoint const& seed, QRect const& rect, int pix_count) : m_seed(seed), m_rect(rect), m_pixCount(pix_count) {} 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. */ QPoint const& seed() const { return m_seed; } int width() const { return m_rect.width(); } int height() const { return m_rect.height(); } QRect const& rect() const { return m_rect; } int pixCount() const { return m_pixCount; } private: QPoint m_seed; QRect m_rect; int m_pixCount; }; } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/ConnCompEraser.cpp000066400000000000000000000200731271170121200230110ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* The code is based on Paul Heckbert's stack-based seed fill algorithm * from "Graphic Gems", ed. Andrew Glassner, Academic Press, 1990. * This version is optimized to elliminate all multiplications. */ #include "ConnCompEraser.h" #include "ConnComp.h" #include "BitOps.h" #include #include #include #include #include namespace imageproc { struct ConnCompEraser::BBox { int xmin; int xmax; int ymin; int ymax; BBox(int x, int y) : xmin(x), xmax(x), ymin(y), ymax(y) {} int width() const { return xmax - xmin + 1; } int height() const { return ymax - ymin + 1; } }; inline uint32_t ConnCompEraser::getBit(uint32_t const* const line, int const x) { uint32_t const mask = (uint32_t(1) << 31) >> (x & 31); return line[x >> 5] & mask; } inline void ConnCompEraser::clearBit(uint32_t* const line, int const x) { uint32_t const mask = (uint32_t(1) << 31) >> (x & 31); line[x >> 5] &= ~mask; } ConnCompEraser::ConnCompEraser(BinaryImage const& image, Connectivity conn) : m_image(image), m_pLine(0), 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(). } ConnComp ConnCompEraser::nextConnComp() { if (!moveToNextBlackPixel()) { return ConnComp(); } if (m_connectivity == CONN4) { return eraseConnComp4(); } else { return eraseConnComp8(); } } void ConnCompEraser::pushSegSameDir(Segment const& 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); int const new_dy = seg.dy; int const new_dy_wpl = seg.dy_wpl; int const 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(Segment const& 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); int const new_dy = -seg.dy; int const new_dy_wpl = -seg.dy_wpl; int const 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); } 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; uint32_t const* pword = line + (m_x >> 5); // Stop word is a last word in line that holds data. int const last_bit_idx = m_width - 1; uint32_t const* p_stop_word = line + (last_bit_idx >> 5); uint32_t const 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) { int const 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) { int const shift = countMostSignificantZeroes(word); m_x = ((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) { int const shift = countMostSignificantZeroes(word); m_x = ((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; } return false; } ConnComp ConnCompEraser::eraseConnComp4() { pushInitialSegments(); BBox bbox(m_x, m_y); int pix_count = 0; while (!m_segStack.empty()) { // Pop a segment off the stack. Segment const seg(m_segStack.top()); m_segStack.pop(); int const 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); } QRect rect(bbox.xmin, bbox.ymin, bbox.width(), bbox.height()); return ConnComp(QPoint(m_x, m_y), rect, pix_count); } ConnComp ConnCompEraser::eraseConnComp8() { pushInitialSegments(); BBox bbox(m_x, m_y); int pix_count = 0; while (!m_segStack.empty()) { // Pop a segment off the stack. Segment const seg(m_segStack.top()); m_segStack.pop(); int const 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); } QRect rect(bbox.xmin, bbox.ymin, bbox.width(), bbox.height()); return ConnComp(QPoint(m_x, m_y), rect, pix_count); } } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/ConnCompEraser.h000066400000000000000000000056661271170121200224710ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_CONNCOMPERASER_H_ #define IMAGEPROC_CONNCOMPERASER_H_ #include "NonCopyable.h" #include "Connectivity.h" #include "ConnComp.h" #include "BinaryImage.h" #include #include namespace imageproc { class ConnComp; /** * \brief Erases connected components one by one and returns their bounding boxes. */ class 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(BinaryImage const& 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 Returns the image in its present state. * * Every time nextConnComp() is called, a connected component * is erased from the image, assuming there was one. */ BinaryImage const& image() const { return m_image; } private: struct Segment { uint32_t* line; /**< Pointer to the beginning of the line. */ int xleft; /**< Leftmost pixel to process. */ int xright; /**< Rightmost pixel to process. */ int y; /**< 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. */ }; struct BBox; void pushSegSameDir(Segment const& seg, int xleft, int xright, BBox& bbox); void pushSegInvDir(Segment const& seg, int xleft, int xright, BBox& bbox); void pushInitialSegments(); bool moveToNextBlackPixel(); ConnComp eraseConnComp4(); ConnComp eraseConnComp8(); static uint32_t getBit(uint32_t const* line, int x); static void clearBit(uint32_t* line, int x); BinaryImage m_image; uint32_t* m_pLine; int const m_width; int const m_height; int const m_wpl; Connectivity const m_connectivity; std::stack m_segStack; int m_x; int m_y; }; } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/ConnCompEraserExt.cpp000066400000000000000000000056321271170121200234760ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ConnCompEraserExt.h" #include "RasterOp.h" #include #ifndef Q_MOC_RUN #include #endif #include #include #include namespace imageproc { ConnCompEraserExt::ConnCompEraserExt( BinaryImage const& image, Connectivity const conn) : 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. QRect const& rect = m_lastCC.rect(); BinaryImage const& src = m_eraser.image(); size_t const src_wpl = src.wordsPerLine(); size_t const dst_wpl = m_lastImage.wordsPerLine(); size_t const first_word_idx = rect.left() / 32; // Note: rect.right() == rect.x() + rect.width() - 1 size_t const span_length = (rect.right() + 31) / 32 - first_word_idx; size_t const src_initial_offset = rect.top() * src_wpl + first_word_idx; size_t const dst_initial_offset = rect.top() * dst_wpl + first_word_idx; uint32_t const* 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(); return m_lastCC; } BinaryImage ConnCompEraserExt::computeConnCompImage() const { if (m_lastCC.isNull()) { return BinaryImage(); } return computeDiffImage(m_lastCC.rect()); } BinaryImage ConnCompEraserExt::computeConnCompImageAligned(QRect* rect) const { if (m_lastCC.isNull()) { return BinaryImage(); } QRect r(m_lastCC.rect()); r.setX((r.x() >> 5) << 5); if (rect) { *rect = r; } return computeDiffImage(r); } BinaryImage ConnCompEraserExt::computeDiffImage(QRect const& 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()); return diff; } } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/ConnCompEraserExt.h000066400000000000000000000060061271170121200231370ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_CONNCOMPERASEREXT_H_ #define IMAGEPROC_CONNCOMPERASEREXT_H_ #include "NonCopyable.h" #include "Connectivity.h" #include "ConnCompEraser.h" #include "ConnComp.h" #include "BinaryImage.h" class QRect; namespace imageproc { /** * \brief Same as ConnCompEraser, except it provides images of connected components. */ class 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(BinaryImage const& 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 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 = 0) const; private: ConnCompEraser m_eraser; BinaryImage computeDiffImage(QRect const& 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; }; } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/Connectivity.h000066400000000000000000000022631271170121200222570ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_CONNECTIVITY_H_ #define IMAGEPROC_CONNECTIVITY_H_ 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, /** All 8 neighbours are considered to be connected. */ CONN8 }; } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/ConnectivityMap.cpp000066400000000000000000000273571271170121200232630ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ConnectivityMap.h" #include "BinaryImage.h" #include "InfluenceMap.h" #include "BitOps.h" #ifndef Q_MOC_RUN #include #endif #include #include #include #include #include #include namespace imageproc { uint32_t const ConnectivityMap::BACKGROUND = ~uint32_t(0); uint32_t const ConnectivityMap::UNTAGGED_FG = BACKGROUND - 1; ConnectivityMap::ConnectivityMap() : m_pData(0), m_size(), m_stride(0), m_maxLabel(0) { } ConnectivityMap::ConnectivityMap(QSize const& size) : m_pData(0), m_size(size), m_stride(0), m_maxLabel(0) { if (m_size.isEmpty()) { return; } int const width = m_size.width(); int const height = m_size.height(); m_data.resize((width + 2) * (height + 2), 0); m_stride = width + 2; m_pData = &m_data[0] + 1 + m_stride; } ConnectivityMap::ConnectivityMap( BinaryImage const& image, Connectivity const conn) : m_pData(0), m_size(image.size()), m_stride(0), m_maxLabel(0) { if (m_size.isEmpty()) { return; } int const width = m_size.width(); int const height = m_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; int const dst_stride = m_stride; uint32_t const* src = image.data(); int const src_stride = image.wordsPerLine(); uint32_t const 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); } ConnectivityMap::ConnectivityMap(ConnectivityMap const& other) : m_data(other.m_data), m_pData(0), 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; } } ConnectivityMap::ConnectivityMap(InfluenceMap const& imap) : m_pData(0), 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); } ConnectivityMap& ConnectivityMap::operator=(ConnectivityMap const& other) { ConnectivityMap(other).swap(*this); return *this; } ConnectivityMap& ConnectivityMap::operator=(InfluenceMap const& imap) { if (m_size == imap.size() && !m_size.isEmpty()) { // Common case optimization. copyFromInfluenceMap(imap); } else { ConnectivityMap(imap).swap(*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); } void ConnectivityMap::addComponent(BinaryImage const& image) { if (m_size != image.size()) { throw std::invalid_argument("ConnectivityMap::addComponent: sizes don't match"); } if (m_size.isEmpty()) { return; } int const width = m_size.width(); int const height = m_size.height(); uint32_t* dst = m_pData; int const dst_stride = m_stride; uint32_t const* src = image.data(); int const src_stride = image.wordsPerLine(); uint32_t const new_label = m_maxLabel + 1; uint32_t const 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; } QImage ConnectivityMap::visualized(QColor bgcolor) const { if (m_size.isEmpty()) { return QImage(); } int const width = m_size.width(); int const height = m_size.height(); // Convert to premultiplied RGBA. bgcolor = bgcolor.toRgb(); bgcolor.setRedF(bgcolor.redF() * bgcolor.alphaF()); bgcolor.setGreenF(bgcolor.greenF() * bgcolor.alphaF()); bgcolor.setBlueF(bgcolor.blueF() * bgcolor.alphaF()); QImage dst(m_size, QImage::Format_ARGB32); dst.fill(bgcolor.rgba()); uint32_t const* src_line = m_pData; int const src_stride = m_stride; uint32_t* dst_line = reinterpret_cast(dst.bits()); int const dst_stride = dst.bytesPerLine() / sizeof(uint32_t); for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { uint32_t const val = src_line[x]; if (val == 0) { continue; } int const bits_unused = countMostSignificantZeroes(val); uint32_t const reversed = reverseBits(val) >> bits_unused; uint32_t const mask = ~uint32_t(0) >> bits_unused; double const H = 0.99 * (double(reversed) / mask); double const S = 1.0; double const 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; } void ConnectivityMap::copyFromInfluenceMap(InfluenceMap const& imap) { assert(!imap.size().isEmpty()); assert(imap.size() == m_size); int const width = m_size.width() + 2; int const height = m_size.height() + 2; uint32_t* dst = &m_data[0]; InfluenceMap::Cell const* src = imap.paddedData(); for (int todo = width * height; todo > 0; --todo) { *dst = src->label; ++dst; ++src; } } void ConnectivityMap::assignIds(Connectivity const conn) { uint32_t const num_initial_tags = initialTagging(); std::vector table(num_initial_tags, 0); switch (conn) { case CONN4: spreadMin4(); break; case CONN8: spreadMin8(); break; } 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); m_maxLabel = next_label - 1; } /** * Tags every object pixel that has a non-object pixel to the left. */ uint32_t ConnectivityMap::initialTagging() { int const width = m_size.width(); int const height = m_size.height(); uint32_t next_label = 1; uint32_t* line = m_pData; int const 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; } } } return next_label - 1; } void ConnectivityMap::spreadMin4() { int const width = m_size.width(); int const height = m_size.height(); int const 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; } 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; } uint32_t const 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. uint32_t const 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); } void ConnectivityMap::spreadMin8() { int const width = m_size.width(); int const height = m_size.height(); int const 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; } prev_line = line; line -= stride; FastQueue queue; // Bottom to top. for (int y = height - 1; y >= 0; --y) { for (int x = width - 1; x >= 0; --x) { if (line[x] == BACKGROUND) { continue; } uint32_t const new_val = std::min( std::min(prev_line[x - 1], prev_line[x]), std::min(prev_line[x + 1], line[x + 1]) ); 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. uint32_t const 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]); } } prev_line = line; line -= stride; } processQueue8(queue); } void ConnectivityMap::processNeighbor( FastQueue& queue, uint32_t const 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); } } void ConnectivityMap::processQueue4(FastQueue& queue) { int const stride = m_stride; while (!queue.empty()) { uint32_t* p = queue.front(); queue.pop(); uint32_t const this_val = *p; // Northern neighbor. p -= stride; 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); } } void ConnectivityMap::processQueue8(FastQueue& queue) { int const stride = m_stride; while (!queue.empty()) { uint32_t* p = queue.front(); queue.pop(); uint32_t const this_val = *p; // Northern neighbor. p -= stride; processNeighbor(queue, this_val, p); // North-eastern neighbor. ++p; processNeighbor(queue, this_val, p); // 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); // 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); } } void ConnectivityMap::markUsedIds(std::vector& used_map) const { int const width = m_size.width(); int const height = m_size.height(); int const stride = m_stride; uint32_t const* 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; } } } void ConnectivityMap::remapIds(std::vector const& map) { BOOST_FOREACH(uint32_t& label, m_data) { if (label == BACKGROUND) { label = 0; } else { label = map[label - 1]; } } } } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/ConnectivityMap.h000066400000000000000000000143731271170121200227220ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_CONNECTIVITY_MAP_H_ #define IMAGEPROC_CONNECTIVITY_MAP_H_ #include "Connectivity.h" #include "FastQueue.h" #include #include #include #include #include class QImage; namespace imageproc { class BinaryImage; class InfluenceMap; /** * \brief Assigns each pixel a label that identifies the connected component * it belongs to. * * Such a map makes it possible to quickly tell if a pair of pixels are * connected or not. * * Background (white) pixels are assigned the label of zero, and the remaining * 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. */ ConnectivityMap(QSize const& size); /** * \brief Labels components in a binary image. */ ConnectivityMap(BinaryImage const& 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, T const* data, int units_per_line, Connectivity conn); ConnectivityMap(ConnectivityMap const& other); /** * \brief Constructs a connectivity map from an influence map. */ explicit ConnectivityMap(InfluenceMap const& imap); ConnectivityMap& operator=(ConnectivityMap const& other); /** * \brief Converts an influence map to a connectivity map. */ ConnectivityMap& operator=(InfluenceMap const& 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(BinaryImage const& image); /** * \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 const* 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. */ uint32_t const* paddedData() const { return m_pData ? &m_data[0] : 0; } /** * \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] : 0; } /** * \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 bgcolor Background color. Transparency is supported. */ QImage visualized(QColor bgcolor = Qt::black) const; private: void copyFromInfluenceMap(InfluenceMap const& 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(std::vector const& map); void expandImpl(BinaryImage const* mask); static uint32_t const BACKGROUND; static uint32_t const UNTAGGED_FG; std::vector m_data; uint32_t* m_pData; QSize m_size; int m_stride; uint32_t m_maxLabel; }; inline void swap(ConnectivityMap& o1, ConnectivityMap& o2) { o1.swap(o2); } template ConnectivityMap::ConnectivityMap( QSize const size, T const* src, int const src_stride, Connectivity const conn) : m_pData(0), m_size(size), m_stride(0), m_maxLabel(0) { if (size.isEmpty()) { return; } int const width = size.width(); int const 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; int const 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); } } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/Constants.cpp000066400000000000000000000023121271170121200221030ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Constants.h" namespace imageproc { namespace constants { double const SQRT_2 = 1.4142135623730950488016887242097; double const PI = 3.141592653589793; double const DEG2RAD = PI / 180.0; double const RAD2DEG = 180.0 / PI; double const INCH2MM = 25.4; double const MM2INCH = 1.0 / INCH2MM; double const DPM2DPI = 0.0254; double const DPI2DPM = 1.0 / DPM2DPI; } // namespace constants } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/Constants.h000066400000000000000000000026531271170121200215600ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_CONSTANTS_H_ #define IMAGEPROC_CONSTANTS_H_ namespace imageproc { namespace constants { extern double const PI; extern double const SQRT_2; /** * angle_rad = angle_deg * RED2RAD */ extern double const DEG2RAD; /** * angle_deg = angle_rad * RAD2DEG */ extern double const RAD2DEG; /** * mm = inch * INCH2MM */ extern double const INCH2MM; /** * inch = mm * MM2INCH */ extern double const MM2INCH; /** * dots_per_meter = dots_per_inch * DPI2DPM */ extern double const DPI2DPM; /** * dots_per_inch = dots_per_meter * DPM2DPI */ extern double const DPM2DPI; } // namespace constants } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/DentFinder.cpp000066400000000000000000000252061271170121200221600ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "DentFinder.h" #include "BinaryImage.h" #include "BWColor.h" #include namespace imageproc { struct DentFinder::ImgInfo { ImgInfo(BinaryImage const& src, BinaryImage& dst); uint32_t const* src_data; uint32_t* dst_data; int src_wpl; int dst_wpl; int width; int height; }; DentFinder::ImgInfo::ImgInfo(BinaryImage const& src, BinaryImage& dst) : src_data(src.data()), dst_data(dst.data()), src_wpl(src.wordsPerLine()), dst_wpl(dst.wordsPerLine()), width(src.width()), height(src.height()) { } imageproc::BinaryImage DentFinder::findDentsAndHoles(imageproc::BinaryImage const& src) { if (src.isNull()) { return BinaryImage(); } BinaryImage dst(src.size(), WHITE); ImgInfo info(src, dst); scanHorizontalLines(info); scanVerticalLines(info); scanSlashDiagonals(info); scanBackslashDiagonals(info); return dst; } uint32_t DentFinder::getPixel(uint32_t const* const src_line, int const x) { int const offset = x >> 5; int const shift = x & 31; uint32_t const msb = uint32_t(1) << 31; return src_line[offset] & (msb >> shift); } void DentFinder::transferPixel( uint32_t const* const src_line, uint32_t* const dst_line, int const x) { int const offset = x >> 5; int const shift = x & 31; uint32_t const msb = uint32_t(1) << 31; dst_line[offset] |= ~src_line[offset] & (msb >> shift); } void DentFinder::scanHorizontalLines(ImgInfo const info) { uint32_t const* src_line = info.src_data; uint32_t* dst_line = info.dst_data; for (int y = 0; y < info.height; ++y, src_line += info.src_wpl, dst_line += info.dst_wpl) { // Find the first black pixel. int first_black = -1; // TODO: rename to first_black_x for (int x = 0; x < info.width; ++x) { if (getPixel(src_line, x)) { first_black = x; break; } } if (first_black == -1) { // Only one black span on this line. continue; } // Continue until we encounter a white pixel. int first_black_end = -1; for (int x = first_black + 1; x < info.width; ++x) { if (!getPixel(src_line, x)) { first_black_end = x; break; } } if (first_black_end == -1) { // Only one black span on this line. continue; } // Find the last black pixel. int last_black = -1; for (int x = info.width - 1; x >= 0; --x) { if (getPixel(src_line, x)) { last_black = x; break; } } // We know we have at least one black pixel. assert(last_black != -1); if (first_black_end > last_black) { // Only one black span on this line. continue; } // Continue until we encounter a white pixel. int last_black_end = -1; for (int x = last_black - 1; x >= 0; --x) { if (!getPixel(src_line, x)) { last_black_end = x; break; } } assert(last_black_end != -1); // Every white pixel between the first and the last black span // becomes black in the destination image. for (int x = first_black_end; x <= last_black_end; ++x) { transferPixel(src_line, dst_line, x); } } } void DentFinder::scanVerticalLines(ImgInfo const info) { for (int x = 0; x < info.width; ++x) { int const offset = x >> 5; uint32_t const msb = uint32_t(1) << 31; uint32_t const mask = msb >> (x & 31); // Find the first black pixel. int first_black = -1; uint32_t const* p_src_first = info.src_data + offset; for (int y = 0; y < info.height; ++y, p_src_first += info.src_wpl) { if (*p_src_first & mask) { first_black = y; break; } } if (first_black == -1) { // Only one black span on this line. continue; } // Continue until we encounter a white pixel. int first_black_end = -1; for (int y = first_black + 1; y < info.height; ++y) { p_src_first += info.src_wpl; if (!(*p_src_first & mask)) { first_black_end = y; break; } } if (first_black_end == -1) { // Only one black span on this line. continue; } // Find the last black pixel. int last_black = -1; uint32_t const* p_src_last = info.src_data + info.height * info.src_wpl + offset; for (int y = info.height - 1; y >= 0; --y) { p_src_last -= info.src_wpl; if (*p_src_last & mask) { last_black = y; break; } } // We know we have at least one black pixel. assert(last_black != -1); if (first_black_end > last_black) { // Only one black span on this line. continue; } // Continue until we encounter a white pixel. int last_black_end = -1; for (int y = last_black - 1; y >= 0; --y) { p_src_last -= info.src_wpl; if (!(*p_src_first & mask)) { last_black_end = y; break; } } assert(last_black_end != -1); // Every white pixel between the first and the last black span // becomes black in the destination image. uint32_t* p_dst = info.dst_data + first_black_end * info.dst_wpl + offset; for (int y = first_black_end; y <= last_black_end; ++y) { *p_dst |= ~*p_src_first & mask; p_src_first += info.src_wpl; p_dst += info.dst_wpl; } } } void DentFinder::scanSlashDiagonals(ImgInfo const info) { // These are endpoint coordinates of our slash diagonals. int x0 = 0, y0 = 0; int x1 = 0, y1 = 0; /* +------------+ |(x1, y1)--+ | |(x0, y0) | | | | | | | | v | | +-------> | +------------+ */ while (x0 < info.width /*&& y1 < info.height*/) { do { // just to be able to break from it. // Find the first black pixel. int first_black_x = -1; int x = x0; int y = y0; uint32_t const* src_line = info.src_data + info.src_wpl * y; do { if (getPixel(src_line, x)) { first_black_x = x; break; } ++x; --y; src_line -= info.src_wpl; } while (x < x1); if (first_black_x == -1) { // No black pixels on this line. break; } // Continue until we encounter a white pixel. int first_black_end_x = -1; int first_black_end_y = -1; do { if (!getPixel(src_line, x)) { first_black_end_x = x; first_black_end_y = y; break; } ++x; --y; src_line -= info.src_wpl; } while (x < x1); if (first_black_end_x == -1) { // Only one black span on this line. break; } // Find the last black pixel. int last_black_x = -1; x = x1; y = y1; src_line = info.src_data + info.src_wpl * y; do { if (getPixel(src_line, x)) { last_black_x = x; break; } --x; ++y; src_line += info.src_wpl; } while (x >= x0); // We know we have at least one black pixel. assert(last_black_x != -1); if (first_black_end_x > last_black_x) { // Only one black span on this line. break; } // Continue until we encounter a white pixel. int last_black_end_x = -1; int last_black_end_y = -1; do { if (!getPixel(src_line, x)) { last_black_end_x = x; last_black_end_y = y; break; } --x; ++y; src_line += info.src_wpl; } while (x >= x0); assert(last_black_end_x != -1); // Every white pixel between the first and the last black span // becomes black in the destination image. x = first_black_end_x; y = first_black_end_y; src_line = info.src_data + info.src_wpl * y; uint32_t* dst_line = info.dst_data + info.dst_wpl * y; do { transferPixel(src_line, dst_line, x); ++x; --y; src_line -= info.src_wpl; dst_line -= info.dst_wpl; } while (x <= last_black_end_x); } while (false); if (y0 + 1 < info.height) { ++y0; } else { ++x0; } if (x1 + 1 < info.width) { ++x1; } else { ++y1; } } } void DentFinder::scanBackslashDiagonals(ImgInfo const info) { // These are endpoint coordinates of our backslash diagonals. int x0 = 0, y0 = info.height - 1; int x1 = 0, y1 = y0; /* +------------+ | +-------> | | | ^ | | | | | |(x0, y0) | | |(x1, y1)--+ | +------------+ */ while (x0 < info.width) { do { // just to be able to break from it. // Find the first black pixel. int first_black_x = -1; int x = x0; int y = y0; uint32_t const* src_line = info.src_data + info.src_wpl * y; do { if (getPixel(src_line, x)) { first_black_x = x; break; } ++x; ++y; src_line += info.src_wpl; } while (x < x1); if (first_black_x == -1) { // No black pixels on this line. break; } // Continue until we encounter a white pixel. int first_black_end_x = -1; int first_black_end_y = -1; do { if (!getPixel(src_line, x)) { first_black_end_x = x; first_black_end_y = y; break; } ++x; ++y; src_line += info.src_wpl; } while (x < x1); if (first_black_end_x == -1) { // Only one black span on this line. break; } // Find the last black pixel. int last_black_x = -1; x = x1; y = y1; src_line = info.src_data + info.src_wpl * y; do { if (getPixel(src_line, x)) { last_black_x = x; break; } --x; --y; src_line -= info.src_wpl; } while (x >= x0); // We know we have at least one black pixel. assert(last_black_x != -1); if (first_black_end_x > last_black_x) { // Only one black span on this line. break; } // Continue until we encounter a white pixel. int last_black_end_x = -1; int last_black_end_y = -1; do { if (!getPixel(src_line, x)) { last_black_end_x = x; last_black_end_y = y; break; } --x; --y; src_line -= info.src_wpl; } while (x >= x0); assert(last_black_end_x != -1); // Every white pixel between the first and the last black span // becomes black in the destination image. x = first_black_end_x; y = first_black_end_y; src_line = info.src_data + info.src_wpl * y; uint32_t* dst_line = info.dst_data + info.dst_wpl * y; do { transferPixel(src_line, dst_line, x); ++x; ++y; src_line += info.src_wpl; dst_line += info.dst_wpl; } while (x <= last_black_end_x); } while (false); if (y0 > 0) { --y0; } else { ++x0; } if (x1 + 1 < info.width) { ++x1; } else { --y1; } } } } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/DentFinder.h000066400000000000000000000031501271170121200216170ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_DENTFINDER_H_ #define IMAGEPROC_DENTFINDER_H_ #include namespace imageproc { class BinaryImage; class DentFinder { public: /** * The idea is to scan all horizontal, vertical, and diagonal lines * and consider every white pixel between the first and the last black * span to belong to a dent or a hole. */ static imageproc::BinaryImage findDentsAndHoles( imageproc::BinaryImage const& src); private: struct ImgInfo; static void scanHorizontalLines(ImgInfo info); static void scanVerticalLines(ImgInfo info); static void scanSlashDiagonals(ImgInfo info); static void scanBackslashDiagonals(ImgInfo info); static uint32_t getPixel(uint32_t const* src_line, int x); static void transferPixel(uint32_t const* src_line, uint32_t* dst_line, int x); }; } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/DrawOver.cpp000066400000000000000000000050051271170121200216620ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "DrawOver.h" #include "BinaryImage.h" #include "RasterOp.h" #include #include #include #include #include #include #include namespace imageproc { void drawOver( QImage& dst, QRect const& dst_rect, QImage const& src, QRect const& 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"); } uint8_t* dst_line = dst.bits(); int const dst_bpl = dst.bytesPerLine(); uint8_t const* src_line = src.bits(); int const src_bpl = src.bytesPerLine(); int const depth = src.depth(); assert(dst.depth() == depth); 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. return; } int const 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; } } } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/DrawOver.h000066400000000000000000000033601271170121200213310ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_DRAWOVER_H_ #define IMAGEPROC_DRAWOVER_H_ class QImage; class QRect; namespace imageproc { /** * \brief Overdraws a portion of one image with a portion of another. * * \param dst The destination image. Can be in any format, as long * as the source image has the same format. * \param dst_rect The area of the destination image to be overdrawn. * This area must lie completely within the destination * image, and its size must match the size of \p src_rect. * \param src The source image. Can be in any format, as long * as the destination image has the same format. * \param src_rect The area of the source image to draw over * the destination image. This area must lie completely * within the source image, and its size must match the * size of \p dst_rect. */ void drawOver( QImage& dst, QRect const& dst_rect, QImage const& src, QRect const& src_rect); } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/FindPeaksGeneric.h000066400000000000000000000153171271170121200227460ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #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 namespace imageproc { namespace detail { namespace find_peaks { template void raiseAllButPeaks( MostSignificantSelector most_significant, LeastSignificantSelector least_significant, IncreaseSignificance increase_significance, QSize peak_neighborhood, T outside_values, T const* input, int input_stride, QSize size, T* to_be_raised, int to_be_raised_stride) { 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 ); std::vector mask(to_be_raised, to_be_raised + to_be_raised_stride * size.height()); int const 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); seedFillGenericInPlace( most_significant, least_significant, CONN8, &to_be_raised[0], to_be_raised_stride, size, &mask[0], mask_stride ); } } // namespace find_peaks } // namespace detail /** * \brief Finds positive or negative peaks on a 2D grid. * * A peak is defined as a cell or an 8-connected group of cells that is more * significant than any of its neighbor cells. More significant means either * greater or less, depending on the kind of peaks we want to locate. * In addition, we provide functionality to suppress peaks that are * in a specified neighborhood of a more significant peak. * * \param most_significant A functor or a pointer to a free function that * can be called with two arguments of type T and return the bigger * or the smaller of the two. * \param least_significant Same as most_significant, but the oposite operation. * \param increase_significance A functor or a pointer to a free function that * takes one argument and returns the next most significant value next * to it. Hint: for floating point data, use the nextafter() family of * functions. Their generic versions are available in Boost. * \param peak_mutator A functor or a pointer to a free function that will * transform a peak value. Two typical cases would be returning * the value as is and returning a fixed value. * \param non_peak_mutator Same as peak_mutator, but for non-peak values. * \param neighborhood The area around a peak (centered at width/2, height/2) * in which less significant peaks will be suppressed. Passing an empty * neighborhood is equivalent of passing a 1x1 neighborhood. * \param outside_values Values that are assumed to be outside of the grid bounds. * This will affect peak detection at the edges of grid. * \param[in,out] data Pointer to the data buffer. * \param stride The size of a row in the data buffer, in terms of the number of T objects. * \param size Grid dimensions. */ template void findPeaksInPlaceGeneric( MostSignificantSelector most_significant, LeastSignificantSelector least_significant, IncreaseSignificance increase_significance, PeakMutator peak_mutator, NonPeakMutator non_peak_mutator, QSize peak_neighborhood, T outside_values, T* data, int stride, QSize size) { if (size.isEmpty()) { return; } std::vector raised(size.width() * size.height()); int const 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]; int const w = size.width(); int const 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; } } /** * \brief Same as findPeaksInPlaceGeneric(), but returning a binary image * rather than mutating the input data. */ template BinaryImage findPeaksGeneric( MostSignificantSelector most_significant, LeastSignificantSelector least_significant, IncreaseSignificance increase_significance, QSize peak_neighborhood, T outside_values, T const* data, int stride, QSize size) { if (size.isEmpty()) { return BinaryImage(); } std::vector raised(size.width() * size.height()); int const 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(); int const peaks_stride = peaks.wordsPerLine(); T const* data_line = data; T const* raised_line = &raised[0]; int const w = size.width(); int const h = size.height(); uint32_t const 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; } } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/GaussBlur.cpp000066400000000000000000000063051271170121200220440ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich Based on code from the GIMP project, Copyright (C) 1995 Spencer Kimball and Peter Mattis 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "GaussBlur.h" #include "GrayImage.h" #include "Constants.h" #include #include #include #include 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 float div = sqrt(2.0 * constants::PI) * std_dev; const float x0 = -1.783 / std_dev; const float x1 = -1.723 / std_dev; const float x2 = 0.6318 / std_dev; const float x3 = 1.997 / std_dev; const float x4 = 1.6803 / div; const float x5 = 3.735 / div; const float x6 = -0.6803 / div; const float x7 = -0.2598 / div; n_p [0] = x4 + x6; n_p [1] = (exp(x1)*(x7*sin(x3)-(x6+2*x4)*cos(x3)) + exp(x0)*(x5*sin(x2) - (2*x6+x4)*cos (x2))); n_p [2] = (2 * exp(x0+x1) * ((x4+x6)*cos(x3)*cos(x2) - x5*cos(x3)*sin(x2) - x7*cos(x2)*sin(x3)) + x6*exp(2*x0) + x4*exp(2*x1)); n_p [3] = (exp(x1+2*x0) * (x7*sin(x3) - x6*cos(x3)) + exp(x0+2*x1) * (x5*sin(x2) - x4*cos(x2))); n_p [4] = 0.0; d_p [0] = 0.0; d_p [1] = -2 * exp(x1) * cos(x3) - 2 * exp(x0) * cos (x2); d_p [2] = 4 * cos(x3) * cos(x2) * exp(x0 + x1) + exp(2 * x1) + exp(2 * x0); d_p [3] = -2 * cos(x2) * exp(x0 + 2*x1) - 2*cos(x3) * exp(x1 + 2*x0); d_p [4] = 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]; } float const a = sum_n_p / (1.0 + sum_d); float const b = 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; } } } // namespace gauss_blur_impl GrayImage gaussBlur(GrayImage const& src, float h_sigma, float v_sigma) { using namespace boost::lambda; 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) ); return dst; } } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/GaussBlur.h000066400000000000000000000157511271170121200215160ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich Based on code from the GIMP project, Copyright (C) 1995 Spencer Kimball and Peter Mattis 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_GAUSSBLUR_H_ #define IMAGEPROC_GAUSSBLUR_H_ #include "ValueConv.h" #include #ifndef Q_MOC_RUN #include #endif #include #include namespace imageproc { class GrayImage; /** * \brief Applies gaussian blur on a GrayImage. * * \param src The image to apply gaussian blur to. * \param h_sigma The standard deviation in horizontal direction. * \param v_sigma The standard deviation in vertical direction. * \return The blurred image. */ GrayImage gaussBlur(GrayImage const& src, float h_sigma, float v_sigma); /** * \brief Applies a 2D gaussian filter on an arbitrary data grid. * * \param size Data grid dimensions. * \param h_sigma The standard deviation in horizontal direction. * \param v_sigma The standard deviation in vertical direction. * \param input A random access iterator (usually a pointer) * to the beginning of input data. * \param input_stride The distance (in terms of iterator difference) * from an input grid cell to the one directly below it. * \param float_reader A functor to convert whatever value corresponds to *input * into a float. Consider using one of the functors from ValueConv.h * The functor will be called like this: * \code * FloatReader const reader = ...; * float const val = reader(input[x]); * \endcode * \param output A random access iterator (usually a pointer) * to the beginning of output data. Output may point to the same * memory as input. * \param output_stride The distance (in terms of iterator difference) * from an output grid cell to the one directly below it. * \param float_writer A functor that takes a float value, optionally * converts it into another type and updates an output item. * The functor will be called like this: * \code * FloatWriter const writer = ...; * float const val = ...; * writer(output[x], val); * \endcode * Consider using boost::lambda, possible in conjunction with one of the functors * from ValueConv.h: * \code * using namespace boost::lambda; * * // Just copying. * gaussBlurGeneric(..., _1 = _2); * * // Convert to uint8_t, with rounding and clipping. * gaussBlurGeneric(..., _1 = bind(RoundAndClipValueConv(), _2); * \endcode */ template void gaussBlurGeneric(QSize size, float h_sigma, float v_sigma, SrcIt input, int input_stride, FloatReader float_reader, DstIt output, int output_stride, FloatWriter float_writer); 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 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; } } class FloatToFloatWriter { public: void operator()(float& dst, float src) const { dst = src; } }; } // namespace gauss_blur_impl template void gaussBlurGeneric(QSize const size, float const h_sigma, float const v_sigma, SrcIt const input, int const input_stride, FloatReader const float_reader, DstIt const output, int const output_stride, FloatWriter const float_writer) { if (size.isEmpty()) { return; } int const width = size.width(); int const height = size.height(); int const 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]); int const 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; float const initial_p = float_reader(sp_p[0]); float const initial_m = float_reader(sp_m[0]); for (int y = 0; y < height; ++y) { int const 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); float const* 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])); float const* sp_p = intermediate_line; float const* sp_m = intermediate_line + width - 1; float* vp = &val_p[0]; float* vm = &val_m[0] + width - 1; float const initial_p = sp_p[0]; float const initial_m = sp_m[0]; for (int x = 0; x < width; ++x) { int const 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; } } } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/GrayImage.cpp000066400000000000000000000022331271170121200217760ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "GrayImage.h" #include "Grayscale.h" #include namespace imageproc { GrayImage::GrayImage(QSize size) { if (size.isEmpty()) { return; } m_image = QImage(size, QImage::Format_Indexed8); m_image.setColorTable(createGrayscalePalette()); if (m_image.isNull()) { throw std::bad_alloc(); } } GrayImage::GrayImage(QImage const& image) : m_image(toGrayscale(image)) { } } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/GrayImage.h000066400000000000000000000055571271170121200214570ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_GRAYIMAGE_H_ #define IMAGEPROC_GRAYIMAGE_H_ #include #include #include #include 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(QImage const& 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. */ QImage const& toQImage() const { return m_image; } operator QImage const&() 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(); } uint8_t const* 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(); } private: QImage m_image; }; inline bool operator==(GrayImage const& lhs, GrayImage const& rhs) { return lhs.toQImage() == rhs.toQImage(); } inline bool operator!=(GrayImage const& lhs, GrayImage const& rhs) { return lhs.toQImage() != rhs.toQImage(); } } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/GrayRasterOp.h000066400000000000000000000127341271170121200221670ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_GRAYRASTEROP_H_ #define IMAGEPROC_GRAYRASTEROP_H_ #include "Grayscale.h" #include "GrayImage.h" #include #include #include #include #include #include namespace imageproc { /** * \brief Perform pixel-wise operations on two images. * * \param dst The destination image. Changes will be written there. * \param src The source image. May be the same as the destination image. * * The template argument is the operation to perform. This is generally a * combination of several GRop* class templates, such as * GRopSubtract\. */ template void grayRasterOp(GrayImage& dst, GrayImage const& src); /** * \brief Raster operation that takes source pixels as they are. * \see grayRasterOp() */ class GRopSrc { public: static uint8_t transform(uint8_t src, uint8_t /*dst*/) { return src; } }; /** * \brief Raster operation that takes destination pixels as they are. * \see grayRasterOp() */ class GRopDst { public: static uint8_t transform(uint8_t /*src*/, uint8_t dst) { return dst; } }; /** * \brief Raster operation that inverts the gray level. * \see grayRasterOp() */ template class GRopInvert { public: static uint8_t transform(uint8_t src, uint8_t dst) { return uint8_t(0xff) - Arg::transform(src, dst); } }; /** * \brief Raster operation that subtracts gray levels of Rhs from Lhs. * * The "Clipped" part of the name indicates that negative subtraction results * are turned into zero. * * \see grayRasterOp() */ template class GRopClippedSubtract { public: static uint8_t transform(uint8_t src, uint8_t dst) { uint8_t const lhs = Lhs::transform(src, dst); uint8_t const rhs = Rhs::transform(src, dst); return lhs > rhs ? lhs - rhs : uint8_t(0); } }; /** * \brief Raster operation that subtracts gray levels of Rhs from Lhs. * * The "Unclipped" part of the name indicates that underflows aren't handled. * Negative results will appear as 256 - |negative_result|. * * \see grayRasterOp() */ template class GRopUnclippedSubtract { public: static uint8_t transform(uint8_t src, uint8_t dst) { uint8_t const lhs = Lhs::transform(src, dst); uint8_t const rhs = Rhs::transform(src, dst); return lhs - rhs; } }; /** * \brief Raster operation that sums Rhs and Lhs gray levels. * * The "Clipped" part of the name indicates that overflow are clipped at 255. * * \see grayRasterOp() */ template class GRopClippedAdd { public: static uint8_t transform(uint8_t src, uint8_t dst) { unsigned const lhs = Lhs::transform(src, dst); unsigned const rhs = Rhs::transform(src, dst); unsigned const sum = lhs + rhs; return sum < 256 ? static_cast(sum) : uint8_t(255); } }; /** * \brief Raster operation that sums Rhs and Lhs gray levels. * * The "Unclipped" part of the name indicates that overflows aren't handled. * Results exceeding 255 will appear as result - 256. * * \see grayRasterOp() */ template class GRopUnclippedAdd { public: static uint8_t transform(uint8_t src, uint8_t dst) { uint8_t const lhs = Lhs::transform(src, dst); uint8_t const rhs = Rhs::transform(src, dst); return lhs + rhs; } }; /** * \brief Raster operation that takes the darkest of its arguments. * \see grayRasterOp() */ template class GRopDarkest { public: static uint8_t transform(uint8_t src, uint8_t dst) { uint8_t const lhs = Lhs::transform(src, dst); uint8_t const rhs = Rhs::transform(src, dst); return lhs < rhs ? lhs : rhs; } }; /** * \brief Raster operation that takes the lightest of its arguments. * \see grayRasterOp() */ template class GRopLightest { public: static uint8_t transform(uint8_t src, uint8_t dst) { uint8_t const lhs = Lhs::transform(src, dst); uint8_t const rhs = Rhs::transform(src, dst); return lhs > rhs ? lhs : rhs; } }; template void grayRasterOp(GrayImage& dst, GrayImage const& src) { 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"); } uint8_t const* src_line = src.data(); uint8_t* dst_line = dst.data(); int const src_stride = src.stride(); int const dst_stride = dst.stride(); int const width = src.width(); int const 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; } } } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/Grayscale.cpp000066400000000000000000000270431271170121200220510ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Grayscale.h" #include "GrayImage.h" #include "BinaryImage.h" #include "BitOps.h" #include #include #include #include #include #include #include #include namespace imageproc { static QImage monoMsbToGrayscale(QImage const& src) { int const width = src.width(); int const height = src.height(); QImage dst(width, height, QImage::Format_Indexed8); dst.setColorTable(createGrayscalePalette()); if (width > 0 && height > 0 && dst.isNull()) { throw std::bad_alloc(); } uint8_t const* src_line = src.bits(); uint8_t* dst_line = dst.bits(); int const src_bpl = src.bytesPerLine(); int const dst_bpl = dst.bytesPerLine(); uint8_t bin2gray[2] = { 0, 0xff }; if (src.numColors() >= 2) { if (qGray(src.color(0)) > qGray(src.color(1))) { // if color 0 is lighter than color 1 bin2gray[0] = 0xff; bin2gray[1] = 0; } } for (int y = 0; y < height; ++y) { for (int x = 0; x < width;) { uint8_t const b = src_line[x / 8]; for (int i = 7; i >= 0 && x < width; --i, ++x) { dst_line[x] = bin2gray[(b >> i) & 1]; } } src_line += src_bpl; dst_line += dst_bpl; } dst.setDotsPerMeterX(src.dotsPerMeterX()); dst.setDotsPerMeterY(src.dotsPerMeterY()); return dst; } static QImage monoLsbToGrayscale(QImage const& src) { int const width = src.width(); int const height = src.height(); QImage dst(width, height, QImage::Format_Indexed8); dst.setColorTable(createGrayscalePalette()); if (width > 0 && height > 0 && dst.isNull()) { throw std::bad_alloc(); } uint8_t const* src_line = src.bits(); uint8_t* dst_line = dst.bits(); int const src_bpl = src.bytesPerLine(); int const dst_bpl = dst.bytesPerLine(); uint8_t bin2gray[2] = { 0, 0xff }; if (src.numColors() >= 2) { if (qGray(src.color(0)) > qGray(src.color(1))) { // if color 0 is lighter than color 1 bin2gray[0] = 0xff; bin2gray[1] = 0; } } for (int y = 0; y < height; ++y) { for (int x = 0; x < width;) { uint8_t const b = src_line[x / 8]; for (int i = 0; i < 8 && x < width; ++i, ++x) { dst_line[x] = bin2gray[(b >> i) & 1]; } } src_line += src_bpl; dst_line += dst_bpl; } dst.setDotsPerMeterX(src.dotsPerMeterX()); dst.setDotsPerMeterY(src.dotsPerMeterY()); return dst; } static QImage anyToGrayscale(QImage const& src) { int const width = src.width(); int const height = src.height(); 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(); int const 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; } dst.setDotsPerMeterX(src.dotsPerMeterX()); dst.setDotsPerMeterY(src.dotsPerMeterY()); return dst; } QVector createGrayscalePalette() { QVector palette(256); for (int i = 0; i < 256; ++i) { palette[i] = qRgb(i, i, i); } return palette; } QImage toGrayscale(QImage const& 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.numColors() == 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( GrayImage const& src, double const black_clip_fraction, double const white_clip_fraction) { if (src.isNull()) { return src; } GrayImage dst(src); int const width = dst.width(); int const height = dst.height(); int const num_pixels = width * height; int black_clip_pixels = qRound(black_clip_fraction * num_pixels); int white_clip_pixels = qRound(white_clip_fraction * num_pixels); GrayscaleHistogram const hist(dst); int min = 0; for (; min <= 255; ++min) { if (black_clip_pixels < hist[min]) { break; } black_clip_pixels -= hist[min]; } int max = 255; for (; max >= 0; --max) { if (white_clip_pixels < hist[max]) { break; } white_clip_pixels -= hist[max]; } uint8_t gray_mapping[256]; if (min >= max) { int const 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) { int const src_level = qBound(min, i, max); int const num = 255 * (src_level - min); int const denom = max - min; int const dst_level = (num + denom / 2) / denom; gray_mapping[i] = static_cast(dst_level); } } uint8_t* line = dst.data(); int const 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]]; } } return dst; } GrayImage createFramedImage(QSize const& size, unsigned char const inner_color, unsigned char const frame_color) { GrayImage image(size); image.fill(inner_color); int const width = size.width(); int const height = size.height(); unsigned char* line = image.data(); int const stride = image.stride(); memset(line, frame_color, width); for (int y = 0; y < height; ++y, line += stride) { line[0] = frame_color; line[width - 1] = frame_color; } memset(line - stride, frame_color, width); return image; } unsigned char darkestGrayLevel(QImage const& image) { QImage const gray(toGrayscale(image)); int const width = image.width(); int const height = image.height(); unsigned char const* line = image.bits(); int const bpl = image.bytesPerLine(); 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; } return darkest; } GrayscaleHistogram::GrayscaleHistogram(QImage const& 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( QImage const& img, BinaryImage const& 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(QImage const& img) { int const w = img.width(); int const h = img.height(); int const bpl = img.bytesPerLine(); int const last_byte_idx = (w - 1) >> 3; int const 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; } uint8_t const* 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); } int const num_bits_0 = w * h - num_bits_1; QRgb color0 = 0xffffffff; QRgb color1 = 0xff000000; if (img.numColors() >= 2) { color0 = img.color(0); color1 = img.color(1); } m_pixels[qGray(color0)] = num_bits_0; m_pixels[qGray(color1)] = num_bits_1; } void GrayscaleHistogram::fromMonoMSBImage(QImage const& img, BinaryImage const& mask) { int const w = img.width(); int const h = img.height(); int const wpl = img.bytesPerLine() >> 2; int const last_word_idx = (w - 1) >> 5; int const last_word_unused_bits = (((last_word_idx + 1) << 5) - w); uint32_t last_word_mask = ~uint32_t(0) << last_word_unused_bits; uint32_t const* line = (uint32_t const*)img.bits(); uint32_t const* mask_line = mask.data(); int const 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) { uint32_t const mask = mask_line[i]; num_bits_1 += countNonZeroBits(line[i] & mask); num_bits_0 += countNonZeroBits(~line[i] & mask); } // The last (possibly incomplete) word. uint32_t const 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.numColors() >= 2) { color0 = img.color(0); color1 = img.color(1); } m_pixels[qGray(color0)] = num_bits_0; m_pixels[qGray(color1)] = num_bits_1; } void GrayscaleHistogram::fromGrayscaleImage(QImage const& img) { int const w = img.width(); int const h = img.height(); int const bpl = img.bytesPerLine(); uint8_t const* 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(QImage const& img, BinaryImage const& mask) { int const w = img.width(); int const h = img.height(); int const bpl = img.bytesPerLine(); uint8_t const* line = img.bits(); uint32_t const* mask_line = mask.data(); int const mask_wpl = mask.wordsPerLine(); uint32_t const 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(QImage const& img) { int const w = img.width(); int const h = img.height(); for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { ++m_pixels[qGray(img.pixel(x, y))]; } } } void GrayscaleHistogram::fromAnyImage(QImage const& img, BinaryImage const& mask) { int const w = img.width(); int const h = img.height(); uint32_t const* mask_line = mask.data(); int const mask_wpl = mask.wordsPerLine(); uint32_t const 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 scantailor-RELEASE_0_9_12_2/imageproc/Grayscale.h000066400000000000000000000065731271170121200215230ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_GRAYSCALE_H_ #define IMAGEPROC_GRAYSCALE_H_ #include "GrayImage.h" #include #include class QImage; class QSize; namespace imageproc { class BinaryImage; class GrayscaleHistogram { public: explicit GrayscaleHistogram(QImage const& img); GrayscaleHistogram(QImage const& img, BinaryImage const& mask); int& operator[](int idx) { return m_pixels[idx]; } int operator[](int idx) const { return m_pixels[idx]; } private: void fromMonoImage(QImage const& img); void fromMonoMSBImage(QImage const& img, BinaryImage const& mask); void fromGrayscaleImage(QImage const& img); void fromGrayscaleImage(QImage const& img, BinaryImage const& mask); void fromAnyImage(QImage const& img); void fromAnyImage(QImage const& img, BinaryImage const& mask); int m_pixels[256]; }; /** * \brief Create a 256-element grayscale palette. */ QVector createGrayscalePalette(); /** * \brief Convert an image from any format to grayscale. * * \param src The source image in any format. * \return A grayscale image with proper palette. Null will be returned * if \p src was null. */ QImage toGrayscale(QImage const& src); /** * \brief Stetch the distribution of gray levels to cover the whole range. * * \param src The source image. It doesn't have to be grayscale. * \param black_clip_fraction The fraction of pixels (fractions of 1) that are * allowed to go negative. Such pixels will be clipped to 0 (black). * \param white_clip_fraction The fraction of pixels (fractions of 1) that are * allowed to exceed the maximum brightness level. Such pixels will be * clipped to 255 (white). * \return A grayscale image, or a null image, if \p src was null. */ GrayImage stretchGrayRange(GrayImage const& src, double black_clip_fraction = 0.0, double white_clip_fraction = 0.0); /** * \brief Create a grayscale image consisting of a 1 pixel frame and an inner area. * * \param size The size of the image including the frame. * \param inner_color The gray level of the inner area. Defaults to white. * \param frame_color The gray level of the frame area. Defaults to black. * \return The resulting image. */ GrayImage createFramedImage( QSize const& size, unsigned char inner_color = 0xff, unsigned char frame_color = 0x00); /** * \brief Find the darkest gray level of an image. * * \param image The image to process. If it's null, 0xff will * be returned as the darkest image. If it's not grayscale, * a grayscale copy will be created. */ unsigned char darkestGrayLevel(QImage const& image); } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/HoughLineDetector.cpp000066400000000000000000000313601271170121200235100ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "HoughLineDetector.h" #include "BinaryImage.h" #include "BWColor.h" #include "ConnCompEraser.h" #include "ConnComp.h" #include "Connectivity.h" #include "Constants.h" #include "Morphology.h" #include "RasterOp.h" #include "SeedFill.h" #include "Grayscale.h" #include #include #include #include #include #include #include #include #include #ifndef Q_MOC_RUN #include #endif #include #include #include #include #include namespace imageproc { class HoughLineDetector::GreaterQualityFirst { public: bool operator()(HoughLine const& lhs, HoughLine const& rhs) const { return lhs.quality() > rhs.quality(); } }; HoughLineDetector::HoughLineDetector( QSize const& input_dimensions, double const distance_resolution, double const start_angle, double const angle_delta, int const num_angles) : m_distanceResolution(distance_resolution), m_recipDistanceResolution(1.0 / distance_resolution) { int const max_x = input_dimensions.width() - 1; int const 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; QPointF const uv(cos(angle), sin(angle)); BOOST_FOREACH (QPoint const& p, checkpoints) { double const 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); } // We bias distances to make them non-negative. m_distanceBias = -min_distance; double const max_biased_distance = max_distance + m_distanceBias; int const 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); } void HoughLineDetector::process(int x, int y, unsigned weight) { unsigned* hist_line = &m_histogram[0]; BOOST_FOREACH (QPointF const& uv, m_angleUnitVectors) { double const distance = uv.x() * x + uv.y() * y; double const biased_distance = distance + m_distanceBias; int const bin = (int)(biased_distance * m_recipDistanceResolution + 0.5); assert(bin >= 0 && bin < m_histWidth); hist_line[bin] += weight; hist_line += m_histWidth; } } QImage HoughLineDetector::visualizeHoughSpace(unsigned const 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; unsigned const* 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; } if (max_value == 0) { intensity.fill(0); return intensity; } unsigned char* intensity_line = intensity.bits(); int const 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) { unsigned const intensity = (unsigned)floor( hist_line[x] * 255.0 / max_value + 0.5 ); intensity_line[x] = (unsigned char)intensity; } intensity_line += intensity_bpl; hist_line += m_histWidth; } BinaryImage const 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; } std::vector HoughLineDetector::findLines(unsigned const quality_lower_bound) const { BinaryImage peaks( findHistogramPeaks( m_histogram, m_histWidth, m_histHeight, quality_lower_bound ) ); std::vector lines; QRect const peaks_rect(peaks.rect()); ConnCompEraser eraser(peaks.release(), CONN8); ConnComp cc; while (!(cc = eraser.nextConnComp()).isNull()) { unsigned const level = m_histogram[ cc.seed().y() * m_histWidth + cc.seed().x() ]; QPoint const center(cc.rect().center()); QPointF const norm_uv(m_angleUnitVectors[center.y()]); double const distance = (center.x() + 0.5) * m_distanceResolution - m_distanceBias; lines.push_back(HoughLine(norm_uv, distance, level)); } std::sort(lines.begin(), lines.end(), GreaterQualityFirst()); return lines; } BinaryImage HoughLineDetector::findHistogramPeaks( std::vector const& hist, int const width, int const height, unsigned const 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. BinaryImage const not_peaks(seedFill(diff, peak_candidates, CONN8)); rasterOp >(peak_candidates, not_peaks); return peak_candidates; } /** * Build a binary image where a black pixel indicates that the corresponding * histogram bin meets the following conditions: * \li It doesn't have a greater neighbor (in a 5x5 window). * \li It's value is not below \p lower_bound. */ BinaryImage HoughLineDetector::findPeakCandidates( std::vector const& hist, int const width, int const height, unsigned const lower_bound) { 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) ); return equal_map; } /** * Increment bins that correspond to black pixels in \p mask. */ void HoughLineDetector::incrementBinsMasked( std::vector& hist, int const width, int const height, BinaryImage const& mask) { uint32_t const* mask_line = mask.data(); int const mask_wpl = mask.wordsPerLine(); unsigned* hist_line = &hist[0]; uint32_t const 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; } } /** * Every bin in \p dst is set to the maximum of the corresponding bin * in \p src and its neighbors in a 5x5 window. */ void HoughLineDetector::max5x5( std::vector const& src, std::vector& dst, int const width, int const 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); } /** * Every bin in \p dst is set to the maximum of the corresponding bin * in \p src and its left and right neighbors (if it has them). */ void HoughLineDetector::max3x1( std::vector const& src, std::vector& dst, int const width, int const height) { if (width == 1) { dst = src; return; } unsigned const* 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) { unsigned const prev = src_line[x - 1]; unsigned const cur = src_line[x]; unsigned const 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; } } /** * Every bin in \p dst is set to the maximum of the corresponding bin * in \p src and its top and bottom neighbors (if it has them). */ void HoughLineDetector::max1x3( std::vector const& src, std::vector& dst, int const width, int const height) { if (height == 1) { dst = src; return; } // First row (no top neighbors). unsigned const* 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) { unsigned const prev = p_src[x - width]; unsigned const cur = p_src[x]; unsigned const 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; } } /** * Builds a binary image where a black pixel indicates that the corresponding * bins in two histograms have equal values, and those values are not below * \p lower_bound. */ BinaryImage HoughLineDetector::buildEqualMap( std::vector const& src1, std::vector const& src2, int const width, int const height, unsigned const lower_bound) { BinaryImage dst(width, height, WHITE); uint32_t* dst_line = dst.data(); int const dst_wpl = dst.wordsPerLine(); unsigned const* src1_line = &src1[0]; unsigned const* src2_line = &src2[0]; uint32_t const 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; } /*=============================== HoughLine ================================*/ QPointF HoughLine::pointAtY(double const y) const { double x = (m_distance - y * m_normUnitVector.y()) / m_normUnitVector.x(); return QPointF(x, y); } QPointF HoughLine::pointAtX(double const x) const { double y = (m_distance - x * m_normUnitVector.x()) / m_normUnitVector.y(); return QPointF(x, y); } QLineF HoughLine::unitSegment() const { QPointF const line_point(m_normUnitVector * m_distance); QPointF const line_vector(m_normUnitVector.y(), -m_normUnitVector.x()); return QLineF(line_point, line_point + line_vector); } } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/HoughLineDetector.h000066400000000000000000000123361271170121200231570ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_HOUGHLINEDETECTOR_H_ #define IMAGEPROC_HOUGHLINEDETECTOR_H_ #include #include class QSize; class QLineF; class QImage; namespace imageproc { class BinaryImage; /** * \brief A line detected by HoughLineDetector. * * A line is represented by a unit-size vector perpendicular to * the line, and a distance along that vector to a point on the line. * In other words, unit_vector * distance is a point on the line and * unit_vector is the normal vector for that line. */ class HoughLine { public: HoughLine() : m_normUnitVector(), m_distance(), m_quality() {} HoughLine(QPointF const& norm_uv, double distance, unsigned quality) : m_normUnitVector(norm_uv), m_distance(distance), m_quality(quality) {} QPointF const& 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(QSize const& 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( std::vector const& hist, int width, int height, unsigned lower_bound); static BinaryImage findPeakCandidates( std::vector const& hist, int width, int height, unsigned lower_bound); static void incrementBinsMasked( std::vector& hist, int width, int height, BinaryImage const& mask); static void max5x5( std::vector const& src, std::vector& dst, int width, int height); static void max3x1( std::vector const& src, std::vector& dst, int width, int height); static void max1x3( std::vector const& src, std::vector& dst, int width, int height); static BinaryImage buildEqualMap( std::vector const& src1, std::vector const& 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 scantailor-RELEASE_0_9_12_2/imageproc/InfluenceMap.cpp000066400000000000000000000160651271170121200225070ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "InfluenceMap.h" #include "BinaryImage.h" #include "ConnectivityMap.h" #include "FastQueue.h" #include "BitOps.h" #include #include #include #include #include class QImage; namespace imageproc { InfluenceMap::InfluenceMap() : m_pData(0), m_size(), m_stride(0), m_maxLabel(0) { } InfluenceMap::InfluenceMap(ConnectivityMap const& cmap) : m_pData(0), m_size(), m_stride(0), m_maxLabel(0) { if (cmap.size().isEmpty()) { return; } init(cmap); } InfluenceMap::InfluenceMap( ConnectivityMap const& cmap, BinaryImage const& mask) : m_pData(0), 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(InfluenceMap const& other) : m_data(other.m_data), m_pData(0), 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; } } InfluenceMap& InfluenceMap::operator=(InfluenceMap const& other) { InfluenceMap(other).swap(*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); } void InfluenceMap::init( ConnectivityMap const& cmap, BinaryImage const* mask) { int const width = cmap.size().width() + 2; int const 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]; uint32_t const* 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; } if (mask) { uint32_t const* mask_line = mask->data(); int const mask_stride = mask->wordsPerLine(); cell = m_pData; uint32_t const 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; } } 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); int32_t const dx2 = cell->vec.x << 1; int32_t const 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 = cell->vec.x + 1; nbh->vec.y = 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 = 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 = cell->vec.x - 1; nbh->vec.y = 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 = 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 = cell->vec.x - 1; nbh->vec.y = 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 = 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 = cell->vec.x + 1; nbh->vec.y = 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 = cell->vec.x + 1; nbh->vec.y = cell->vec.y; queue.push(nbh); } } } QImage InfluenceMap::visualized() const { if (m_size.isEmpty()) { return QImage(); } int const width = m_size.width(); int const height = m_size.height(); QImage dst(m_size, QImage::Format_ARGB32); dst.fill(0x00FFFFFF); // transparent white Cell const* src_line = m_pData; int const src_stride = m_stride; uint32_t* dst_line = reinterpret_cast(dst.bits()); int const dst_stride = dst.bytesPerLine() / sizeof(uint32_t); for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { uint32_t const val = src_line[x].label; if (val == 0) { continue; } int const bits_unused = countMostSignificantZeroes(val); uint32_t const reversed = reverseBits(val) >> bits_unused; uint32_t const mask = ~uint32_t(0) >> bits_unused; double const H = 0.99 * (double(reversed) / mask); double const S = 1.0; double const 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; } } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/InfluenceMap.h000066400000000000000000000107131271170121200221460ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_INFLUENCE_MAP_H_ #define IMAGEPROC_INFLUENCE_MAP_H_ #include #include #include class QImage; namespace imageproc { class BinaryImage; class ConnectivityMap; /** * \brief Takes labelled regions and extends them to cover a larger area. * * Extension goes by taking over zero (that is background) labels. * Extension is done in such a way that two different labels meet at * 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(ConnectivityMap const& cmap); /** * \brief Take labelled regions from a connectivity map * and extend them to cover the area that's black in mask. */ InfluenceMap(ConnectivityMap const& cmap, BinaryImage const& mask); InfluenceMap(InfluenceMap const& other); InfluenceMap& operator=(InfluenceMap const& 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(). */ Cell const* 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(). */ Cell* 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. */ Cell const* paddedData() const { return m_pData ? &m_data[0] : 0; } /** * \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] : 0; } /** * \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(ConnectivityMap const& cmap, BinaryImage const* mask = 0); std::vector m_data; Cell* m_pData; QSize m_size; int m_stride; uint32_t m_maxLabel; }; inline void swap(InfluenceMap& o1, InfluenceMap& o2) { o1.swap(o2); } } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/IntegralImage.h000066400000000000000000000103751271170121200223140ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_INTEGRALIMAGE_H_ #define IMAGEPROC_INTEGRALIMAGE_H_ #include "NonCopyable.h" #include #include #include namespace imageproc { /** * \brief Provides the sum of values in a sub-rectangle of a 2D array in constant time. * * Suppose you have a MxN array of some values. Now if we are not going to * alter it, but are going to calculate the sum of values in various * rectangular sub-regions, it's best to use an integral image for this purpose. * We construct it once, with complexity of O(M*N), and then obtain the sum * of values in a rectangular sub-region in O(1). * * \note Template parameter T must be large enough to hold the sum of all * values in the array. */ template class IntegralImage { DECLARE_NON_COPYABLE(IntegralImage) public: IntegralImage(int width, int height); explicit IntegralImage(QSize const& 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(QRect const& 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; }; template IntegralImage::IntegralImage(int const width, int const height) : m_lineSum() // init with 0 or with default constructor. { // The first row and column are fake. init(width + 1, height + 1); } template IntegralImage::IntegralImage(QSize const& size) : m_lineSum() // init with 0 or with default constructor. { // The first row and column are fake. init(size.width() + 1, size.height() + 1); } template IntegralImage::~IntegralImage() { delete[] m_pData; } template void IntegralImage::init(int const width, int const height) { m_width = width; m_height = height; m_pData = 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(); } m_pAbove = m_pData; m_pCur = m_pAbove + width; // Skip the first row. } template void IntegralImage::push(T const val) { m_lineSum += val; *m_pCur = *m_pAbove + m_lineSum; ++m_pCur; ++m_pAbove; } template void IntegralImage::beginRow() { m_lineSum = T(); // Initialize and skip the fake column. *m_pCur = T(); ++m_pCur; ++m_pAbove; } template inline T IntegralImage::sum(QRect const& rect) const { // Keep in mind that row 0 and column 0 are fake. int const pre_left = rect.left(); int const pre_right = rect.right() + 1; // QRect::right() is inclusive. int const pre_top = rect.top(); int const 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; } } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/LeastSquaresFit.cpp000066400000000000000000000055351271170121200232200ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #define _ISOC99SOURCE // For copysign() #include "LeastSquaresFit.h" #include #include #include #include #ifdef _MSC_VER #undef copysign // Just in case. #define copysign _copysign #endif namespace imageproc { void leastSquaresFit(QSize const& C_size, double* C, double* x, double* d) { int const width = C_size.width(); int const height = C_size.height(); if (width < 0 || height < 0 || height < width) { throw std::invalid_argument("leastSquaresFit: invalid dimensions"); } // Calculate a QR decomposition of C using Givens rotations. // We store R in place of C, while Q is not stored at all. // Instead, we rotate the d vector on the fly. int jj = 0; // j * width + j for (int j = 0; j < width; ++j, jj += width + 1) { int ij = jj + width; // i * width + j for (int i = j + 1; i < height; ++i, ij += width) { double const a = C[jj]; double const b = C[ij]; if (b == 0.0) { continue; } double sin, cos; if (a == 0.0) { cos = 0.0; sin = copysign(1.0, b); C[jj] = fabs(b); } else if (fabs(b) > fabs(a)) { double const t = a / b; double const u = copysign(sqrt(1.0 + t*t), b); sin = 1.0 / u; cos = sin * t; C[jj] = b * u; } else { double const t = b / a; double const u = copysign(sqrt(1.0 + t*t), a); cos = 1.0 / u; sin = cos * t; C[jj] = a * u; } C[ij] = 0.0; int ik = ij + 1; // i * width + k int jk = jj + 1; // j * width + k for (int k = j + 1; k < width; ++k, ++ik, ++jk) { double const temp = cos * C[jk] + sin * C[ik]; C[ik] = cos * C[ik] - sin * C[jk]; C[jk] = temp; } // Rotate d. double const temp = cos * d[j] + sin * d[i]; d[i] = cos * d[i] - sin * d[j]; d[j] = temp; } } // Solve R*x = d by back-substitution. int ii = width * width - 1; // i * width + i for (int i = width - 1; i >= 0; --i, ii -= width + 1) { double sum = d[i]; int ik = ii + 1; for (int k = i + 1; k < width; ++k, ++ik) { sum -= C[ik] * x[k]; } assert(C[ii] != 0.0); x[i] = sum / C[ii]; } } } scantailor-RELEASE_0_9_12_2/imageproc/LeastSquaresFit.h000066400000000000000000000025241271170121200226600ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_LEAST_SQUARES_FIT_H_ #define IMAGEPROC_LEAST_SQUARES_FIT_H_ class QSize; namespace imageproc { /** * \brief Solves C * x - d = r, |r| = min! * * \param C_size Dimensions of the C matrix. * \param C The C matrix stored linearly in row-major order. It's contents * won't be preserved. * \param x The resulting vector of C_size.width() elements. * \param d The d vector of C_size.height() elements. It's contents won't * be preserved. */ void leastSquaresFit(QSize const& C_size, double* C, double* x, double* d); } #endif scantailor-RELEASE_0_9_12_2/imageproc/LocalMinMaxGeneric.h000066400000000000000000000242501271170121200232420ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_LOCAL_MIN_MAX_GENERIC_H_ #define IMAGEPROC_LOCAL_MIN_MAX_GENERIC_H_ #include #include #include #include #include namespace imageproc { namespace detail { namespace local_min_max { template void fillAccumulator( MinMaxSelector selector, int todo_before, int todo_within, int todo_after, T outside_values, T const* src, int const src_delta, T* dst, int const 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); } } template void fillWithConstant(T* from, T* to, T constant) { for (++to; from != to; ++from) { *from = constant; } } template void horizontalPass( MinMaxSelector selector, QRect const neighborhood, T const outside_values, T const* input, int const input_stride, QSize const input_size, T* output, int const output_stride) { int const se_len = neighborhood.width(); int const width = input_size.width(); int const width_m1 = width - 1; int const height = input_size.height(); int const dx1 = neighborhood.left(); int const 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) { int const dst_segment_last = std::min( dst_segment_first + se_len, width ) - 1; // inclusive int const src_segment_first = dst_segment_first + dx1; int const src_segment_last = dst_segment_last + dx2; int const 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 int const from = std::min(width_m1, src_segment_middle); int const to = std::max(0, src_segment_first); int const todo_before = src_segment_middle - from; int const todo_within = from - to + 1; int const todo_after = to - src_segment_first; int const src_delta = -1; int const 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 int const from = std::max(0, src_segment_middle); int const to = std::min(width_m1, src_segment_last); int const todo_before = from - src_segment_middle; int const todo_within = to - from + 1; int const todo_after = src_segment_last - to; int const src_delta = 1; int const dst_delta = 1; fillAccumulator( selector, todo_before, todo_within, todo_after, outside_values, input + src_segment_middle, src_delta, accum_middle, dst_delta ); } int const offset1 = dx1 - src_segment_middle; int const 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; } } template void verticalPass( MinMaxSelector selector, QRect const neighborhood, T const outside_values, T const* input, int const input_stride, QSize const input_size, T* output, int const output_stride) { int const se_len = neighborhood.height(); int const width = input_size.width(); int const height = input_size.height(); int const height_m1 = height - 1; int const dy1 = neighborhood.top(); int const 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) { int const dst_segment_last = std::min( dst_segment_first + se_len, height ) - 1; // inclusive int const src_segment_first = dst_segment_first + dy1; int const src_segment_last = dst_segment_last + dy2; int const 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 int const from = std::min(height_m1, src_segment_middle); int const to = std::max(0, src_segment_first); int const todo_before = src_segment_middle - from; int const todo_within = from - to + 1; int const todo_after = to - src_segment_first; int const src_delta = -input_stride; int const 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 int const from = std::max(0, src_segment_middle); int const to = std::min(height_m1, src_segment_last); int const todo_before = from - src_segment_middle; int const todo_within = to - from + 1; int const todo_after = src_segment_last - to; int const src_delta = input_stride; int const 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 ); } int const offset1 = dy1 - src_segment_middle; int const 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; } } } // namespace local_min_max } // namespace detail /** * \brief For each cell on a 2D grid, finds the minimum or the maximum value * in a rectangular neighborhood. * * This can be seen as a generalized version of grayscale erode and dilate operations. * * \param selector A functor or a pointer to a free function that can be called with * two arguments of type T and return the bigger or the smaller of the two. * \param neighborhood The rectangular neighborhood to search for maximum or minimum values. * The (0, 0) point would usually be located at the center of the neighborhood * rectangle, although it's not strictly required. The neighborhood rectangle * can't be empty. * \param outside_values Values that are assumed to be outside of the grid bounds. * \param input Pointer to the input buffer. * \param input_stride The size of a row in input buffer, in terms of the number of T objects. * \param input_size Dimensions of the input grid. * \param output Pointer to the output data. Note that the input and output buffers * may be the same. * \param output_stride The size of a row in the output buffer, in terms of the number of T objects. * The output grid is presumed to have the same dimensions as the input grid. * * This code is an implementation of the following algorithm:\n * A fast algorithm for local minimum and maximum filters on rectangular and octagonal kernels, * Patt. Recog. Letters, 13, pp. 517-521, 1992 * * A good description of this algorithm is available online at: * http://leptonica.com/grayscale-morphology.html#FAST-IMPLEMENTATION */ template void localMinMaxGeneric( MinMaxSelector selector, QRect const neighborhood, T const outside_values, T const* input, int const input_stride, QSize const input_size, T* output, int const output_stride) { assert(!neighborhood.isEmpty()); if (input_size.isEmpty()) { return; } std::vector temp(input_size.width() * input_size.height()); int const 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::verticalPass( selector, neighborhood, outside_values, &temp[0], temp_stride, input_size, output, output_stride ); } } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/MaxWhitespaceFinder.cpp000066400000000000000000000276461271170121200240420ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "MaxWhitespaceFinder.h" #include #include #ifndef Q_MOC_RUN #include #endif #include #include #include #include #include namespace imageproc { using namespace max_whitespace_finder; namespace { class AreaCompare { public: bool operator()(QRect const lhs, QRect const rhs) const { int const lhs_area = lhs.width() * lhs.height(); int const rhs_area = rhs.width() * rhs.height(); return lhs_area < rhs_area; } }; } // anonymous namespace MaxWhitespaceFinder::MaxWhitespaceFinder(BinaryImage const& img, QSize min_size) : m_integralImg(img.size()), m_ptrQueuedRegions(new PriorityStorageImpl(AreaCompare())), m_minSize(min_size) { init(img); } void MaxWhitespaceFinder::init(BinaryImage const& img) { int const width = img.width(); int const height = img.height(); uint32_t const* line = img.data(); int const wpl = img.wordsPerLine(); for (int y = 0; y < height; ++y, line += wpl) { m_integralImg.beginRow(); for (int x = 0; x < width; ++x) { int const shift = 31 - (x & 31); m_integralImg.push((line[x >> 5] >> shift) & 1); } } Region region(0, img.rect()); m_ptrQueuedRegions->push(region); } void MaxWhitespaceFinder::addObstacle(QRect const& obstacle) { if (m_ptrQueuedRegions->size() == 1) { m_ptrQueuedRegions->top().addObstacle(obstacle); } else { m_newObstacles.push_back(obstacle); } } QRect MaxWhitespaceFinder::next(ObstacleMode const 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); if (!region.obstacles().empty()) { subdivideUsingObstacles(region); continue; } if (m_integralImg.sum(region.bounds()) != 0) { subdivideUsingRaster(region); continue; } if (obstacle_mode == AUTO_OBSTACLES) { m_newObstacles.push_back(region.bounds()); } return region.bounds(); } return QRect(); } void MaxWhitespaceFinder::subdivideUsingObstacles(Region const& region) { QRect const bounds(region.bounds()); QRect const pivot_rect(findPivotObstacle(region)); subdivide(region, bounds, pivot_rect); } void MaxWhitespaceFinder::subdivideUsingRaster(Region const& region) { QRect const bounds(region.bounds()); QPoint const pivot_pixel(findBlackPixelCloseToCenter(bounds)); QRect const pivot_rect(extendBlackPixelToBlackBox(pivot_pixel, bounds)); subdivide(region, bounds, pivot_rect); } void MaxWhitespaceFinder::subdivide( Region const& region, QRect const bounds, QRect const 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(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(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(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(m_newObstacles.size(), new_bounds); new_region.addObstacles(region); m_ptrQueuedRegions->push(new_region); } } QRect MaxWhitespaceFinder::findPivotObstacle(Region const& region) const { assert(!region.obstacles().empty()); QPoint const center(region.bounds().center()); QRect best_obstacle; int best_distance = std::numeric_limits::max(); BOOST_FOREACH (QRect const& obstacle, region.obstacles()) { QPoint const vec(center - obstacle.center()); int const distance = vec.x() * vec.x() + vec.y() * vec.y(); if (distance <= best_distance) { best_obstacle = obstacle; best_distance = distance; } } return best_obstacle; } QPoint MaxWhitespaceFinder::findBlackPixelCloseToCenter( QRect const non_white_rect) const { assert(m_integralImg.sum(non_white_rect) != 0); QPoint const 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. // 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 (;;) { int const outer_inner_dw = outer_rect.width() - inner_rect.width(); int const outer_inner_dh = outer_rect.height() - inner_rect.height(); if (outer_inner_dw <= 1 && outer_inner_dh <= 1) { break; } int const delta_left = inner_rect.left() - outer_rect.left(); int const delta_right = outer_rect.right() - inner_rect.right(); int const delta_top = inner_rect.top() - outer_rect.top(); int const 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. unsigned const 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. unsigned const 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. unsigned const 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()); 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); } } QRect MaxWhitespaceFinder::extendBlackPixelToBlackBox( QPoint const pixel, QRect const bounds) const { assert(bounds.contains(pixel)); 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; } // 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 (;;) { int const outer_inner_dw = outer_rect.width() - inner_rect.width(); int const outer_inner_dh = outer_rect.height() - inner_rect.height(); if (outer_inner_dw <= 1 && outer_inner_dh <= 1) { break; } int const delta_left = inner_rect.left() - outer_rect.left(); int const delta_right = outer_rect.right() - inner_rect.right(); int const delta_top = inner_rect.top() - outer_rect.top(); int const 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)); unsigned const 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; } /*======================= MaxWhitespaceFinder::Region =====================*/ MaxWhitespaceFinder::Region::Region( unsigned known_new_obstacles, QRect const& bounds) : m_knownNewObstacles(known_new_obstacles), m_bounds(bounds) { } MaxWhitespaceFinder::Region::Region(Region const& other) : 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(Region const& other_region) { BOOST_FOREACH (QRect const& obstacle, other_region.obstacles()) { QRect const 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( std::vector const& new_obstacles) { for (size_t i = m_knownNewObstacles; i < new_obstacles.size(); ++i) { QRect const 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); } } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/MaxWhitespaceFinder.h000066400000000000000000000232661271170121200235010ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #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 namespace imageproc { class BinaryImage; namespace max_whitespace_finder { 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. */ MaxWhitespaceFinder( BinaryImage const& 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, BinaryImage const& 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(QRect const& rect); /** * \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, QRect const& bounds); /** * A shallow copy. Copies everything except the obstacle list. */ Region(Region const& other); QRect const& bounds() const { return m_bounds; } std::vector const& obstacles() const { return m_obstacles; } void addObstacle(QRect const& obstacle) { m_obstacles.push_back(obstacle); } void addObstacles(Region const& other_region); void addNewObstacles(std::vector const& new_obstacles); void swap(Region& other); void swapObstacles(Region& other) { m_obstacles.swap(other.m_obstacles); } private: Region& operator=(Region const&); unsigned m_knownNewObstacles; QRect m_bounds; std::vector m_obstacles; }; void init(BinaryImage const& img); void subdivideUsingObstacles(Region const& region); void subdivideUsingRaster(Region const& region); void subdivide(Region const& region, QRect bounds, QRect pivot); QRect findPivotObstacle(Region const& region) const; QPoint findBlackPixelCloseToCenter(QRect non_white_rect) const; QRect extendBlackPixelToBlackBox(QPoint pixel, QRect bounds) const; IntegralImage m_integralImg; std::auto_ptr m_ptrQueuedRegions; std::vector m_newObstacles; QSize m_minSize; }; namespace max_whitespace_finder { class PriorityStorage { protected: typedef MaxWhitespaceFinder::Region Region; public: virtual ~PriorityStorage() {} virtual bool empty() const = 0; virtual size_t size() const = 0; virtual Region& top() = 0; virtual void push(Region& region) = 0; virtual void pop() = 0; }; template class PriorityStorageImpl : public PriorityStorage { public: PriorityStorageImpl(QualityCompare comp) : m_qualityLess(comp) {} virtual bool empty() const { return m_priorityQueue.empty(); } virtual size_t size() const { return m_priorityQueue.size(); } virtual Region& top() { return m_priorityQueue.front(); } virtual void push(Region& region); virtual void pop(); private: class ProxyComparator { public: ProxyComparator(QualityCompare delegate) : m_delegate(delegate) {} bool operator()(Region const& lhs, Region const& rhs) const { return m_delegate(lhs.bounds(), rhs.bounds()); } private: QualityCompare m_delegate; }; void pushHeap( 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; }; template void PriorityStorageImpl::push(Region& region) { m_priorityQueue.push_back(region); m_priorityQueue.back().swapObstacles(region); pushHeap(m_priorityQueue.begin(), m_priorityQueue.end()); } template void PriorityStorageImpl::pop() { 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 void PriorityStorageImpl::pushHeap( std::deque::iterator const begin, std::deque::iterator const end) { typedef std::vector::iterator::difference_type Distance; 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; } } /** * 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 void PriorityStorageImpl::popHeap( std::deque::iterator const begin, std::deque::iterator const end) { // Swap the first (top) and the last elements. begin->swap(*(end - 1)); typedef std::vector::iterator::difference_type Distance; Distance const 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) { Distance const 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. Distance const 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); } } // namespace max_whitespace_finder template MaxWhitespaceFinder::MaxWhitespaceFinder( QualityCompare const comp, BinaryImage const& img, QSize const min_size) : m_integralImg(img.size()), m_ptrQueuedRegions( new max_whitespace_finder::PriorityStorageImpl(comp)), m_minSize(min_size) { init(img); } } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/MorphGradientDetect.cpp000066400000000000000000000026361271170121200240340ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "MorphGradientDetect.h" #include "Morphology.h" #include "Grayscale.h" #include "GrayRasterOp.h" #include #include namespace imageproc { GrayImage morphGradientDetectDarkSide(GrayImage const& image, QSize const& area) { GrayImage lighter(erodeGray(image, area, 0x00)); grayRasterOp >(lighter, image); return lighter; } GrayImage morphGradientDetectLightSide(GrayImage const& image, QSize const& area) { GrayImage darker(dilateGray(image, area, 0xff)); grayRasterOp >(darker, image); return darker; } } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/MorphGradientDetect.h000066400000000000000000000045511271170121200234770ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_MORPHGRADIENTDETECT_H_ #define IMAGEPROC_MORPHGRADIENTDETECT_H_ class QSize; namespace imageproc { class GrayImage; /** * \brief Morphological gradient detection. * * This function finds the the difference between the gray level of a pixel * and the gray level of the lightest pixel in its neighborhood, which * is specified by the \p area parameter. * The DarkSide in the name suggests that given a dark-to-light transition, * the gradient will be detected on the dark side. * \par * There is no requirement for the source image to be grayscale. A grayscale * version will be created transparently, if necessary. * \par * Smoothing the image before calling this function is often a good idea, * especially for black and white images. */ GrayImage morphGradientDetectDarkSide(GrayImage const& image, QSize const& area); /** * \brief Morphological gradient detection. * * This function finds the the difference between the gray level of a pixel * and the gray level of the darkest pixel in its neighborhood, which * is specified by the \p area parameter. * The LightSide in the name suggests that given a dark-to-light transition, * the gradient will be detected on the light side. * \par * There is no requirement for the source image to be grayscale. A grayscale * version will be created transparently, if necessary. * \par * Smoothing the image before calling this function is often a good idea, * especially for black and white images. */ GrayImage morphGradientDetectLightSide(GrayImage const& image, QSize const& area); } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/Morphology.cpp000066400000000000000000001023251271170121200222730ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Morphology.h" #include "BinaryImage.h" #include "GrayImage.h" #include "RasterOp.h" #include "Grayscale.h" #include #include #include #include #ifndef Q_MOC_RUN #include #endif #include #include #include #include #include #include namespace imageproc { Brick::Brick(QSize const& size) { int const x_origin = size.width() >> 1; int const 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(QSize const& size, QPoint const& origin) { int const x_origin = origin.x(); int const 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) { } void Brick::flip() { int const new_min_x = -m_maxX; m_maxX = -m_minX; m_minX = new_min_x; int const new_min_y = -m_maxY; m_maxY = -m_minY; m_minY = new_min_y; } Brick Brick::flipped() const { Brick brick(*this); brick.flip(); return brick; } namespace { class ReusableImages { public: void store(BinaryImage& img); BinaryImage retrieveOrCreate(QSize const& size); private: std::vector m_images; }; void ReusableImages::store(BinaryImage& img) { assert(!img.isNull()); // Using push_back(null_image) then swap() avoids atomic operations // inside BinaryImage. m_images.push_back(BinaryImage()); m_images.back().swap(img); } BinaryImage ReusableImages::retrieveOrCreate(QSize const& size) { if (m_images.empty()) { return BinaryImage(size); } else { BinaryImage img; m_images.back().swap(img); m_images.pop_back(); return img; } } class CoordinateSystem { public: /** * \brief Constructs a global coordinate system. */ CoordinateSystem() : m_origin(0, 0) {} /** * \brief Constructs a coordinate system relative to the global system. */ CoordinateSystem(QPoint const& origin) : m_origin(origin) {} QPoint const& origin() const { return m_origin; } QRect fromGlobal(QRect const& rect) const { return rect.translated(-m_origin); } QRect toGlobal(QRect const& rect) const { return rect.translated(m_origin); } QRect mapTo(QRect const& rect, CoordinateSystem const& target_cs) const { return rect.translated(m_origin).translated(-target_cs.origin()); } QPoint offsetTo(CoordinateSystem const& target_cs) const { return m_origin - target_cs.origin(); } private: QPoint m_origin; }; void adjustToFit(QRect const& 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); } inline QRect extendByBrick(QRect const& rect, Brick const& brick) { return rect.adjusted(brick.minX(), brick.minY(), brick.maxX(), brick.maxY()); } inline QRect shrinkByBrick(QRect const& rect, Brick const& brick) { return rect.adjusted(brick.maxX(), brick.maxY(), brick.minX(), brick.minY()); } static int const COMPOSITE_THRESHOLD = 8; void doInitialCopy( BinaryImage& dst, CoordinateSystem const& dst_cs, QRect const& dst_relevant_rect, BinaryImage const& src, CoordinateSystem const& src_cs, BWColor const initial_color, int const dx, int const 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); rasterOp(dst, dst_rect, src, src_rect.topLeft()); dst.fillFrame(dst_relevant_rect, dst_rect, initial_color); } void spreadInto( BinaryImage& dst, CoordinateSystem const& dst_cs, QRect const& dst_relevant_rect, BinaryImage const& src, CoordinateSystem const& src_cs, int const dx_min, int const dx_step, int const dy_min, int const dy_step, int const num_steps, AbstractRasterOp const& rop) { 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); adjustToFit(dst_relevant_rect, dst_rect, src_rect); rop(dst, dst_rect, src, src_rect.topLeft()); } } void spreadInDirectionLow( BinaryImage& dst, CoordinateSystem const& dst_cs, QRect const& dst_relevant_rect, BinaryImage const& src, CoordinateSystem const& src_cs, int const dx_min, int const dx_step, int const dy_min, int const dy_step, int const num_steps, AbstractRasterOp const& rop, BWColor const initial_color, bool const dst_composition_allowed) { assert(dx_step == 0 || dy_step == 0); if (num_steps == 0) { return; } doInitialCopy( dst, dst_cs, dst_relevant_rect, src, src_cs, initial_color, dx_min, dy_min ); 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; 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); 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; } 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 ); } } void spreadInDirection( BinaryImage& dst, CoordinateSystem const& dst_cs, QRect const& dst_relevant_rect, BinaryImage const& src, CoordinateSystem const& src_cs, ReusableImages& tmp_images, CoordinateSystem const& tmp_cs, QSize const& tmp_image_size, int const dx_min, int const dx_step, int const dy_min, int const dy_step, int const num_steps, AbstractRasterOp const& rop, BWColor const initial_color, bool const dst_composition_allowed) { 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 ); return; } int const first_phase_steps = (int)sqrt((double)num_steps); 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 ); int const 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 ); int const steps_done = first_phase_steps * second_phase_steps; int const 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 ); spreadInto( dst, dst_cs, dst_relevant_rect, tmp, tmp_cs, 0, 0, 0, 0, 1, rop ); } tmp_images.store(tmp); } void dilateOrErodeBrick( BinaryImage& dst, BinaryImage const& src, Brick const& brick, QRect const& dst_area, BWColor const src_surroundings, AbstractRasterOp const& rop, BWColor const 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; } CoordinateSystem const src_cs; // global coordinate system CoordinateSystem const dst_cs(dst_area.topLeft()); QRect const 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; } } QRect const 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()); QRect const 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); } } class Darker { 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); } }; template void fillExtremumArrayLeftHalf( uint8_t* dst, uint8_t const* const src_center, int const src_delta, int const src_first_offset, int const src_center_offset) { uint8_t const* 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; } } template void fillExtremumArrayRightHalf( uint8_t* dst, uint8_t const* const src_center, int const src_delta, int const src_center_offset, int const src_last_offset) { uint8_t const* 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; } } template void spreadGrayHorizontal( GrayImage& dst, GrayImage const& src, int const dy, int const dx1, int const dx2) { int const src_stride = src.stride(); int const dst_stride = dst.stride(); uint8_t const* src_line = src.data() + dy * src_stride; uint8_t* dst_line = dst.data(); int const dst_width = dst.width(); int const dst_height = dst.height(); int const 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) { int const dst_segment_last = std::min( dst_segment_first + se_len, dst_width ) - 1; // inclusive int const src_segment_first = dst_segment_first + dx1; int const src_segment_last = dst_segment_last + dx2; int const 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) { int const src_first = x + dx1; int const 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; } } template void spreadGrayHorizontal( GrayImage& dst, CoordinateSystem const& dst_cs, GrayImage const& src, CoordinateSystem const& src_cs, int const dy, int const dx1, int const dx2) { // src_point = dst_point + dst_to_src; QPoint const 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() ); } template void spreadGrayVertical( GrayImage& dst, GrayImage const& src, int const dx, int const dy1, int const dy2) { int const src_stride = src.stride(); int const dst_stride = dst.stride(); uint8_t const* const src_data = src.data() + dx; uint8_t* const dst_data = dst.data(); int const dst_width = dst.width(); int const dst_height = dst.height(); int const 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) { int const dst_segment_last = std::min( dst_segment_first + se_len, dst_height ) - 1; // inclusive int const src_segment_first = dst_segment_first + dy1; int const src_segment_last = dst_segment_last + dy2; int const 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) { int const src_first = y + dy1; int const 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; } } } } template void spreadGrayVertical( GrayImage& dst, CoordinateSystem const& dst_cs, GrayImage const& src, CoordinateSystem const& src_cs, int const dx, int const dy1, int const dy2) { // src_point = dst_point + dst_to_src; QPoint const 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() ); } GrayImage extendGrayImage( GrayImage const& src, QRect const& dst_area, uint8_t const background) { GrayImage dst(dst_area.size()); CoordinateSystem const dst_cs(dst_area.topLeft()); QRect const src_rect_in_dst_cs(dst_cs.fromGlobal(src.rect())); QRect const 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); return dst; } uint8_t const* src_line = src.data(); uint8_t* dst_line = dst.data(); int const src_stride = src.stride(); int const 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 const front_span_len = bound_src_rect_in_dst_cs.left(); int const data_span_len = bound_src_rect_in_dst_cs.width(); int const back_span_offset = front_span_len + data_span_len; int const back_span_len = dst_area.width() - back_span_offset; QPoint const 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_stride; dst_line += dst_stride; } int const height = dst_area.height(); for (; y < height; ++y, dst_line += dst_stride) { memset(dst_line, background, dst_stride); } return dst; } template GrayImage dilateOrErodeGray( GrayImage const& src, Brick const& brick, QRect const& dst_area, unsigned char const src_surroundings) { 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; } CoordinateSystem const 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. Brick const 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: Brick const collect_area1( collect_area.minX(), collect_area.minY(), collect_area.maxX(), collect_area.minY() ); Brick const collect_area2( 0, 0, 0, collect_area.maxY() - collect_area.minY() ); QRect const 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. { QRect const 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 { QRect const 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() ); } } return dst; } } // anonymous namespace BinaryImage dilateBrick( BinaryImage const& src, Brick const& brick, QRect const& dst_area, BWColor const 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; } GrayImage dilateGray( GrayImage const& src, Brick const& brick, QRect const& dst_area, unsigned char const 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( BinaryImage const& src, Brick const& brick, BWColor const src_surroundings) { return dilateBrick(src, brick, src.rect(), src_surroundings); } GrayImage dilateGray( GrayImage const& src, Brick const& brick, unsigned char const src_surroundings) { return dilateGray(src, brick, src.rect(), src_surroundings); } BinaryImage erodeBrick( BinaryImage const& src, Brick const& brick, QRect const& dst_area, BWColor const 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; } GrayImage erodeGray( GrayImage const& src, Brick const& brick, QRect const& dst_area, unsigned char const 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( BinaryImage const& src, Brick const& brick, BWColor const src_surroundings) { return erodeBrick(src, brick, src.rect(), src_surroundings); } GrayImage erodeGray( GrayImage const& src, Brick const& brick, unsigned char const src_surroundings) { return erodeGray(src, brick, src.rect(), src_surroundings); } BinaryImage openBrick( BinaryImage const& src, QSize const& brick, QRect const& dst_area, BWColor const 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"); } Brick actual_brick(brick); 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); } // 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); CoordinateSystem tmp_cs(tmp_area.topLeft()); BinaryImage const tmp( erodeBrick(src, actual_brick, tmp_area, src_surroundings) ); actual_brick.flip(); return dilateBrick( tmp, actual_brick, tmp_cs.fromGlobal(dst_area), src_surroundings ); } BinaryImage openBrick( BinaryImage const& src, QSize const& brick, BWColor const src_surroundings) { return openBrick(src, brick, src.rect(), src_surroundings); } GrayImage openGray( GrayImage const& src, QSize const& brick, QRect const& dst_area, unsigned char const 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"); } Brick const brick1(brick); Brick const brick2(brick1.flipped()); // We are going to make two operations: // tmp = erodeGray(src, brick1), then dst = dilateGray(tmp, brick2) QRect const tmp_rect(extendByBrick(dst_area, brick1)); CoordinateSystem tmp_cs(tmp_rect.topLeft()); GrayImage const tmp( dilateOrErodeGray(src, brick1, tmp_rect, src_surroundings) ); return dilateOrErodeGray( tmp, brick2, tmp_cs.fromGlobal(dst_area), src_surroundings ); } GrayImage openGray( GrayImage const& src, QSize const& brick, unsigned char const src_surroundings) { return openGray(src, brick, src.rect(), src_surroundings); } BinaryImage closeBrick( BinaryImage const& src, QSize const& brick, QRect const& dst_area, BWColor const 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"); } Brick actual_brick(brick); 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); } // 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); CoordinateSystem tmp_cs(tmp_area.topLeft()); BinaryImage const tmp( dilateBrick(src, actual_brick, tmp_area, src_surroundings) ); actual_brick.flip(); return erodeBrick( tmp, actual_brick, tmp_cs.fromGlobal(dst_area), src_surroundings ); } BinaryImage closeBrick( BinaryImage const& src, QSize const& brick, BWColor const src_surroundings) { return closeBrick(src, brick, src.rect(), src_surroundings); } GrayImage closeGray( GrayImage const& src, QSize const& brick, QRect const& dst_area, unsigned char const 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"); } Brick const brick1(brick); Brick const brick2(brick1.flipped()); // We are going to make two operations: // tmp = dilateGray(src, brick1), then dst = erodeGray(tmp, brick2) QRect const tmp_rect(extendByBrick(dst_area, brick2)); CoordinateSystem tmp_cs(tmp_rect.topLeft()); GrayImage const tmp( dilateOrErodeGray(src, brick1, tmp_rect, src_surroundings) ); return dilateOrErodeGray( tmp, brick2, tmp_cs.fromGlobal(dst_area), src_surroundings ); } GrayImage closeGray( GrayImage const& src, QSize const& brick, unsigned char const src_surroundings) { return closeGray(src, brick, src.rect(), src_surroundings); } BinaryImage hitMissMatch( BinaryImage const& src, BWColor const src_surroundings, std::vector const& hits, std::vector const& misses) { if (src.isNull()) { return BinaryImage(); } QRect const rect(src.rect()); // same as dst.rect() BinaryImage dst(src.size()); bool first = true; BOOST_FOREACH (QPoint const& 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); } } BOOST_FOREACH (QPoint const& 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 (first) { dst.fill(WHITE); // No matches. } return dst; } BinaryImage hitMissMatch( BinaryImage const& src, BWColor const src_surroundings, char const* const pattern, int const pattern_width, int const pattern_height, QPoint const& pattern_origin) { std::vector hits; std::vector misses; char const* 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( BinaryImage const& src, BWColor const src_surroundings, char const* const pattern, int const pattern_width, int const pattern_height) { BinaryImage dst(src); hitMissReplaceInPlace( dst, src_surroundings, pattern, pattern_width, pattern_height ); return dst; } void hitMissReplaceInPlace( BinaryImage& img, BWColor const src_surroundings, char const* const pattern, int const pattern_width, int const 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. int const pattern_len = pattern_width * pattern_height; char const* const minus_pos = (char const*)memchr(pattern, '-', pattern_len); char const* const plus_pos = (char const*)memchr(pattern, '+', pattern_len); char const* 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; } QPoint const origin( (origin_pos - pattern) % pattern_width, (origin_pos - pattern) / pattern_width ); std::vector hits; std::vector misses; std::vector white_to_black; std::vector black_to_white; char const* 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" ); } } } BinaryImage const matches(hitMissMatch(img, src_surroundings, hits, misses)); QRect const rect(img.rect()); BOOST_FOREACH (QPoint const& 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() ); } BOOST_FOREACH (QPoint const& 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() ); } } } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/Morphology.h000066400000000000000000000275101271170121200217420ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_MORPHOLOGY_H_ #define IMAGEPROC_MORPHOLOGY_H_ #include "BWColor.h" #include class QSize; class QRect; class QPoint; namespace imageproc { class BinaryImage; class GrayImage; class Brick { public: /** * \brief Constructs a brick with origin at the center. */ Brick(QSize const& 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(QSize const& size, QPoint const& 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; }; /** * \brief Turn every black pixel into a brick of black pixels. * * \param src The source image. * \param brick The brick to turn each black pixel into. * \param dst_area The area in source image coordinates that * will be returned as a destination image. It doesn't have * to fit into the source image area. * \param src_surroundings The color of pixels that are assumed to * surround the source image. */ BinaryImage dilateBrick( BinaryImage const& src, Brick const& brick, QRect const& dst_area, BWColor src_surroundings = WHITE); /** * \brief Same as above, but assumes dst_rect == src.rect() */ BinaryImage dilateBrick( BinaryImage const& src, Brick const& brick, BWColor src_surroundings = WHITE); /** * \brief Spreads darker pixels over the brick's area. * * \param src The source image. * \param brick The area to spread darker pixels into. * \param dst_area The area in source image coordinates that * will be returned as a destination image. It doesn't have * to fit into the source image area. * \param src_surroundings The color of pixels that are assumed to * surround the source image. */ GrayImage dilateGray( GrayImage const& src, Brick const& brick, QRect const& dst_area, unsigned char src_surroundings = 0xff); /** * \brief Same as above, but assumes dst_rect == src.rect() */ GrayImage dilateGray( GrayImage const& src, Brick const& brick, unsigned char src_surroundings = 0xff); /** * \brief Turn every white pixel into a brick of white pixels. * * \param src The source image. * \param brick The brick to turn each white pixel into. * \param dst_area The area in source image coordinates that * will be returned as a destination image. It doesn't have * to fit into the source image area. * \param src_surroundings The color of pixels that are assumed to * surround the source image. */ BinaryImage erodeBrick( BinaryImage const& src, Brick const& brick, QRect const& dst_area, BWColor src_surroundings = BLACK); /** * \brief Same as above, but assumes dst_rect == src.rect() */ BinaryImage erodeBrick( BinaryImage const& src, Brick const& brick, BWColor src_surroundings = BLACK); /** * \brief Spreads lighter pixels over the brick's area. * * \param src The source image. * \param brick The area to spread lighter pixels into. * \param dst_area The area in source image coordinates that * will be returned as a destination image. It doesn't have * to fit into the source image area. * \param src_surroundings The color of pixels that are assumed to * surround the source image. */ GrayImage erodeGray( GrayImage const& src, Brick const& brick, QRect const& dst_area, unsigned char src_surroundings = 0x00); /** * \brief Same as above, but assumes dst_rect == src.rect() */ GrayImage erodeGray( GrayImage const& src, Brick const& brick, unsigned char src_surroundings = 0x00); /** * \brief Turn the black areas where the brick doesn't fit, into white. * * \param src The source image. * \param brick The brick to fit into black areas. * \param dst_area The area in source image coordinates that * will be returned as a destination image. It doesn't have * to fit into the source image area. * \param src_surroundings The color of pixels that are assumed to * surround the source image. If set to BLACK, a brick will be able * to fit by going partially off-screen (off the source * image area actually). */ BinaryImage openBrick( BinaryImage const& src, QSize const& brick, QRect const& dst_area, BWColor src_surroundings = WHITE); /** * \brief Same as above, but assumes dst_rect == src.rect() */ BinaryImage openBrick( BinaryImage const& src, QSize const& brick, BWColor src_surroundings = WHITE); /** * \brief Remove dark areas smaller than the structuring element. * * \param src The source image. * \param brick The structuring element. * \param dst_area The area in source image coordinates that * will be returned as a destination image. It doesn't have * to fit into the source image area. * \param src_surroundings The color of pixels that are assumed to * surround the source image. */ GrayImage openGray( GrayImage const& src, QSize const& brick, QRect const& dst_area, unsigned char src_surroundings); /** * \brief Same as above, but assumes dst_rect == src.rect() */ GrayImage openGray( GrayImage const& src, QSize const& brick, unsigned char src_surroundings); /** * \brief Turn the white areas where the brick doesn't fit, into black. * * \param src The source image. * \param brick The brick to fit into white areas. * \param dst_area The area in source image coordinates that * will be returned as a destination image. It doesn't have * to fit into the source image area. * \param src_surroundings The color of pixels that are assumed to * surround the source image. If set to WHITE, a brick will be able * to fit by going partially off-screen (off the source * image area actually). */ BinaryImage closeBrick( BinaryImage const& src, QSize const& brick, QRect const& dst_area, BWColor src_surroundings = WHITE); /** * \brief Same as above, but assumes dst_rect == src.rect() */ BinaryImage closeBrick( BinaryImage const& src, QSize const& brick, BWColor src_surroundings = WHITE); /** * \brief Remove light areas smaller than the structuring element. * * \param src The source image. * \param brick The structuring element. * \param dst_area The area in source image coordinates that * will be returned as a destination image. It doesn't have * to fit into the source image area. * \param src_surroundings The color of pixels that are assumed to * surround the source image. */ GrayImage closeGray( GrayImage const& src, QSize const& brick, QRect const& dst_area, unsigned char src_surroundings); /** * \brief Same as above, but assumes dst_rect == src.rect() */ GrayImage closeGray( GrayImage const& src, QSize const& brick, unsigned char src_surroundings); /** * \brief Performs a hit-miss matching operation. * * \param src The input image. * \param src_surroundings The color that is assumed to be outside of the * input image. * \param hits Offsets to hit positions relative to the origin point. * \param misses Offsets to miss positions relative to the origin point. * \return A binary image where black pixels indicate a successful pattern match. */ BinaryImage hitMissMatch( BinaryImage const& src, BWColor src_surroundings, std::vector const& hits, std::vector const& misses); /** * \brief A more user-friendly version of a hit-miss match operation. * * \param src The input image. * \param src_surroundings The color that is assumed to be outside of the * input image. * \param pattern A string representing a pattern. Example: * \code * char const* pattern = * "?X?" * "X X" * "?X?"; * \endcode * Here X stads for a hit (black pixel) and [space] stands for a miss * (white pixel). Question marks indicate pixels that we are not interested in. * \param pattern_width The width of the pattern. * \param pattern_height The height of the pattern. * \param pattern_origin A point usually within the pattern indicating where * to place a mark if the pattern matches. * \return A binary image where black pixels indicate a successful pattern match. */ BinaryImage hitMissMatch( BinaryImage const& src, BWColor src_surroundings, char const* pattern, int pattern_width, int pattern_height, QPoint const& pattern_origin); /** * \brief Does a hit-miss match and modifies user-specified pixels. * * \param src The input image. * \param src_surroundings The color that is assumed to be outside of the * input image. * \param pattern A string representing a pattern. Example: * \code * char const* pattern = * " - " * "X+X" * "XXX"; * \endcode * Pattern characters have the following meaning:\n * 'X': A black pixel.\n * ' ': A white pixel.\n * '-': A black pixel we want to turn into white.\n * '+': A white pixel we want to turn into black.\n * '?': Any pixel, we don't care which.\n * \param pattern_width The width of the pattern. * \param pattern_height The height of the pattern. * \return The result of a match-and-replace operation. */ BinaryImage hitMissReplace( BinaryImage const& src, BWColor src_surroundings, char const* pattern, int pattern_width, int pattern_height); /** * \brief Does a hit-miss match and modifies user-specified pixels. * * \param[in,out] img The image to make replacements in. * \param src_surroundings The color that is assumed to be outside of the * input image. * \param pattern A string representing a pattern. Example: * \code * char const* pattern = * " - " * "X+X" * "XXX"; * \endcode * Pattern characters have the following meaning:\n * 'X': A black pixel.\n * ' ': A white pixel.\n * '-': A black pixel we want to turn into white.\n * '+': A white pixel we want to turn into black.\n * '?': Any pixel, we don't care which.\n * \param pattern_width The width of the pattern. * \param pattern_height The height of the pattern. */ void hitMissReplaceInPlace( BinaryImage& img, BWColor src_surroundings, char const* pattern, int pattern_width, int pattern_height); } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/OrthogonalRotation.cpp000066400000000000000000000107211271170121200237660ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "OrthogonalRotation.h" #include "BinaryImage.h" #include "BWColor.h" #include "RasterOp.h" #include #include #include #include namespace imageproc { static inline uint32_t mask(int x) { return (uint32_t(1) << 31) >> (x % 32); } static BinaryImage rotate0(BinaryImage const& src, QRect const& src_rect) { if (src_rect == src.rect()) { return src; } BinaryImage dst(src_rect.width(), src_rect.height()); rasterOp(dst, dst.rect(), src, src_rect.topLeft()); return dst; } static BinaryImage rotate90(BinaryImage const& src, QRect const& src_rect) { int const dst_w = src_rect.height(); int const dst_h = src_rect.width(); BinaryImage dst(dst_w, dst_h); dst.fill(WHITE); int const src_wpl = src.wordsPerLine(); int const dst_wpl = dst.wordsPerLine(); uint32_t const* 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) { int const src_x = src_rect.left() + dst_y; uint32_t const* src_pword = src_data + src_x / 32; uint32_t const 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; } return dst; } static BinaryImage rotate180(BinaryImage const& src, QRect const& src_rect) { int const dst_w = src_rect.width(); int const dst_h = src_rect.height(); BinaryImage dst(dst_w, dst_h); dst.fill(WHITE); int const src_wpl = src.wordsPerLine(); int const dst_wpl = dst.wordsPerLine(); uint32_t const* 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; } return dst; } static BinaryImage rotate270(BinaryImage const& src, QRect const& src_rect) { int const dst_w = src_rect.height(); int const dst_h = src_rect.width(); BinaryImage dst(dst_w, dst_h); dst.fill(WHITE); int const src_wpl = src.wordsPerLine(); int const dst_wpl = dst.wordsPerLine(); uint32_t const* 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) { int const src_x = src_rect.right() - dst_y; uint32_t const* src_pword = src_data + src_x / 32; uint32_t const 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; } return dst; } BinaryImage orthogonalRotation( BinaryImage const& src, QRect const& src_rect, int const 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"); } } BinaryImage orthogonalRotation(BinaryImage const& src, int const degrees) { return orthogonalRotation(src, src.rect(), degrees); } } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/OrthogonalRotation.h000066400000000000000000000034671271170121200234440ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_ORTHOGONAL_ROTATION_H_ #define IMAGEPROC_ORTHOGONAL_ROTATION_H_ class QRect; namespace imageproc { class BinaryImage; /** * \brief Rotation by 0, 90, 180 or 270 degrees. * * \param src The source image. May be null, in which case * a null rotated image will be returned. * \param src_rect The area that is to be rotated. * \param degrees The rotation angle in degrees. The angle * must be a multiple of 90. Positive values indicate * clockwise rotation. * \return The rotated area of the source image. The dimensions * of the returned image will correspond to \p src_rect, * possibly with width and height swapped. */ BinaryImage orthogonalRotation( BinaryImage const& src, QRect const& src_rect, int degrees); /** * \brief Rotation by 90, 180 or 270 degrees. * * This is an overload provided for convenience. * It rotates the whole image, not a portion of it. */ BinaryImage orthogonalRotation(BinaryImage const& src, int degrees); } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/PolygonRasterizer.cpp000066400000000000000000000346411271170121200236430ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "PolygonRasterizer.h" #include "PolygonUtils.h" #include "BinaryImage.h" #include #include #include #include #include #include #include #ifndef Q_MOC_RUN #include #endif #include #include #include #include #include #include #include #include namespace imageproc { /** * \brief A non-horizontal and non zero-length polygon edge. */ class PolygonRasterizer::Edge { public: Edge(QPointF const& top, QPointF const& bottom, int vert_direction); Edge(QPointF const& from, QPointF const& to); QPointF const& top() const { return m_top; } QPointF const& bottom() const { return m_bottom; } double topY() const { return m_top.y(); } double bottomY() const { return m_bottom.y(); } double xForY(double y) const; 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 }; /** * \brief A non-overlaping edge component. */ class PolygonRasterizer::EdgeComponent { public: EdgeComponent(Edge const* edge, double top, double bottom) : m_top(top), m_bottom(bottom), m_x(), m_pEdge(edge) {} double top() const { return m_top; } double bottom() const { return m_bottom; } Edge const& edge() const { return *m_pEdge; } double x() const { return m_x; } void setX(double x) { m_x = x; } private: double m_top; double m_bottom; double m_x; Edge const* m_pEdge; }; class PolygonRasterizer::EdgeOrderY { public: bool operator()(EdgeComponent const& lhs, EdgeComponent const& rhs) const { return lhs.top() < rhs.top(); } bool operator()(EdgeComponent const& lhs, double rhs) const { return lhs.bottom() <= rhs; // bottom is not a part of the interval. } bool operator()(double lhs, EdgeComponent const& rhs) const { return lhs < rhs.top(); } }; class PolygonRasterizer::EdgeOrderX { public: bool operator()(EdgeComponent const& lhs, EdgeComponent const& rhs) const { return lhs.x() < rhs.x(); } }; class PolygonRasterizer::Rasterizer { public: Rasterizer(QRect const& image_rect, QPolygonF const& poly, Qt::FillRule const fill_rule, bool invert); void fillBinary(BinaryImage& image, BWColor color) const; void fillGrayscale(QImage& image, uint8_t color) const; private: void prepareEdges(); static void oddEvenLineBinary( EdgeComponent const* edges, int num_edges, uint32_t* line, uint32_t pattern); static void oddEvenLineGrayscale( EdgeComponent const* edges, int num_edges, uint8_t* line, uint8_t color); static void windingLineBinary( EdgeComponent const* edges, int num_edges, uint32_t* line, uint32_t pattern, bool invert); static void windingLineGrayscale( EdgeComponent const* edges, int num_edges, uint8_t* line, uint8_t pattern, bool invert); 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; }; /*============================= PolygonRasterizer ===========================*/ void PolygonRasterizer::fill( BinaryImage& image, BWColor const color, QPolygonF const& poly, Qt::FillRule const fill_rule) { if (image.isNull()) { throw std::invalid_argument("PolygonRasterizer: target image is null"); } Rasterizer rasterizer(image.rect(), poly, fill_rule, false); rasterizer.fillBinary(image, color); } void PolygonRasterizer::fillExcept( BinaryImage& image, BWColor const color, QPolygonF const& poly, Qt::FillRule const fill_rule) { if (image.isNull()) { throw std::invalid_argument("PolygonRasterizer: target image is null"); } Rasterizer rasterizer(image.rect(), poly, fill_rule, true); rasterizer.fillBinary(image, color); } void PolygonRasterizer::grayFill( QImage& image, unsigned char const color, QPolygonF const& poly, Qt::FillRule const 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); } void PolygonRasterizer::grayFillExcept( QImage& image, unsigned char const color, QPolygonF const& poly, Qt::FillRule const 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); } /*======================= PolygonRasterizer::Edge ==========================*/ PolygonRasterizer::Edge::Edge( QPointF const& top, QPointF const& bottom, int const 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(QPointF const& from, QPointF const& 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()); } double PolygonRasterizer::Edge::xForY(double y) const { double const fraction = (y - m_top.y()) * m_reDeltaY; return m_top.x() + m_deltaX * fraction; } /*=================== PolygonRasterizer::Rasterizer ====================*/ PolygonRasterizer::Rasterizer::Rasterizer( QRect const& image_rect, QPolygonF const& poly, Qt::FillRule const fill_rule, bool const invert) : 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(); m_fillPoly = path1.intersected(path2).toFillPolygon(); if (invert) { m_boundingBox = path1.subtracted(path2).boundingRect(); } else { m_boundingBox = m_fillPoly.boundingRect(); } prepareEdges(); } void PolygonRasterizer::Rasterizer::prepareEdges() { int const 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) { QPointF const from(m_fillPoly[i]); QPointF const to(m_fillPoly[i + 1]); if (from.y() != to.y()) { m_edges.push_back(Edge(from, to)); } } assert(m_fillPoly.isClosed()); if (m_invert) { // Add left and right edges with neutral direction (0), // to avoid confusing a winding fill. QRectF const rect(m_imageRect); m_edges.push_back(Edge(rect.topLeft(), rect.bottomLeft(), 0)); m_edges.push_back(Edge(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); BOOST_FOREACH(QPointF const& 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()); BOOST_FOREACH(Edge const& edge, m_edges) { std::vector::iterator it( std::lower_bound(y_values.begin(), y_values.end(), edge.topY()) ); assert(*it == edge.topY()); do { std::vector::iterator next(it); ++next; assert(next != y_values.end()); m_edgeComponents.push_back( EdgeComponent(&edge, *it, *next) ); it = next; } while (*it != edge.bottomY()); } std::sort(m_edgeComponents.begin(), m_edgeComponents.end(), EdgeOrderY()); } void PolygonRasterizer::Rasterizer::fillBinary( BinaryImage& image, BWColor const color) const { std::vector edges_for_line; typedef std::vector::const_iterator EdgeIter; uint32_t* line = image.data(); int const wpl = image.wordsPerLine(); uint32_t const pattern = (color == WHITE) ? 0 : ~uint32_t(0); int i = qRound(m_boundingBox.top()); line += i * wpl; int const limit = qRound(m_boundingBox.bottom()); for (; i < limit; ++i, line += wpl, edges_for_line.clear()) { double const y = i + 0.5; // Get edges intersecting this horizontal line. std::pair const 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. BOOST_FOREACH(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(), edges_for_line.size(), line, pattern ); } else { windingLineBinary( &edges_for_line.front(), edges_for_line.size(), line, pattern, m_invert ); } } } void PolygonRasterizer::Rasterizer::fillGrayscale( QImage& image, uint8_t const color) const { std::vector edges_for_line; typedef std::vector::const_iterator EdgeIter; uint8_t* line = image.bits(); int const bpl = image.bytesPerLine(); int i = qRound(m_boundingBox.top()); line += i * bpl; int const limit = qRound(m_boundingBox.bottom()); for (; i < limit; ++i, line += bpl, edges_for_line.clear()) { double const y = i + 0.5; // Get edges intersecting this horizontal line. std::pair const 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. BOOST_FOREACH(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(), edges_for_line.size(), line, color ); } else { windingLineGrayscale( &edges_for_line.front(), edges_for_line.size(), line, color, m_invert ); } } } void PolygonRasterizer::Rasterizer::oddEvenLineBinary( EdgeComponent const* const edges, int const num_edges, uint32_t* const line, uint32_t const pattern) { for (int i = 0; i < num_edges - 1; i += 2) { double const x_from = edges[i].x(); double const x_to = edges[i + 1].x(); fillBinarySegment( qRound(x_from), qRound(x_to), line, pattern ); } } void PolygonRasterizer::Rasterizer::oddEvenLineGrayscale( EdgeComponent const* const edges, int const num_edges, uint8_t* const line, uint8_t const color) { for (int i = 0; i < num_edges - 1; i += 2) { int const from = qRound(edges[i].x()); int const to = qRound(edges[i + 1].x()); memset(line + from, color, to - from); } } void PolygonRasterizer::Rasterizer::windingLineBinary( EdgeComponent const* const edges, int const num_edges, uint32_t* const line, uint32_t const 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) { double const x_from = edges[i].x(); double const x_to = edges[i + 1].x(); fillBinarySegment( qRound(x_from), qRound(x_to), line, pattern ); } } } void PolygonRasterizer::Rasterizer::windingLineGrayscale( EdgeComponent const* const edges, int const num_edges, uint8_t* const line, uint8_t const 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) { int const from = qRound(edges[i].x()); int const to = qRound(edges[i + 1].x()); memset(line + from, color, to - from); } } } void PolygonRasterizer::Rasterizer::fillBinarySegment( int const x_from, int const x_to, uint32_t* const line, uint32_t const pattern) { if (x_from == x_to) { return; } uint32_t const full_mask = ~uint32_t(0); uint32_t const first_word_mask = full_mask >> (x_from & 31); uint32_t const last_word_mask = full_mask << (31 - ((x_to - 1) & 31)); int const first_word_idx = x_from >> 5; int const last_word_idx = (x_to - 1) >> 5; // x_to is exclusive if (first_word_idx == last_word_idx) { uint32_t const 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 scantailor-RELEASE_0_9_12_2/imageproc/PolygonRasterizer.h000066400000000000000000000031311271170121200232760ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_POLYGONRASTERIZER_H_ #define IMAGEPROC_POLYGONRASTERIZER_H_ #include "BWColor.h" #include class QPolygonF; class QRectF; class QImage; namespace imageproc { class BinaryImage; class PolygonRasterizer { public: static void fill( BinaryImage& image, BWColor color, QPolygonF const& poly, Qt::FillRule fill_rule); static void fillExcept( BinaryImage& image, BWColor color, QPolygonF const& poly, Qt::FillRule fill_rule); static void grayFill( QImage& image, unsigned char color, QPolygonF const& poly, Qt::FillRule fill_rule); static void grayFillExcept( QImage& image, unsigned char color, QPolygonF const& poly, Qt::FillRule fill_rule); private: class Edge; class EdgeComponent; class EdgeOrderY; class EdgeOrderX; class Rasterizer; }; } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/PolygonUtils.cpp000066400000000000000000000137121271170121200226050ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "PolygonUtils.h" #include #include #include #ifndef Q_MOC_RUN #include #endif #include #include #include #include namespace imageproc { double const PolygonUtils::ROUNDING_MULTIPLIER = 1 << 12; double const PolygonUtils::ROUNDING_RECIP_MULTIPLIER = 1.0 / ROUNDING_MULTIPLIER; class PolygonUtils::Before { public: bool operator()(QPointF const& lhs, QPointF const& rhs) const { return compare(lhs, rhs) < 0; } bool operator()(QLineF const& lhs, QLineF const& 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(QPointF const& lhs, QPointF const& rhs) { double const dx = lhs.x() - rhs.x(); double const dy = lhs.y() - rhs.y(); if (fabs(dx) > 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; } }; QPolygonF PolygonUtils::round(QPolygonF const& poly) { QPolygonF rounded; rounded.reserve(poly.size()); BOOST_FOREACH (QPointF const& p, poly) { rounded.push_back(roundPoint(p)); } return rounded; } bool PolygonUtils::fuzzyCompare(QPolygonF const& poly1, QPolygonF const& poly2) { 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); 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)); if (edges1.size() != edges2.size()) { return false; } std::sort(edges1.begin(), edges1.end(), Before()); std::sort(edges2.begin(), edges2.end(), Before()); return fuzzyCompareImpl(edges1, edges2); } QPointF PolygonUtils::roundPoint(QPointF const& p) { return QPointF(roundValue(p.x()), roundValue(p.y())); } double PolygonUtils::roundValue(double const val) { return floor(val * ROUNDING_MULTIPLIER + 0.5) * ROUNDING_RECIP_MULTIPLIER; } std::vector PolygonUtils::extractAndNormalizeEdges(QPolygonF const& poly) { std::vector edges; int const 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; } void PolygonUtils::maybeAddNormalizedEdge( std::vector& edges, QPointF const& p1, QPointF const& p2) { if (fuzzyCompareImpl(p1, p2)) { return; } if (Before()(p2, p1)) { edges.push_back(QLineF(p2, p1)); } else { edges.push_back(QLineF(p1, p2)); } } bool PolygonUtils::fuzzyCompareImpl( std::vector const& lines1, std::vector const& lines2) { assert(lines1.size() == lines2.size()); size_t const size = lines1.size(); for (size_t i = 0; i < size; ++i) { if (!fuzzyCompareImpl(lines1[i], lines2[i])) { return false; } } return true; } bool PolygonUtils::fuzzyCompareImpl(QLineF const& line1, QLineF const& line2) { return fuzzyCompareImpl(line1.p1(), line2.p1()) && fuzzyCompareImpl(line1.p2(), line2.p2()); } bool PolygonUtils::fuzzyCompareImpl(QPointF const& p1, QPointF const& p2) { double const dx = fabs(p1.x() - p2.x()); double const dy = fabs(p1.y() - p2.y()); return dx <= ROUNDING_RECIP_MULTIPLIER && dy <= ROUNDING_RECIP_MULTIPLIER; } namespace { struct LexicographicPointComparator { bool operator()(QPointF const& p1, QPointF const& 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(QPointF const& O, QPointF const& A, QPointF const& B) { 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 int const n = 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]; } // 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); QPolygonF poly(k); BOOST_FOREACH(QPointF const& pt, hull) { poly << pt; } return poly; } } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/PolygonUtils.h000066400000000000000000000047271271170121200222600ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_POLYGONUTILS_H_ #define IMAGEPROC_POLYGONUTILS_H_ #include class QPolygonF; class QPointF; 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(QPolygonF const& 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(QPolygonF const& poly1, QPolygonF const& poly2); static QPolygonF convexHull(std::vector point_cloud); private: class Before; static QPointF roundPoint(QPointF const& p); static double roundValue(double val); static std::vector extractAndNormalizeEdges(QPolygonF const& poly); static void maybeAddNormalizedEdge( std::vector& edges, QPointF const& p1, QPointF const& p2); static bool fuzzyCompareImpl( std::vector const& lines1, std::vector const& lines2); static bool fuzzyCompareImpl(QLineF const& line1, QLineF const& line2); static bool fuzzyCompareImpl(QPointF const& p1, QPointF const& p2); static double const ROUNDING_MULTIPLIER; static double const ROUNDING_RECIP_MULTIPLIER; }; } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/PolynomialLine.cpp000066400000000000000000000033321271170121200230650ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "PolynomialLine.h" #include namespace imageproc { void PolynomialLine::validateArguments( int const degree, int const 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"); } } double PolynomialLine::calcScale(int const num_values) { if (num_values <= 1) { return 0.0; } else { return 1.0 / (num_values - 1); } } void PolynomialLine::prepareEquations( std::vector& equations, int const degree, int const num_values) { equations.reserve((degree + 1) * num_values); // Pretend that data points are positioned in range of [1, 2]. double const scale = calcScale(num_values); for (int i = 0; i < num_values; ++i) { double const position = 1.0 + i * scale; double pow = 1.0; for (int j = 0; j <= degree; ++j, pow *= position) { equations.push_back(pow); } } } } scantailor-RELEASE_0_9_12_2/imageproc/PolynomialLine.h000066400000000000000000000145611271170121200225400ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_POLYNOMIAL_LINE_H_ #define IMAGEPROC_POLYNOMIAL_LINE_H_ #include "LeastSquaresFit.h" #include #include #include #include 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, T const* 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 prepareEquations( std::vector& equations, int degree, int num_values); std::vector m_coeffs; }; template inline T PolynomialLine::StaticCastPostProcessor::operator()(double const val) const { return static_cast(val); } template PolynomialLine::RoundAndClipPostProcessor::RoundAndClipPostProcessor() : m_min(std::numeric_limits::min()), m_max(std::numeric_limits::max()) { } template inline T PolynomialLine::RoundAndClipPostProcessor::operator()(double const val) const { double const rounded = 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 PolynomialLine::PolynomialLine( int degree, T const* values, int const num_values, int const step) { validateArguments(degree, num_values); if (degree + 1 > num_values) { degree = num_values - 1; } QSize const dimensions(degree + 1, num_values); std::vector equations; std::vector data_points; data_points.resize(dimensions.height()); m_coeffs.resize(dimensions.width()); prepareEquations(equations, degree, num_values); for (int i = 0; i < num_values; ++i, values += step) { data_points[i] = *values; } leastSquaresFit(dimensions, &equations[0], &m_coeffs[0], &data_points[0]); } template void PolynomialLine::output(T* values, int num_values, int step) const { typedef DefaultPostProcessor< T, std::numeric_limits::is_integer > PP; output(values, num_values, step, PP()); } template void PolynomialLine::output( T* values, int num_values, int step, PostProcessor pp) const { if (num_values <= 0) { return; } // Pretend that data points are positioned in range of [1, 2]. double const scale = calcScale(num_values); for (int i = 0; i < num_values; ++i, values += step) { double const position = 1.0 + i * scale; double sum = 0.0; double pow = 1.0; std::vector::const_iterator it(m_coeffs.begin()); std::vector::const_iterator const end(m_coeffs.end()); for (; it != end; ++it, pow *= position) { sum += *it * pow; } *values = pp(sum); } } } #endif scantailor-RELEASE_0_9_12_2/imageproc/PolynomialSurface.cpp000066400000000000000000000213241271170121200235670ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see */ #include "PolynomialSurface.h" #include "LeastSquaresFit.h" #include "AlignedArray.h" #include "BinaryImage.h" #include "GrayImage.h" #include "Grayscale.h" #include "BitOps.h" #include #ifndef Q_MOC_RUN #include #endif #include #include #include #include #include namespace imageproc { PolynomialSurface::PolynomialSurface( int const hor_degree, int const vert_degree, GrayImage const& 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"); } int const num_data_points = src.width() * src.height(); if (num_data_points == 0) { m_horDegree = 0; m_vertDegree = 0; m_coeffs.push_back(0.0); return; } maybeReduceDegrees(num_data_points); int const num_terms = calcNumTerms(); QSize const dimensions(num_terms, num_data_points); std::vector equations; std::vector data_points; equations.reserve(dimensions.width() * dimensions.height()); data_points.reserve(dimensions.height()); m_coeffs.resize(dimensions.width()); prepareEquationsAndDataPoints(src, equations, data_points); assert(int(equations.size()) == dimensions.width() * dimensions.height()); assert(int(data_points.size()) == num_data_points); leastSquaresFit(dimensions, &equations[0], &m_coeffs[0], &data_points[0]); } PolynomialSurface::PolynomialSurface( int const hor_degree, int const vert_degree, GrayImage const& src, BinaryImage const& 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"); } int const num_data_points = mask.countBlackPixels(); if (num_data_points == 0) { m_horDegree = 0; m_vertDegree = 0; m_coeffs.push_back(0.0); return; } maybeReduceDegrees(num_data_points); int const num_terms = calcNumTerms(); QSize const dimensions(num_terms, num_data_points); std::vector equations; std::vector data_points; equations.reserve(dimensions.width() * dimensions.height()); data_points.reserve(dimensions.height()); m_coeffs.resize(dimensions.width()); prepareEquationsAndDataPoints(src, mask, equations, data_points); assert(int(equations.size()) == dimensions.width() * dimensions.height()); assert(int(data_points.size()) == num_data_points); leastSquaresFit(dimensions, &equations[0], &m_coeffs[0], &data_points[0]); } GrayImage PolynomialSurface::render(QSize const& size) const { if (size.isEmpty()) { return GrayImage(); } GrayImage image(size); int const width = size.width(); int const height = size.height(); unsigned char* line = image.data(); int const bpl = image.stride(); int const num_coeffs = m_coeffs.size(); // Pretend that both x and y positions of pixels // lie in range of [0, 1]. double const xscale = calcScale(width); double const yscale = calcScale(height); AlignedArray vert_matrix(num_coeffs * height); float* out = &vert_matrix[0]; for (int y = 0; y < height; ++y) { double const 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) { double const 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; } } } float const* vert_line = &vert_matrix[0]; for (int y = 0; y < height; ++y, line += bpl, vert_line += num_coeffs) { float const* 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]; } int const isum = (int)(sum * 255.0); line[x] = static_cast(qBound(0, isum, 255)); } } return image; } void PolynomialSurface::maybeReduceDegrees(int const num_data_points) { assert(num_data_points > 0); 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); } double PolynomialSurface::calcScale(int const dimension) { if (dimension <= 1) { return 0.0; } else { return 1.0 / (dimension - 1); } } void PolynomialSurface::prepareEquationsAndDataPoints( GrayImage const& image, std::vector& equations, std::vector& data_points) const { int const width = image.width(); int const height = image.height(); uint8_t const* line = image.data(); int const bpl = image.stride(); // Pretend that both x and y positions of pixels // lie in range of [0, 1]. double const xscale = calcScale(width); double const yscale = calcScale(height); for (int y = 0; y < height; ++y, line += bpl) { double const y_adjusted = yscale * y; for (int x = 0; x < width; ++x) { double const x_adjusted = xscale * x; data_points.push_back((1.0 / 255.0) * line[x]); double pow1 = 1.0; for (int i = 0; i <= m_vertDegree; ++i) { double pow2 = pow1; for (int j = 0; j <= m_horDegree; ++j) { equations.push_back(pow2); pow2 *= x_adjusted; } pow1 *= y_adjusted; } } } } void PolynomialSurface::prepareEquationsAndDataPoints( GrayImage const& image, BinaryImage const& mask, std::vector& equations, std::vector& data_points) const { int const width = image.width(); int const height = image.height(); double const xscale = calcScale(width); double const yscale = calcScale(height); uint8_t const* image_line = image.data(); int const image_bpl = image.stride(); uint32_t const* mask_line = mask.data(); int const mask_wpl = mask.wordsPerLine(); int const last_word_idx = (width - 1) >> 5; int const last_word_mask = ~uint32_t(0) << (31 - ((width - 1) & 31)); for (int y = 0; y < height; ++y) { double const y_adjusted = y * yscale; int idx = 0; // Full words. for (; idx < last_word_idx; ++idx) { processMaskWord( image_line, mask_line[idx], idx, y, y_adjusted, xscale, equations, data_points ); } // Last word. processMaskWord( image_line, mask_line[idx] & last_word_mask, idx, y, y_adjusted, xscale, equations, data_points ); image_line += image_bpl; mask_line += mask_wpl; } } void PolynomialSurface::processMaskWord( uint8_t const* const image_line, uint32_t word, int const word_idx, int const y, double const y_adjusted, double const xscale, std::vector& equations, std::vector& data_points) const { uint32_t const msb = uint32_t(1) << 31; int const xbase = word_idx << 5; int x = xbase; uint32_t mask = msb; for (; word; word &= ~mask, mask >>= 1, ++x) { if (!(word & mask)) { // Skip a group of zero bits. int const offset = countMostSignificantZeroes(word); x = xbase + offset; mask = msb >> offset; assert(word & mask); } data_points.push_back((1.0 / 255.0) * image_line[x]); double const x_adjusted = xscale * x; double pow1 = 1.0; for (int i = 0; i <= m_vertDegree; ++i) { double pow2 = pow1; for (int j = 0; j <= m_horDegree; ++j) { equations.push_back(pow2); pow2 *= x_adjusted; } pow1 *= y_adjusted; } } } } scantailor-RELEASE_0_9_12_2/imageproc/PolynomialSurface.h000066400000000000000000000076451271170121200232460ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_POLYNOMIAL_SURFACE_H_ #define IMAGEPROC_POLYNOMIAL_SURFACE_H_ #include #include #include namespace imageproc { class BinaryImage; 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, GrayImage const& 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, GrayImage const& src, BinaryImage const& mask); /** * \brief Visualizes the polynomial surface as a grayscale image. * * The surface will be stretched / shrinked to fit the new size. */ GrayImage render(QSize const& size) const; private: void maybeReduceDegrees(int num_data_points); int calcNumTerms() const; static double calcScale(int dimension); void prepareEquationsAndDataPoints( GrayImage const& image, std::vector& equations, std::vector& data_points) const; void prepareEquationsAndDataPoints( GrayImage const& image, BinaryImage const& mask, std::vector& equations, std::vector& data_points) const; void processMaskWord( uint8_t const* image_line, uint32_t word, int mask_word_idx, int y, double y_adjusted, double xscale, std::vector& equations, std::vector& data_points) const; std::vector m_coeffs; int m_horDegree; int m_vertDegree; }; } #endif scantailor-RELEASE_0_9_12_2/imageproc/RastLineFinder.cpp000066400000000000000000000244331271170121200230100ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "RastLineFinder.h" #include "NumericTraits.h" #include "VecNT.h" #include "Constants.h" #include #include #include #include #include #include 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) { } 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 >= 180) { if (error) { *error = "RastLineFinder: angle tolerance must be below 180 degrees"; } return false; } if (m_maxDistFromLine <= 0) { if (error) { *error = "RastLineFinder: max-dist-from-line must be positive"; } return false; } if (m_minSupportPoints < 2) { if (error) { *error = "RastLineFinder: min-support-points must be at least 2"; } return false; } return true; } RastLineFinder::RastLineFinder(std::vector const& points, RastLineFinderParams const& 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_points.reserve(points.size()); std::vector candidate_idxs; candidate_idxs.reserve(points.size()); double max_sqdist = 0; BOOST_FOREACH(QPointF const& pt, points) { m_points.push_back(pt); candidate_idxs.push_back(candidate_idxs.size()); double const sqdist = Vec2d(pt - m_origin).squaredNorm(); if (sqdist > max_sqdist) { max_sqdist = sqdist; } } float const max_dist = sqrt(max_sqdist) + 1.0; // + 1.0 to combant rounding issues double delta_deg = fmod(params.maxAngleDeg() - params.minAngleDeg(), 360.0); if (delta_deg < 0) { delta_deg += 360; } double const min_angle_deg = fmod(params.minAngleDeg(), 360.0); double const max_angle_deg = min_angle_deg + delta_deg; SearchSpace ssp( *this, -max_dist, max_dist, min_angle_deg * constants::DEG2RAD, 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; } 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); } } 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); } } } } return QLineF(); } void RastLineFinder::pushIfGoodEnough(SearchSpace& ssp) { if (ssp.pointIdxs().size() >= m_minSupportPoints) { m_orderedSearchSpaces.pushDestructive(ssp); } } void RastLineFinder::markPointsUnavailable(std::vector const& point_idxs) { BOOST_FOREACH(unsigned idx, point_idxs) { m_points[idx].available = false; } } void RastLineFinder::pruneUnavailablePoints() { 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); } } 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( RastLineFinder const& owner, float min_dist, float max_dist, float min_angle_rad, float max_angle_rad, std::vector const& 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()); QPointF const origin(owner.m_origin); double const min_sqdist = double(m_minDist) * double(m_minDist); double const max_sqdist = double(m_maxDist) * double(m_maxDist); QPointF const min_angle_unit_vec(cos(m_minAngleRad), sin(m_minAngleRad)); QPointF const max_angle_unit_vec(cos(m_maxAngleRad), sin(m_maxAngleRad)); QPointF const min_angle_inner_pt(origin + min_angle_unit_vec * m_minDist); QPointF const max_angle_inner_pt(origin + max_angle_unit_vec * m_minDist); QPointF const min_angle_outer_pt(origin + min_angle_unit_vec * m_maxDist); QPointF const max_angle_outer_pt(origin + max_angle_unit_vec * m_maxDist); Vec2d const min_towards_max_angle_vec(-min_angle_unit_vec.y(), min_angle_unit_vec.x()); Vec2d const max_towards_min_angle_vec(max_angle_unit_vec.y(), -max_angle_unit_vec.x()); BOOST_FOREACH(unsigned idx, candidate_idxs) { Point const& pnt = owner.m_points[idx]; if (!pnt.available) { continue; } Vec2d const 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; } m_pointIdxs.push_back(idx); } // Compact m_pointIdxs, as we expect a lot of SearchSpace objects // to exist at the same time. std::vector(m_pointIdxs).swap(m_pointIdxs); } QLineF RastLineFinder::SearchSpace::representativeLine(RastLineFinder const& owner) const { float const dist = 0.5f * (m_minDist + m_maxDist); float const angle = 0.5f * (m_minAngleRad + m_maxAngleRad); QPointF const angle_unit_vec(cos(angle), sin(angle)); QPointF const angle_norm_vec(-angle_unit_vec.y(), angle_unit_vec.x()); QPointF const p1(owner.m_origin + angle_unit_vec * dist); QPointF const p2(p1 + angle_norm_vec); return QLineF(p1, p2); } bool RastLineFinder::SearchSpace::subdivideDist( RastLineFinder const& 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, m_minDist + owner.m_maxDistFromLine*2, m_minAngleRad, m_maxAngleRad, m_pointIdxs); SearchSpace ssp2(owner, m_maxDist - owner.m_maxDistFromLine*2, m_maxDist, m_minAngleRad, m_maxAngleRad, m_pointIdxs); ssp1.swap(subspace1); ssp2.swap(subspace2); } else { float const mid_dist = 0.5f * (m_maxDist + m_minDist); SearchSpace ssp1(owner, m_minDist, mid_dist + owner.m_maxDistFromLine, m_minAngleRad, m_maxAngleRad, m_pointIdxs); SearchSpace ssp2(owner, mid_dist - owner.m_maxDistFromLine, m_maxDist, m_minAngleRad, m_maxAngleRad, m_pointIdxs); ssp1.swap(subspace1); ssp2.swap(subspace2); } return true; } bool RastLineFinder::SearchSpace::subdivideAngle( RastLineFinder const& owner, SearchSpace& subspace1, SearchSpace& subspace2) const { assert(m_maxAngleRad >= m_minAngleRad); if (m_maxAngleRad - m_minAngleRad <= owner.m_angleToleranceRad * 2 || m_pointIdxs.size() < 2) { return false; } float const 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); ssp1.swap(subspace1); ssp2.swap(subspace2); return true; } void RastLineFinder::SearchSpace::pruneUnavailablePoints(PointUnavailablePred pred) { 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); } } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/RastLineFinder.h000066400000000000000000000200251271170121200224460ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_RAST_LINE_FINDER_H_ #define IMAGEPROC_RAST_LINE_FINDER_H_ #include "PriorityQueue.h" #include #include #include #include #include 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(QPointF const& origin) { m_origin = origin; } /** \see setOrigin() */ QPointF const& 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 = 0) const; private: QPointF m_origin; double m_minAngleDeg; double m_maxAngleDeg; double m_angleToleranceDeg; double m_maxDistFromLine; unsigned m_minSupportPoints; }; /** * \brief Finds lines in point clouds. * * This class implements the following algorithm:\n * Thomas M. Breuel. Finding Lines under Bounded Error.\n * Pattern Recognition, 29(1):167-178, 1996.\n * 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(std::vector const& points, RastLineFinderParams const& 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 = 0); private: class Point { public: QPointF pt; bool available; Point(QPointF const& p) : pt(p), available(true) {} }; class PointUnavailablePred { public: PointUnavailablePred(std::vector const* points) : m_pPoints(points) {} bool operator()(unsigned idx) const { return !(*m_pPoints)[idx].available; } private: std::vector const* m_pPoints; }; class SearchSpace { public: SearchSpace(); SearchSpace(RastLineFinder const& owner, float min_dist, float max_dist, float min_angle_rad, float max_angle_rad, std::vector const& candidate_idxs); /** * 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(RastLineFinder const& owner) const; bool subdivideDist(RastLineFinder const& owner, SearchSpace& subspace1, SearchSpace& subspace2) const; bool subdivideAngle(RastLineFinder const& owner, SearchSpace& subspace1, SearchSpace& subspace2) const; void pruneUnavailablePoints(PointUnavailablePred pred); std::vector& pointIdxs() { return m_pointIdxs; } std::vector const& 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(SearchSpace const& lhs, SearchSpace const& rhs) const { return lhs.pointIdxs().size() > rhs.pointIdxs().size(); } }; void pushIfGoodEnough(SearchSpace& ssp); void markPointsUnavailable(std::vector const& point_idxs); void pruneUnavailablePoints(); QPointF m_origin; double m_angleToleranceRad; double m_maxDistFromLine; unsigned m_minSupportPoints; std::vector m_points; OrderedSearchSpaces m_orderedSearchSpaces; bool m_firstLine; }; } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/RasterOp.h000066400000000000000000000317431271170121200213450ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_RASTEROP_H_ #define IMAGEPROC_RASTEROP_H_ #include "BinaryImage.h" #include #include #include #ifndef Q_MOC_RUN #include #endif #include #include namespace imageproc { /** * \brief Perform pixel-wise logical operations on portions of images. * * \param dst The destination image. Changes will be written there. * \param dr The rectangle within the destination image to process. * \param src The source image. May be the same as the destination image. * \param sp The top-left corner of the rectangle within the source image * to process. The rectangle itself is assumed to be the same * as the destination rectangle. * * The template argument is the operation to perform. This is generally * a combination of several Rop* class templates, such as RopXor\. */ template void rasterOp(BinaryImage& dst, QRect const& dr, BinaryImage const& src, QPoint const& sp); /** * \brief Perform pixel-wise logical operations on whole images. * * \param dst The destination image. Changes will be written there. * \param src The source image. May be the same as the destination image, * otherwise it must have the same dimensions. * * The template argument is the operation to perform. This is generally * a combination of several Rop* class templates, such as RopXor\. */ template void rasterOp(BinaryImage& dst, BinaryImage const& src); /** * \brief Raster operation that takes source pixels as they are. * \see rasterOp() */ class RopSrc { public: static uint32_t transform(uint32_t src, uint32_t /*dst*/) { return src; } }; /** * \brief Raster operation that takes destination pixels as they are. * \see rasterOp() */ class RopDst { public: static uint32_t transform(uint32_t /*src*/, uint32_t dst) { return dst; } }; /** * \brief Raster operation that performs a logical NOT operation. * \see rasterOp() */ template class RopNot { public: static uint32_t transform(uint32_t src, uint32_t dst) { return ~Arg::transform(src, dst); } }; /** * \brief Raster operation that performs a logical AND operation. * \see rasterOp() */ template class RopAnd { public: static uint32_t transform(uint32_t src, uint32_t dst) { return Arg1::transform(src, dst) & Arg2::transform(src, dst); } }; /** * \brief Raster operation that performs a logical OR operation. * \see rasterOp() */ template class RopOr { public: static uint32_t transform(uint32_t src, uint32_t dst) { return Arg1::transform(src, dst) | Arg2::transform(src, dst); } }; /** * \brief Raster operation that performs a logical XOR operation. * \see rasterOp() */ template class RopXor { public: static uint32_t transform(uint32_t src, uint32_t dst) { return Arg1::transform(src, dst) ^ Arg2::transform(src, dst); } }; /** * \brief Raster operation that subtracts black pixels of Arg2 from Arg1. * \see rasterOp() */ 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); return lhs & (lhs ^ rhs); } }; /** * \brief Raster operation that subtracts white pixels of Arg2 from Arg1. * \see rasterOp() */ 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); return lhs | ~(lhs ^ rhs); } }; /** * \brief Polymorphic interface for raster operations. * * If you want to parametrize some of your code with a raster operation, * one way to do it is to have Rop as a template argument. The other, * and usually better way is to have this class as a non-template argument. */ class AbstractRasterOp { public: virtual ~AbstractRasterOp() {} /** * \see rasterOp() */ virtual void operator()( BinaryImage& dst, QRect const& dr, BinaryImage const& src, QPoint const& sp) const = 0; }; /** * \brief A pre-defined raster operation to be called polymorphically. */ template class TemplateRasterOp : public AbstractRasterOp { public: /** * \see rasterOp() */ virtual void operator()( BinaryImage& dst, QRect const& dr, BinaryImage const& src, QPoint const& sp) const { rasterOp(dst, dr, src, sp); } }; namespace detail { template void rasterOpInDirection( BinaryImage& dst, QRect const& dr, BinaryImage const& src, QPoint const& sp, int const dy, int const dx) { int const src_start_bit = sp.x() % 32; int const dst_start_bit = dr.x() % 32; int const rightmost_dst_bit = dr.right(); // == dr.x() + dr.width() - 1; int const rightmost_dst_word = rightmost_dst_bit / 32 - dr.x() / 32; uint32_t const leftmost_dst_mask = ~uint32_t(0) >> dst_start_bit; uint32_t const 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; uint32_t const* 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); uint32_t const 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) { uint32_t const src_word = src_span[0]; uint32_t const dst_word = dst_span[0]; uint32_t const 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); } } return; } if (first_dst_word == last_dst_word) { assert(first_dst_word == 0); uint32_t const mask = first_dst_mask & last_dst_mask; uint32_t const can_word1 = (~uint32_t(0) << src_word1_shift) & mask; uint32_t const 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) { uint32_t const src_word1 = src_span[0]; src_word |= src_word1 << src_word1_shift; } if (can_word2) { uint32_t const src_word2 = src_span[1]; src_word |= src_word2 >> src_word2_shift; } uint32_t const dst_word = dst_span[0]; uint32_t const new_dst_word = Rop::transform(src_word, dst_word); dst_span[0] = (dst_word & ~mask) | (new_dst_word & mask); } } else { uint32_t const can_first_word1 = (~uint32_t(0) << src_word1_shift) & first_dst_mask; uint32_t const can_first_word2 = (~uint32_t(0) >> src_word2_shift) & first_dst_mask; uint32_t const can_last_word1 = (~uint32_t(0) << src_word1_shift) & last_dst_mask; uint32_t const 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) { uint32_t const src_word1 = src_span[widx]; src_word |= src_word1 << src_word1_shift; } if (can_first_word2) { uint32_t const 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) { uint32_t const src_word1 = src_span[widx]; uint32_t const 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) { uint32_t const src_word1 = src_span[widx]; src_word |= src_word1 << src_word1_shift; } if (can_last_word2) { uint32_t const 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; } } } } // namespace detail template void rasterOp(BinaryImage& dst, QRect const& dr, BinaryImage const& src, QPoint const& sp) { using namespace detail; if (dr.isEmpty()) { return; } 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 (!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. 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); return; } if (dr.y() == sp.y() && dr.x() > sp.x()) { rasterOpInDirection(dst, dr, src, sp, 1, -1); return; } } rasterOpInDirection(dst, dr, src, sp, 1, 1); } template void rasterOp(BinaryImage& dst, BinaryImage const& src) { using namespace detail; 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"); } rasterOpInDirection(dst, dst.rect(), src, QPoint(0, 0), 1, 1); } } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/RasterOpGeneric.h000066400000000000000000000127661271170121200226460ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_RASTER_OP_GENERIC_H_ #define IMAGEPROC_RASTER_OP_GENERIC_H_ #include "BinaryImage.h" #include #include #include namespace imageproc { /** * \brief Perform an operation on a single image. * * \param data The pointer to image data. * \param stride The number of elements of type T per image line. * \param size Image size. * \param operation An operation to perform. It will be called like this: * \code * operation(data[offset]); * \endcode * 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 void rasterOpGeneric(T* data, int stride, QSize size, Op operation); /** * \brief Perform an operation on a pair of images. * * \param data1 The pointer to image data of the first image. * \param stride1 The number of elements of type T1 per line of the first image. * \param size Dimensions of both images. * \param data2 The pointer to image data of the second image. * \param stride2 The number of elements of type T2 per line of the second image. * \param operation An operation to perform. It will be called like this: * \code * operation(data1[offset1], data2[offset2]); * \endcode * Depending on whether T1 / T2 are const, the operation may be able to modify * one or both of them. * Hinst: boost::lambda is an easy way to construct operations. */ template void rasterOpGeneric(T1* data1, int stride1, QSize size, T2* data2, int stride2, Op operation); /** * \brief Same as the one above, except one of the images is a const BinaryImage. * * \p operation will be called like this: * \code * uint32_t const bit1 = ? 1 : 0; * operation(bitl, data2[offset2]); * \endcode */ template void rasterOpGeneric(BinaryImage const& image1, T2* data2, int stride2, Op operation); /** * \brief Same as the one above, except one of the images is a non-const BinaryImage. * * \p operation will be called like this: * \code * BitProxy bit1(...); * operation(bitl, data2[offset2]); * \endcode * 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 void rasterOpGeneric(BinaryImage const& image1, T2* data2, int stride2, Op operation); /*======================== Implementation ==========================*/ template void rasterOpGeneric(T* data, int stride, QSize size, Op operation) { if (size.isEmpty()) { return; } int const w = size.width(); int const h = size.height(); for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { operation(data[x]); } data += stride; } } template void rasterOpGeneric(T1* data1, int stride1, QSize size, T2* data2, int stride2, Op operation) { if (size.isEmpty()) { return; } int const w = size.width(); int const 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; } } template void rasterOpGeneric(BinaryImage const& image1, T2* data2, int stride2, Op operation) { if (image1.isNull()) { return; } int const w = image1.width(); int const h = image1.height(); int const stride1 = image1.wordsPerLine(); uint32_t const* data1 = image1.data(); for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { int const 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) {} BitProxy(BitProxy const& other) : m_rWord(other.m_rWord), m_shift(other.m_shift) {} BitProxy& operator=(uint32_t bit) { assert(bit <= 1); uint32_t const mask = uint32_t(1) << m_shift; m_rWord = (m_rWord & ~mask) | (bit << m_shift); return *this; } operator uint32_t() const { return (m_rWord >> m_shift) & uint32_t(1); } private: uint32_t& m_rWord; int m_shift; }; } // namespace rop_generic_impl template void rasterOpGeneric(BinaryImage& image1, T2* data2, int stride2, Op operation) { using namespace rop_generic_impl; if (image1.isNull()) { return; } int const w = image1.width(); int const h = image1.height(); int const 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; } } } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/ReduceThreshold.cpp000066400000000000000000000204261271170121200232210ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ReduceThreshold.h" #include #include #include namespace imageproc { namespace { /** * This lookup table is filled like this: * \code * for (unsigned i = 0; i < 256; i += 2) { * unsigned out = * ((i & (1 << 1)) >> 1) | // bit 1 becomes bit 0 * ((i & (1 << 3)) >> 2) | // bit 3 becomes bit 1 * ((i & (1 << 5)) >> 3) | // bit 5 becomes bit 2 * ((i & (1 << 7)) >> 4); // bit 7 becomes bit 3 * compressBitsLut[i >> 1] = static_cast(out); * } * \endcode * We take every other byte because bit 0 doesn't matter here. */ static uint8_t const 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 }; /** * 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(uint32_t const 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; return r; } /** * Throw away every other bit starting with bit 0 and * pack the remaining bits into the lower half of a word. */ inline uint32_t compressBitsLowerHalf(uint32_t const 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]; return r; } inline uint32_t threshold1(uint32_t const top, uint32_t const bottom) { uint32_t word = top | bottom; word |= word << 1; return word; } inline uint32_t threshold2(uint32_t const top, uint32_t const bottom) { uint32_t word1 = top & bottom; word1 |= word1 << 1; uint32_t word2 = top | bottom; word2 &= word2 << 1; return word1 | word2; } inline uint32_t threshold3(uint32_t const top, uint32_t const bottom) { uint32_t word1 = top | bottom; word1 &= word1 << 1; uint32_t word2 = top & bottom; word2 |= word2 << 1; return word1 & word2; } inline uint32_t threshold4(uint32_t const top, uint32_t const bottom) { uint32_t word = top & bottom; word &= word << 1; return word; } } // anonymous namespace ReduceThreshold::ReduceThreshold(BinaryImage const& image) : m_image(image) { } ReduceThreshold& ReduceThreshold::reduce(int const threshold) { if (threshold < 1 || threshold > 4) { throw std::invalid_argument("ReduceThreshold: invalid threshold"); } BinaryImage const& src = m_image; if (src.isNull()) { return *this; } int const dst_w = src.width() / 2; int const dst_h = src.height() / 2; if (dst_h == 0) { reduceHorLine(threshold); return *this; } else if (dst_w == 0) { reduceVertLine(threshold); return *this; } BinaryImage dst(dst_w, dst_h); int const dst_wpl = dst.wordsPerLine(); int const src_wpl = src.wordsPerLine(); int const steps_per_line = (dst_w * 2 + 31) / 32; assert(steps_per_line <= src_wpl); assert(steps_per_line / 2 <= dst_wpl); uint32_t const* 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; } } m_image = dst; return *this; } void ReduceThreshold::reduceHorLine(int const threshold) { BinaryImage const& 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); int const steps_per_line = (dst.width() * 2 + 31) / 32; uint32_t const* 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; } } m_image = dst; } void ReduceThreshold::reduceVertLine(int const threshold) { BinaryImage const& src = m_image; assert(src.width() == 1); if (src.height() == 1) { // 1x1 image remains the same no matter the threshold. return; } int const dst_h = src.height() / 2; BinaryImage dst(1, dst_h); int const src_wpl = src.wordsPerLine(); int const dst_wpl = dst.wordsPerLine(); uint32_t const* 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; } } m_image = dst; } } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/ReduceThreshold.h000066400000000000000000000051711271170121200226660ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_REDUCETHRESHOLD_H_ #define IMAGEPROC_REDUCETHRESHOLD_H_ #include "BinaryImage.h" namespace imageproc { /** * \brief Performs 2x horizontal and vertical downscaling on 1-bit images. * * The dimensions of the target image will be: * \code * dst_width = max(1, floor(src_width / 2)); * dst_height = max(1, floor(src_height / 2)); * \endcode * \n * Processing a null image results in a null image. * Processing a non-null image never results in a null image.\n * \n * A 2x2 square in the source image becomes 1 pixel in the target image. * The threshold parameter controls how many black pixels need to be in * the source 2x2 square in order to make the destination pixel black. * Valid threshold values are 1, 2, 3 and 4.\n * \n * If the source image is a line 1 pixel thick, downscaling will be done * as if the line was thickened to 2 pixels by duplicating existing pixels.\n * \n * It is possible to do a cascade of reductions: * \code * BinaryImage out = ReduceThreshold(input)(4)(4)(3); * \endcode */ class ReduceThreshold { public: /** * \brief Constructor. Doesn't do any work by itself. */ ReduceThreshold(BinaryImage const& image); /** * \brief Implicit conversion to BinaryImage. */ operator BinaryImage const&() const { return m_image; } /** * \brief Returns a reference to the reduced image. */ BinaryImage const& image() const { return m_image; } /** * \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); } private: void reduceHorLine(int threshold); void reduceVertLine(int threshold); /** * \brief The result of a previous reduction. */ BinaryImage m_image; }; } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/SEDM.cpp000066400000000000000000000313221271170121200206620ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich Based on code from the ANIMAL image processing library. Copyright (C) 2002,2003 Ricardo Fabbri 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "SEDM.h" #include "BinaryImage.h" #include "ConnectivityMap.h" #include "Morphology.h" #include "SeedFill.h" #include "RasterOp.h" #include #include #include #include namespace imageproc { // Note that -1 is an implementation detail. // It exists to make sure INF_DIST + 1 doesn't overflow. uint32_t const SEDM::INF_DIST = ~uint32_t(0) - 1; SEDM::SEDM() : m_pData(0), m_size(), m_stride(0) { } SEDM::SEDM( BinaryImage const& image, DistType const dist_type, Borders const borders) : m_pData(0), m_size(image.size()), m_stride(0) { if (image.isNull()) { return; } int const width = m_size.width(); int const 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)) { int const 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 } uint32_t* p_dist = m_pData; uint32_t const* img_line = image.data(); int const 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(); } SEDM::SEDM(ConnectivityMap& cmap) : m_pData(0), m_size(cmap.size()), m_stride(0) { if (m_size.isEmpty()) { return; } int const width = m_size.width(); int const 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; uint32_t const* 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); } SEDM::SEDM(SEDM const& other) : m_data(other.m_data), m_pData(0), m_size(other.m_size), m_stride(other.m_stride) { if (!m_size.isEmpty()) { m_pData = &m_data[0] + m_stride + 1; } } SEDM& SEDM::operator=(SEDM const& other) { SEDM(other).swap(*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); } 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. BinaryImage const not_peaks(seedFill(diff, peak_candidates, CONN8)); diff.release(); rasterOp >(peak_candidates, not_peaks); return peak_candidates; } inline uint32_t SEDM::distSq( int const x1, int const x2, uint32_t const dy_sq) { if (dy_sq == INF_DIST) { return INF_DIST; } int const dx = x1 - x2; uint32_t const dx_sq = dx * dx; return dx_sq + dy_sq; } void SEDM::processColumns() { int const width = m_size.width() + 2; int const 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) { uint32_t const 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) { uint32_t const sqd = *p_sqd + b; p_sqd -= width; if (*p_sqd > sqd) { *p_sqd = sqd; b += 2; } else { b = 1; } } } } void SEDM::processColumns(ConnectivityMap& cmap) { int const width = m_size.width() + 2; int const 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) { uint32_t const 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) { uint32_t const 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; } } } } void SEDM::processRows() { int const width = m_size.width() + 2; int const 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 { int const 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)); for (int x = width - 1; x >= 0; --x) { int const x2 = s[q]; line[x] = distSq(x, x2, row_copy[x2]); if (x == t[q]) { --q; } } } } void SEDM::processRows(ConnectivityMap& cmap) { int const width = m_size.width() + 2; int const 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 { int const 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)); for (int x = width - 1; x >= 0; --x) { int const x2 = s[q]; line[x] = distSq(x, x2, row_copy[x2]); cmap_line[x] = cmap_row_copy[x2]; if (x == t[q]) { --q; } } } } /*====================== Peak finding stuff goes below ====================*/ BinaryImage SEDM::findPeakCandidatesNonPadded() const { std::vector maxed(m_data.size(), 0); // Every cell becomes the maximum of itself and its neighbors. max3x3(&m_data[0], &maxed[0]); return buildEqualMapNonPadded(&m_data[0], &maxed[0]); } BinaryImage SEDM::buildEqualMapNonPadded(uint32_t const* src1, uint32_t const* src2) const { int const width = m_size.width(); int const height = m_size.height(); BinaryImage dst(width, height, WHITE); uint32_t* dst_line = dst.data(); int const dst_wpl = dst.wordsPerLine(); int const src_stride = m_stride; uint32_t const* src1_line = src1 + src_stride + 1; uint32_t const* src2_line = src2 + src_stride + 1; uint32_t const 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; } void SEDM::max3x3(uint32_t const* src, uint32_t* dst) const { std::vector tmp(m_data.size(), 0); max3x1(src, &tmp[0]); max1x3(&tmp[0], dst); } void SEDM::max3x1(uint32_t const* src, uint32_t* dst) const { int const width = m_size.width() + 2; int const height = m_size.height() + 2; uint32_t const* 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) { uint32_t const prev = src_line[x - 1]; uint32_t const cur = src_line[x]; uint32_t const 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; } } void SEDM::max1x3(uint32_t const* src, uint32_t* dst) const { int const width = m_size.width() + 2; int const height = m_size.height() + 2; // First row (no top neighbors). uint32_t const* 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) { uint32_t const prev = p_src[x - width]; uint32_t const cur = p_src[x]; uint32_t const 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; } } void SEDM::incrementMaskedPadded(BinaryImage const& mask) { int const width = m_size.width() + 2; int const height = m_size.height() + 2; uint32_t* data_line = &m_data[0]; uint32_t const* mask_line = mask.data(); int const mask_wpl = mask.wordsPerLine(); uint32_t const 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 scantailor-RELEASE_0_9_12_2/imageproc/SEDM.h000066400000000000000000000136051271170121200203330ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_SEDM_H_ #define IMAGEPROC_SEDM_H_ #include "foundation/FlagOps.h" #include #include #include namespace imageproc { class BinaryImage; class ConnectivityMap; /** * \brief The squared euclidean distance map. * * For each pixel of the input image stores the squared euclidean * (straight line) distance to either the nearest white or black pixel. * * The implementation is based on the following paper:\n * Meijster, A., Roerdink, J., and Hesselink, W. 2000. * A general algorithm for computing distance transforms in linear time. * In Proceedings of the 5th International Conference on Mathematical * 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 uint32_t const 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( BinaryImage const& 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(SEDM const& other); SEDM& operator=(SEDM const& 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; } /** * \brief Return a matrix of squared distances in row-major order. */ uint32_t const* data() const { return m_pData; } /** * \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(uint32_t const* src1, uint32_t const* src2) const; void max3x3(uint32_t const* src, uint32_t* dst) const; void max3x1(uint32_t const* src, uint32_t* dst) const; void max1x3(uint32_t const* src, uint32_t* dst) const; void incrementMaskedPadded(BinaryImage const& mask); std::vector m_data; uint32_t* m_pData; QSize m_size; int m_stride; }; inline void swap(SEDM& o1, SEDM& o2) { o1.swap(o2); } DEFINE_FLAG_OPS(SEDM::Borders) } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/SavGolFilter.cpp000066400000000000000000000221371271170121200224770ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "SavGolFilter.h" #include "SavGolKernel.h" #include "Grayscale.h" #include "AlignedArray.h" #include #include #include #include #include #include #include #include namespace imageproc { namespace { int calcNumTerms(int const hor_degree, int const vert_degree) { return (hor_degree + 1) * (vert_degree + 1); } class Kernel : public SavGolKernel { public: Kernel(QSize const& size, QPoint const& origin, int hor_degree, int vert_degree) : SavGolKernel(size, origin, hor_degree, vert_degree) {} void convolve( uint8_t* dst, uint8_t const* src_top_left, int src_bpl) const; }; inline void Kernel::convolve(uint8_t* dst, uint8_t const* src_top_left, int src_bpl) const { uint8_t const* p_src = src_top_left; float const* p_kernel = data(); float sum = 0.5; // For rounding purposes. int const w = width(); int const 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; } } int const val = static_cast(sum); *dst = static_cast(qBound(0, val, 255)); } QImage savGolFilterGrayToGray( QImage const& src, QSize const& window_size, int const hor_degree, int const vert_degree) { int const width = src.width(); int const height = src.height(); // Kernel width and height. int const kw = window_size.width(); int const 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. QPoint const 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. int const k_top = k_center.y(); // Length of the bottom segment (B) of the kernel. int const k_bottom = kh - k_top - 1; // Length of the left segment (L) of the kernel. int const k_left = k_center.x(); // Length of the right segment (R) of the kernel. int const k_right = kw - k_left - 1; uint8_t const* const src_data = src.bits(); int const 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(); int const dst_bpl = dst.bytesPerLine(); // Top-left corner. uint8_t const* 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-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; } // Central area. #if 0 // Simple but slow implementation. kernel.recalcForOrigin(k_center); src_line = src_data - k_left; dst_line = dst_data + dst_bpl * k_top; for (int y = k_top; y < height - k_bottom; ++y) { for (int x = k_left; x < width - k_right; ++x) { kernel.convolve(dst_line + x, src_line + x, src_bpl); } src_line += src_bpl; dst_line += dst_bpl; } #else // Take advantage of Savitzky-Golay filter being separable. SavGolKernel const hor_kernel( QSize(window_size.width(), 1), QPoint(k_center.x(), 0), hor_degree, 0 ); SavGolKernel const vert_kernel( QSize(1, window_size.height()), QPoint(0, k_center.y()), 0, vert_degree ); int const shift = kw - 1; // Allocate a 16-byte aligned temporary storage. // That may help the compiler to emit efficient SSE code. int const 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; uint8_t const* 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; } // 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]; } int const val = static_cast(sum); dst_line[i] = static_cast(qBound(0, val, 255)); } temp_line += temp_stride; dst_line += dst_bpl; } #endif // 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; } // 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; } // 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; } } // anonymous namespace QImage savGolFilter( QImage const& src, QSize const& window_size, int const hor_degree, int const 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 scantailor-RELEASE_0_9_12_2/imageproc/SavGolFilter.h000066400000000000000000000041671271170121200221470ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_SAVGOLFILTER_H_ #define IMAGEPROC_SAVGOLFILTER_H_ class QImage; class QSize; namespace imageproc { /** * \brief Performs a grayscale smoothing using the Savitzky-Golay method. * * The Savitzky-Golay method is equivalent to fitting a small neighborhood * around each pixel to a polynomial, and then recalculating the pixel * value from it. In practice, it achieves the same results without fitting * a polynomial for every pixel, so it performs quite well. * * \param src The source image. It doesn't have to be grayscale, but * the resulting image will be grayscale anyway. * \param window_size The apperture size. If it doesn't completely * fit the image area, no filtering will take place. * \param hor_degree The degree of a polynomial in horizontal direction. * \param vert_degree The degree of a polynomial in vertical direction. * \return The filtered grayscale image. * * \note The window size and degrees are not completely independent. * The following inequality must be fulfilled: * \code * window_width * window_height >= (hor_degree + 1) * (vert_degree + 1) * \endcode * Good results for 300 dpi scans are achieved with 7x7 window and 4x4 degree. */ QImage savGolFilter( QImage const& src, QSize const& window_size, int hor_degree, int vert_degree); } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/SavGolKernel.cpp000066400000000000000000000125641271170121200224750ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #define _ISOC99SOURCE // For copysign() #include "SavGolKernel.h" #include #include #include #include #include #include #ifdef _MSC_VER #undef copysign // Just in case. #define copysign _copysign #endif namespace imageproc { namespace { int calcNumTerms(int const hor_degree, int const vert_degree) { return (hor_degree + 1) * (vert_degree + 1); } } // anonymous namespace SavGolKernel::SavGolKernel( QSize const& size, QPoint const& origin, int const hor_degree, int const 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; } } } QR(); recalcForOrigin(origin); } /** * Perform a QR factorization of m_equations by Givens rotations. * We store R in place of m_equations, and we don't store Q anywhere, * 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) { double const a = m_equations[jj]; double const b = m_equations[ij]; if (b == 0.0) { m_rotations.push_back(Rotation(1.0, 0.0)); continue; } double sin, cos; if (a == 0.0) { cos = 0.0; sin = copysign(1.0, b); m_equations[jj] = fabs(b); } else if (fabs(b) > fabs(a)) { double const t = a / b; double const u = copysign(sqrt(1.0 + t*t), b); sin = 1.0 / u; cos = sin * t; m_equations[jj] = b * u; } else { double const t = b / a; double const u = copysign(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.push_back(Rotation(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) { double const temp = cos * m_equations[jk] + sin * m_equations[ik]; m_equations[ik] = cos * m_equations[ik] - sin * m_equations[jk]; m_equations[jk] = temp; } } } } void SavGolKernel::recalcForOrigin(QPoint const& 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) { double const 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]; } 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; } } } } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/SavGolKernel.h000066400000000000000000000056321271170121200221400ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_SAVGOL_KERNEL_H_ #define IMAGEPROC_SAVGOL_KERNEL_H_ #include "AlignedArray.h" #include #include class QPoint; class QSize; namespace imageproc { class SavGolKernel { public: SavGolKernel( QSize const& size, QPoint const& origin, int hor_degree, int vert_degree); void recalcForOrigin(QPoint const& origin); int width() const { return m_width; } int height() const { return m_height; } float const* 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 scantailor-RELEASE_0_9_12_2/imageproc/Scale.cpp000066400000000000000000000262461271170121200211720ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Scale.h" #include "GrayImage.h" #include #include #include #include #include namespace imageproc { /** * This is an optimized implementation for the case when every destination * pixel maps exactly to a M x N block of source pixels. */ static GrayImage scaleDownIntGrayToGray(GrayImage const& src, QSize const& dst_size) { int const sw = src.width(); int const sh = src.height(); int const dw = dst_size.width(); int const dh = dst_size.height(); int const xscale = sw / dw; int const yscale = sh / dh; int const total_area = xscale * yscale; GrayImage dst(dst_size); uint8_t const* src_line = src.data(); uint8_t* dst_line = dst.data(); int const src_stride = src.stride(); int const src_stride_scaled = src_stride * yscale; int const 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; uint8_t const* 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]; } } unsigned const pix_value = (gray_level + (total_area >> 1)) / total_area; assert(pix_value < 256); dst_line[dx] = static_cast(pix_value); } src_line += src_stride_scaled; dst_line += dst_stride; } return dst; } /** * This is an optimized implementation for the case when every destination * pixel maps to a single source pixel (possibly to a part of it). */ static GrayImage scaleUpIntGrayToGray(GrayImage const& src, QSize const& dst_size) { int const sw = src.width(); int const sh = src.height(); int const dw = dst_size.width(); int const dh = dst_size.height(); int const xscale = dw / sw; int const yscale = dh / sh; GrayImage dst(dst_size); uint8_t const* src_line = src.data(); uint8_t* dst_line = dst.data(); int const src_stride = src.stride(); int const dst_stride = dst.stride(); int const 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; } /** * This function is used to calculate the ratio for going * from \p dst to \p src multiplied by 32, so that * \code * int(ratio * (dst_limit - 1)) / 32 < src_limit - 1 * \endcode */ static double calc32xRatio1(int const dst, int const 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; } return ratio; } /** * This is an optimized implementation for the case when * the destination image is larger than the source image both * horizontally and vertically. */ static GrayImage scaleUpGrayToGray(GrayImage const& src, QSize const& dst_size) { int const sw = src.width(); int const sh = src.height(); int const dw = dst_size.width(); int const dh = dst_size.height(); double const dx2sx32 = calc32xRatio1(dw, sw); double const dy2sy32 = calc32xRatio1(dh, sh); GrayImage dst(dst_size); uint8_t const* const src_data = src.data(); uint8_t* dst_line = dst.data(); int const src_stride = src.stride(); int const dst_stride = dst.stride(); for (int dy = 0; dy < dh; ++dy, dst_line += dst_stride) { int const sy32 = (int)(dy * dy2sy32); int const sy = sy32 >> 5; unsigned const top_fraction = 32 - (sy32 & 31); unsigned const bottom_fraction = sy32 & 31; assert(sy + 1 < sh); // calc32xRatio1() ensures that. uint8_t const* src_line = src_data + sy * src_stride; for (int dx = 0; dx < dw; ++dx) { int const sx32 = (int)(dx * dx2sx32); int const sx = sx32 >> 5; unsigned const left_fraction = 32 - (sx32 & 31); unsigned const right_fraction = sx32 & 31; assert(sx + 1 < sw); // calc32xRatio1() ensures that. unsigned gray_level = 0; uint8_t const* 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; unsigned const total_area = 32 * 32; unsigned const pix_value = (gray_level + (total_area >> 1)) / total_area; assert(pix_value < 256); dst_line[dx] = static_cast(pix_value); } } return dst; } /** * This function is used to calculate the ratio for going * from \p dst to \p src multiplied by 32, so that * \code * (int(ratio * dst_limit) - 1) / 32 < src_limit * \endcode */ static double calc32xRatio2(int const dst, int const 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; } return ratio; } /** * This is a generic implementation of the scaling algorithm. */ static GrayImage scaleGrayToGray(GrayImage const& src, QSize const& dst_size) { int const sw = src.width(); int const sh = src.height(); int const dw = dst_size.width(); int const 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); } double const dx2sx32 = calc32xRatio2(dw, sw); double const dy2sy32 = calc32xRatio2(dh, sh); GrayImage dst(dst_size); uint8_t const* const src_data = src.data(); uint8_t* dst_line = dst.data(); int const src_stride = src.stride(); int const dst_stride = dst.stride(); int sy32bottom = 0; for (int dy1 = 1; dy1 <= dh; ++dy1, dst_line += dst_stride) { int const sy32top = sy32bottom; sy32bottom = (int)(dy1 * dy2sy32); int const sytop = sy32top >> 5; int const sybottom = (sy32bottom - 1) >> 5; unsigned const top_fraction = 32 - (sy32top & 31); unsigned const bottom_fraction = sy32bottom - (sybottom << 5); assert(sybottom < sh); // calc32xRatio2() ensures that. unsigned const top_area = top_fraction << 5; unsigned const bottom_area = bottom_fraction << 5; uint8_t const* const src_line_const = src_data + sytop * src_stride; int sx32right = 0; for (int dx = 0; dx < dw; ++dx) { int const sx32left = sx32right; sx32right = (int)((dx + 1) * dx2sx32); int const sxleft = sx32left >> 5; int const sxright = (sx32right - 1) >> 5; unsigned const left_fraction = 32 - (sx32left & 31); unsigned const right_fraction = sx32right - (sxright << 5); assert(sxright < sw); // calc32xRatio2() ensures that. uint8_t const* 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 unsigned const vert_fraction = sy32bottom - sy32top; unsigned const left_area = vert_fraction * left_fraction; unsigned const middle_area = vert_fraction << 5; unsigned const 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 unsigned const hor_fraction = sx32right - sx32left; unsigned const top_area = hor_fraction * top_fraction; unsigned const middle_area = hor_fraction << 5; unsigned const 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 unsigned const left_area = left_fraction << 5; unsigned const right_area = right_fraction << 5; unsigned const topleft_area = top_fraction * left_fraction; unsigned const topright_area = top_fraction * right_fraction; unsigned const bottomleft_area = bottom_fraction * left_fraction; unsigned const 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; } unsigned const total_area = (sy32bottom - sy32top) * (sx32right - sx32left); unsigned const pix_value = (gray_level + (total_area >> 1)) / total_area; assert(pix_value < 256); dst_line[dx] = static_cast(pix_value); } } return dst; } GrayImage scaleToGray(GrayImage const& src, QSize const& dst_size) { if (src.isNull()) { return src; } if (!dst_size.isValid()) { throw std::invalid_argument("scaleToGray: dst_size is invalid"); } if (dst_size.isEmpty()) { return GrayImage(); } return scaleGrayToGray(src, dst_size); } } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/Scale.h000066400000000000000000000024301271170121200206240ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_SCALE_H_ #define IMAGEPROC_SCALE_H_ class QSize; namespace imageproc { class GrayImage; /** * \brief Converts an image to grayscale and scales it to dst_size. * * \param src The source image. * \param dst_size The size to scale the image to. * \return The scaled image. * * This function is a faster replacement for QImage::scaled(), when * dealing with grayscale images. */ GrayImage scaleToGray(GrayImage const& src, QSize const& dst_size); } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/SeedFill.cpp000066400000000000000000000323431271170121200216250ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "SeedFill.h" #include "SeedFillGeneric.h" #include "GrayImage.h" #include #include #include #include #include #include #include #include #include namespace imageproc { namespace { inline uint32_t fillWordHorizontally(uint32_t word, uint32_t const mask) { uint32_t prev_word; do { prev_word = word; word |= (word << 1) | (word >> 1); word &= mask; } while (word != prev_word); return word; } void seedFill4Iteration(BinaryImage& seed, BinaryImage const& mask) { int const w = seed.width(); int const h = seed.height(); int const seed_wpl = seed.wordsPerLine(); int const mask_wpl = mask.wordsPerLine(); int const last_word_idx = (w - 1) >> 5; uint32_t const last_word_mask = ~uint32_t(0) << (((last_word_idx + 1) << 5) - w); uint32_t* seed_line = seed.data(); uint32_t const* mask_line = mask.data(); uint32_t const* 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) { uint32_t const 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; } 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) { uint32_t const 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; } } void seedFill8Iteration(BinaryImage& seed, BinaryImage const& mask) { int const w = seed.width(); int const h = seed.height(); int const seed_wpl = seed.wordsPerLine(); int const mask_wpl = mask.wordsPerLine(); int const last_word_idx = (w - 1) >> 5; uint32_t const last_word_mask = ~uint32_t(0) << (((last_word_idx + 1) << 5) - w); uint32_t* seed_line = seed.data(); uint32_t const* mask_line = mask.data(); uint32_t const* 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) { uint32_t const 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. uint32_t const 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; } 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) { uint32_t const 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. uint32_t const 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; } } inline uint8_t lightest(uint8_t lhs, uint8_t rhs) { return lhs > rhs ? lhs : rhs; } inline uint8_t darkest(uint8_t lhs, uint8_t rhs) { return lhs < rhs ? lhs : rhs; } inline bool darker_than(uint8_t lhs, uint8_t rhs) { return lhs < rhs; } void seedFillGrayHorLine(uint8_t* seed, uint8_t const* mask, int const line_len) { assert(line_len > 0); *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])); } } void seedFillGrayVertLine( uint8_t* seed, int const seed_stride, uint8_t const* mask, int const mask_stride, int const line_len) { assert(line_len > 0); *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])); } } /** * \return non-zero if more iterations are required, zero otherwise. */ uint8_t seedFillGray4SlowIteration(GrayImage& seed, GrayImage const& mask) { int const w = seed.width(); int const h = seed.height(); uint8_t* seed_line = seed.data(); uint8_t const* mask_line = mask.data(); uint8_t const* prev_line = seed_line; int const seed_stride = seed.stride(); int const 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) { uint8_t const 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; 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) { uint8_t const 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; } return modified; } /** * \return non-zero if more iterations are required, zero otherwise. */ uint8_t seedFillGray8SlowIteration(GrayImage& seed, GrayImage const& mask) { int const w = seed.width(); int const h = seed.height(); uint8_t* seed_line = seed.data(); uint8_t const* mask_line = mask.data(); uint8_t const* prev_line = seed_line; int const seed_stride = seed.stride(); int const 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; 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; } return modified; } } // anonymous namespace BinaryImage seedFill( BinaryImage const& seed, BinaryImage const& mask, Connectivity const connectivity) { if (seed.size() != mask.size()) { throw std::invalid_argument("seedFill: seed and mask have different sizes"); } BinaryImage prev; BinaryImage img(seed); do { prev = img; if (connectivity == CONN4) { seedFill4Iteration(img, mask); } else { seedFill8Iteration(img, mask); } } while (img != prev); return img; } GrayImage seedFillGray( GrayImage const& seed, GrayImage const& mask, Connectivity const connectivity) { GrayImage result(seed); seedFillGrayInPlace(result, mask, connectivity); return result; } void seedFillGrayInPlace( GrayImage& seed, GrayImage const& mask, Connectivity const connectivity) { if (seed.size() != mask.size()) { throw std::invalid_argument("seedFillGrayInPlace: seed and mask have different sizes"); } if (seed.isNull()) { return; } seedFillGenericInPlace( &darkest, &lightest, connectivity, seed.data(), seed.stride(), seed.size(), mask.data(), mask.stride() ); } GrayImage seedFillGraySlow( GrayImage const& seed, GrayImage const& mask, Connectivity const connectivity) { GrayImage img(seed); if (connectivity == CONN4) { while (seedFillGray4SlowIteration(img, mask)) { // Continue until done. } } else { while (seedFillGray8SlowIteration(img, mask)) { // Continue until done. } } return img; } } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/SeedFill.h000066400000000000000000000053251271170121200212720ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_SEEDFILL_H_ #define IMAGEPROC_SEEDFILL_H_ #include "BinaryImage.h" #include "Connectivity.h" class QImage; namespace imageproc { class GrayImage; /** * \brief Spread black pixels from seed as long as mask allows it. * * This operation retains black connected componets from \p mask that are * tagged by at least one black pixel in \p seed. The rest do not appear * in the result. * \par * \p seed is allowed to contain black pixels that are not in \p mask. * They will be ignored and will not appear in the resulting image. * \par * The underlying code implements Luc Vincent's iterative seed-fill * algorithm: http://www.vincent-net.com/luc/papers/93ieeeip_recons.pdf */ BinaryImage seedFill( BinaryImage const& seed, BinaryImage const& mask, Connectivity connectivity); /** * \brief Spread darker colors from seed as long as mask allows it. * * The result of this operation is an image where some areas are lighter * than in \p mask, because there were no dark paths linking them to dark * areas in \p seed. * \par * \p seed is allowed to contain pixels darker than the corresponding pixels * in \p mask. Such pixels will be made equal to their mask values. * \par * The underlying code implements Luc Vincent's hybrid seed-fill algorithm: * http://www.vincent-net.com/luc/papers/93ieeeip_recons.pdf */ GrayImage seedFillGray( GrayImage const& seed, GrayImage const& mask, Connectivity connectivity); /** * \brief A faster, in-place version of seedFillGray(). */ void seedFillGrayInPlace( GrayImage& seed, GrayImage const& mask, Connectivity connectivity); /** * \brief A slower but more simple implementation of seedFillGray(). * * This function should not be used for anything but testing the correctness * of the fast and complex implementation that is seedFillGray(). */ GrayImage seedFillGraySlow( GrayImage const& seed, GrayImage const& mask, Connectivity connectivity); } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/SeedFillGeneric.cpp000066400000000000000000000037331271170121200231230ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "SeedFillGeneric.h" namespace imageproc { namespace detail { namespace seed_fill_generic { void initHorTransitions(std::vector& transitions, int const width) { transitions.reserve(width); if (width == 1) { // No transitions allowed. transitions.push_back(HTransition(0, 0)); return; } // Only east transition is allowed. transitions.push_back(HTransition(0, 1)); for (int i = 1; i < width - 1; ++i) { // Both transitions are allowed. transitions.push_back(HTransition(-1, 1)); } // Only west transition is allowed. transitions.push_back(HTransition(-1, 0)); } void initVertTransitions(std::vector& transitions, int const height) { transitions.reserve(height); if (height == 1) { // No transitions allowed. transitions.push_back(VTransition(0, 0)); return; } // Only south transition is allowed. transitions.push_back(VTransition(0, ~0)); for (int i = 1; i < height - 1; ++i) { // Both transitions are allowed. transitions.push_back(VTransition(~0, ~0)); } // Only north transition is allowed. transitions.push_back(VTransition(~0, 0)); } } // namespace seed_fill_generic } // namespace detail } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/SeedFillGeneric.h000066400000000000000000000354331271170121200225720ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_SEEDFILL_GENERIC_H_ #define IMAGEPROC_SEEDFILL_GENERIC_H_ #include "Connectivity.h" #include "FastQueue.h" #include #include #include namespace imageproc { namespace detail { namespace seed_fill_generic { struct HTransition { 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_) {} }; struct VTransition { 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_) {} }; template struct Position { T* seed; T const* mask; int x; int y; Position(T* seed_, T const* 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 void seedFillSingleLine( SpreadOp spread_op, MaskOp mask_op, int const line_len, T* seed, int const seed_stride, T const* mask, int const 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])); } } template inline void processNeighbor( SpreadOp spread_op, MaskOp mask_op, FastQueue >& queue, T const this_val, T* const neighbor, T const* const neighbor_mask, Position const& base_pos, int const x_delta, int const y_delta) { T const new_val(mask_op(*neighbor_mask, spread_op(this_val, *neighbor))); if (new_val != *neighbor) { *neighbor = new_val; int const x = base_pos.x + x_delta; int const y = base_pos.y + y_delta; queue.push(Position(neighbor, neighbor_mask, x, y)); } } template void spread4( SpreadOp spread_op, MaskOp mask_op, FastQueue >& queue, HTransition const* h_transitions, VTransition const* v_transitions, int const seed_stride, int const mask_stride) { while (!queue.empty()) { Position const pos(queue.front()); queue.pop(); T const this_val(*pos.seed); HTransition const ht(h_transitions[pos.x]); VTransition const vt(v_transitions[pos.y]); T* seed; T const* mask; // Western neighbor. seed = pos.seed + ht.west_delta; mask = pos.mask + ht.west_delta; processNeighbor( spread_op, mask_op, queue, 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, 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, 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, this_val, seed, mask, pos, 0, 1 & vt.south_mask ); } } template void spread8( SpreadOp spread_op, MaskOp mask_op, FastQueue >& queue, HTransition const* h_transitions, VTransition const* v_transitions, int const seed_stride, int const mask_stride) { while (!queue.empty()) { Position const pos(queue.front()); queue.pop(); T const this_val(*pos.seed); HTransition const ht(h_transitions[pos.x]); VTransition const vt(v_transitions[pos.y]); T* seed; T const* 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, 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, 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, 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, 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, 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, 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, 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, this_val, seed, mask, pos, ht.west_delta, 1 & vt.south_mask ); } } template void seedFill4( SpreadOp spread_op, MaskOp mask_op, T* const seed, int const seed_stride, QSize const size, T const* const mask, int const mask_stride) { int const w = size.width(); int const h = size.height(); T* seed_line = seed; T const* 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; } seed_line -= seed_stride; mask_line -= mask_stride; FastQueue > queue; std::vector h_transitions; std::vector v_transitions; initHorTransitions(h_transitions, w); initVertTransitions(v_transitions, h); // Bottom to top. for (int y = h - 1; y >= 0; --y) { VTransition const vt(v_transitions[y]); // Right to left. for (int x = w - 1; x >= 0; --x) { HTransition const ht(h_transitions[x]); T* const p_base_seed = seed_line + x; T const* 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 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; Position const pos(p_base_seed, p_base_mask, x, y); T const* p_east_mask = p_base_mask + ht.east_delta; T const* p_south_mask = p_base_mask + (mask_stride & vt.south_mask); // Eastern neighbor. processNeighbor( spread_op, mask_op, queue, new_val, p_east_seed, p_east_mask, pos, ht.east_delta, 0 ); // Southern neighbor. processNeighbor( spread_op, mask_op, queue, new_val, p_south_seed, p_south_mask, pos, 0, 1 & vt.south_mask ); } seed_line -= seed_stride; mask_line -= mask_stride; } spread4( spread_op, mask_op, queue, &h_transitions[0], &v_transitions[0], seed_stride, mask_stride ); } template void seedFill8( SpreadOp spread_op, MaskOp mask_op, T* const seed, int const seed_stride, QSize const size, T const* const mask, int const mask_stride) { int const w = size.width(); int const 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; T const* 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] ) ); } // 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; std::vector h_transitions; std::vector v_transitions; initHorTransitions(h_transitions, w); initVertTransitions(v_transitions, h); // Bottom to top. for (int y = h - 1; y >= 0; --y) { VTransition const vt(v_transitions[y]); for (int x = w - 1; x >= 0; --x) { HTransition const ht(h_transitions[x]); T* const p_base_seed = seed_line + x; T const* 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; T const 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; Position const pos(p_base_seed, p_base_mask, x, y); T const* p_east_mask = p_base_mask + ht.east_delta; T const* p_south_mask = p_base_mask + (mask_stride & vt.south_mask); T const* p_south_west_mask = p_south_mask + ht.west_delta; T const* p_south_east_mask = p_south_mask + ht.east_delta; // Eastern neighbor. processNeighbor( spread_op, mask_op, queue, new_val, p_east_seed, p_east_mask, pos, ht.east_delta, 0 ); // South-eastern neighbor. processNeighbor( spread_op, mask_op, queue, 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, new_val, p_south_seed, p_south_mask, pos, 0, 1 & vt.south_mask ); // South-western neighbor. processNeighbor( spread_op, mask_op, queue, 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; } spread8( spread_op, mask_op, queue, &h_transitions[0], &v_transitions[0], seed_stride, mask_stride ); } } // namespace seed_fill_generic } // namespace detail /** * The following pseudocode illustrates the principle of a seed-fill algorithm: * [code] * do { * foreach () { * val = mask_op(mask[x, y], seed[x, y]); * foreach () { * seed[nx, ny] = mask_op(mask[nx, ny], spread_op(seed[nx, ny], val)); * } * } * } while (); * [/code] * * \param spread_op A functor or a pointer to a free function that can be called with * two arguments of type T and return the bigger or the smaller of the two. * \param mask_op Same as spread_op, but the oposite operation. * \param conn Determines whether to spread values to 4 or 8 eight immediate neighbors. * \param[in,out] seed Pointer to the seed buffer. * \param seed_stride The size of a row in the seed buffer, in terms of the number of T objects. * \param size Dimensions of the seed and the mask buffers. * \param mask Pointer to the mask data. * \param mask_stride The size of a row in the mask buffer, in terms of the number of T objects. * * This code is an implementation of the hybrid grayscale restoration algorithm described in: * Morphological Grayscale Reconstruction in Image Analysis: * 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 void seedFillGenericInPlace( SpreadOp spread_op, MaskOp mask_op, Connectivity conn, T* seed, int seed_stride, QSize size, T const* 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 ); } } } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/Shear.cpp000066400000000000000000000130131271170121200211710ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Shear.h" #include "RasterOp.h" #include "BinaryImage.h" #include #include #include #include #include #include namespace imageproc { void hShearFromTo(BinaryImage const& src, BinaryImage& dst, double const shear, double const y_origin, BWColor const 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()"); } int const width = src.width(); int const height = src.height(); // shift = floor(0.5 + shear * (y + 0.5 - y_origin)); double shift = 0.5 + shear * (0.5 - y_origin); double const shift_end = 0.5 + shear * (height - 0.5 - y_origin); int shift1 = (int)floor(shift); if (shift1 == floor(shift_end)) { assert(shift1 == 0); dst = src; return; } int shift2 = shift1; int y1 = 0; int y2 = 0; for (;;) { ++y2; shift += shear; shift2 = (int)floor(shift); if (shift1 != shift2 || y2 == height) { int const block_height = y2 - y1; if (abs(shift1) >= width) { // The shifted block would be completely off the image. QRect const fr(0, y1, width, block_height); dst.fill(fr, background_color); } else if (shift1 < 0) { // Shift to the left. QRect const dr(0, y1, width + shift1, block_height); QPoint const sp(-shift1, y1); rasterOp(dst, dr, src, sp); QRect const fr(width + shift1, y1, -shift1, block_height); dst.fill(fr, background_color); } else if (shift1 > 0) { // Shift to the right. QRect const dr(shift1, y1, width - shift1, block_height); QPoint const sp(0, y1); rasterOp(dst, dr, src, sp); QRect const fr(0, y1, shift1, block_height); dst.fill(fr, background_color); } else { // No shift, just copy. QRect const dr(0, y1, width, block_height); QPoint const sp(0, y1); rasterOp(dst, dr, src, sp); } if (y2 == height) { break; } y1 = y2; shift1 = shift2; } } } void vShearFromTo(BinaryImage const& src, BinaryImage& dst, double const shear, double const x_origin, BWColor const 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()"); } int const width = src.width(); int const height = src.height(); // shift = floor(0.5 + shear * (x + 0.5 - x_origin)); double shift = 0.5 + shear * (0.5 - x_origin); double const shift_end = 0.5 + shear * (width - 0.5 - x_origin); int shift1 = (int)floor(shift); if (shift1 == floor(shift_end)) { assert(shift1 == 0); dst = src; return; } int shift2 = shift1; int x1 = 0; int x2 = 0; for (;;) { ++x2; shift += shear; shift2 = (int)floor(shift); if (shift1 != shift2 || x2 == width) { int const block_width = x2 - x1; if (abs(shift1) >= height) { // The shifted block would be completely off the image. QRect const fr(x1, 0, block_width, height); dst.fill(fr, background_color); } else if (shift1 < 0) { // Shift upwards. QRect const dr(x1, 0, block_width, height + shift1); QPoint const sp(x1, -shift1); rasterOp(dst, dr, src, sp); QRect const fr(x1, height + shift1, block_width, -shift1); dst.fill(fr, background_color); } else if (shift1 > 0) { // Shift downwards. QRect const dr(x1, shift1, block_width, height - shift1); QPoint const sp(x1, 0); rasterOp(dst, dr, src, sp); QRect const fr(x1, 0, block_width, shift1); dst.fill(fr, background_color); } else { // No shift, just copy. QRect const dr(x1, 0, block_width, height); QPoint const sp(x1, 0); rasterOp(dst, dr, src, sp); } if (x2 == width) { break; } x1 = x2; shift1 = shift2; } } } BinaryImage hShear( BinaryImage const& src, double const shear, double const y_origin, BWColor const background_color) { BinaryImage dst(src.width(), src.height()); hShearFromTo(src, dst, shear, y_origin, background_color); return dst; } BinaryImage vShear( BinaryImage const& src, double const shear, double const x_origin, BWColor const background_color) { BinaryImage dst(src.width(), src.height()); vShearFromTo(src, dst, shear, x_origin, background_color); return dst; } void hShearInPlace( BinaryImage& image, double const shear, double const y_origin, BWColor const background_color) { hShearFromTo(image, image, shear, y_origin, background_color); } void vShearInPlace( BinaryImage& image, double const shear, double const x_origin, BWColor const background_color) { vShearFromTo(image, image, shear, x_origin, background_color); } } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/Shear.h000066400000000000000000000061751271170121200206510ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_SHEAR_H_ #define IMAGEPROC_SHEAR_H_ #include "BWColor.h" namespace imageproc { class BinaryImage; /** * \brief Horizontal shear. * * \param src The source image. * \param dst The desctination image. * \param shear The shift of each next line relative to the previous one. * \param y_origin The y value where a line would have a zero shift. * Note that a value of 1.0 doesn't mean that line 1 * is to have zero shift. The value of 1.5 would mean that. * \param background_color The color used to fill areas not represented * in the source image. * \note The source and destination images must have the same size. */ void hShearFromTo( BinaryImage const& src, BinaryImage& dst, double shear, double y_origin, BWColor background_color); /** * \brief Vertical shear. * * \param src The source image. * \param dst The destination image. * \param shear The shift of each next line relative to the previous one. * \param x_origin The x value where a line would have a zero shift. * Note that a value of 1.0 doesn't mean that line 1 * is to have zero shift. The value of 1.5 would mean that. * \param background_color The color used to fill areas not represented * in the source image. * \note The source and destination images must have the same size. */ void vShearFromTo( BinaryImage const& src, BinaryImage& dst, double shear, double x_origin, BWColor background_color); /** * \brief Horizontal shear returing a new image. * * Same as hShearFromTo(), but creates and returns the destination image. */ BinaryImage hShear( BinaryImage const& src, double shear, double y_origin, BWColor background_color); /** * \brief Vertical shear returning a new image. * * Same as vShearFromTo(), but creates and returns the destination image. */ BinaryImage vShear( BinaryImage const& src, double shear, double x_origin, BWColor background_color); /** * \brief In-place horizontal shear. * * Same as hShearFromTo() with src and dst being the same image. */ void hShearInPlace( BinaryImage& image, double shear, double y_origin, BWColor background_color); /** * \brief In-place vertical shear. * * Same as vShearFromTo() with src and dst being the same image. */ void vShearInPlace( BinaryImage& image, double shear, double x_origin, BWColor background_color); } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/SkewFinder.cpp000066400000000000000000000145721271170121200222030ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "SkewFinder.h" #include "BinaryImage.h" #include "BWColor.h" #include "BitOps.h" #include "Shear.h" #include "ReduceThreshold.h" #include "Constants.h" #include #include #include #include namespace imageproc { double const Skew::GOOD_CONFIDENCE = 2.0; double const SkewFinder::DEFAULT_MAX_ANGLE = 7.0; double const SkewFinder::DEFAULT_ACCURACY = 0.1; int const SkewFinder::DEFAULT_COARSE_REDUCTION = 2; int const SkewFinder::DEFAULT_FINE_REDUCTION = 1; double const 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) { } void SkewFinder::setMaxAngle(double const 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(double const accuracy) { m_accuracy = accuracy; } void SkewFinder::setCoarseReduction(int const reduction) { if (reduction < 0) { throw std::invalid_argument("SkewFinder: coarse reduction is invalid"); } m_coarseReduction = reduction; } void SkewFinder::setFineReduction(int const reduction) { if (reduction < 0) { throw std::invalid_argument("SkewFinder: fine reduction is invalid"); } m_fineReduction = reduction; } void SkewFinder::setResolutionRatio(double const ratio) { if (ratio <= 0.0) { throw std::invalid_argument("SkewFinder: resolution ratio is invalid"); } m_resolutionRatio = ratio; } Skew SkewFinder::findSkew(BinaryImage const& image) const { if (image.isNull()) { throw std::invalid_argument("SkewFinder: null image was provided"); } ReduceThreshold coarse_reduced(image); int const 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()); double const 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) { double const 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); double const fine_score1 = score_plus; double const 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. } 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); } double SkewFinder::process(BinaryImage const& src, BinaryImage& dst, double const angle) const { double const tg = tan(angle * constants::DEG2RAD); double const x_center = 0.5 * dst.width(); vShearFromTo(src, dst, tg / m_resolutionRatio, x_center, WHITE); return calcScore(dst); } double SkewFinder::calcScore(BinaryImage const& image) { int const width = image.width(); int const height = image.height(); uint32_t const* line = image.data(); int const wpl = image.wordsPerLine(); int const last_word_idx = (width - 1) >> 5; uint32_t const 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) { double const diff = num_black_pixels - last_line_black_pixels; score += diff * diff; } last_line_black_pixels = num_black_pixels; } return score; } } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/SkewFinder.h000066400000000000000000000107101271170121200216360ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_SKEWFINDER_H_ #define IMAGEPROC_SKEWFINDER_H_ #include "NonCopyable.h" namespace imageproc { class BinaryImage; /** * \brief The result of the "find skew" operation. * \see SkewFinder */ class Skew { public: /** * \brief The threshold separating good and poor confidence values. * \see confidence() */ static double const 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 double const DEFAULT_MAX_ANGLE; static double const DEFAULT_ACCURACY; static int const DEFAULT_COARSE_REDUCTION; static int const 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(BinaryImage const& image) const; private: static double const LOW_SCORE; double process(BinaryImage const& src, BinaryImage& dst, double angle) const; static double calcScore(BinaryImage const& image); double m_maxAngle; double m_accuracy; double m_resolutionRatio; int m_coarseReduction; int m_fineReduction; }; } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/SlicedHistogram.cpp000066400000000000000000000065371271170121200232250ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "SlicedHistogram.h" #include "BinaryImage.h" #include "BitOps.h" #include #include namespace imageproc { SlicedHistogram::SlicedHistogram() { } SlicedHistogram::SlicedHistogram(BinaryImage const& image, Type const type) { switch (type) { case ROWS: processHorizontalLines(image, image.rect()); break; case COLS: processVerticalLines(image, image.rect()); break; } } SlicedHistogram::SlicedHistogram( BinaryImage const& image, QRect const& area, Type const type) { 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; } } void SlicedHistogram::processHorizontalLines(BinaryImage const& image, QRect const& area) { m_data.reserve(area.height()); int const top = area.top(); int const bottom = area.bottom(); int const wpl = image.wordsPerLine(); int const first_word_idx = area.left() >> 5; int const last_word_idx = area.right() >> 5; // area.right() is within area uint32_t const first_word_mask = ~uint32_t(0) >> (area.left() & 31); int const last_word_unused_bits = (last_word_idx << 5) + 31 - area.right(); uint32_t const last_word_mask = ~uint32_t(0) << last_word_unused_bits; uint32_t const* line = image.data() + top * wpl; if (first_word_idx == last_word_idx) { uint32_t const mask = first_word_mask & last_word_mask; for (int y = top; y <= bottom; ++y, line += wpl) { int const 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); } } } void SlicedHistogram::processVerticalLines(BinaryImage const& image, QRect const& area) { m_data.reserve(area.width()); int const right = area.right(); int const height = area.height(); int const wpl = image.wordsPerLine(); uint32_t const* const top_line = image.data() + area.top() * wpl; for (int x = area.left(); x <= right; ++x) { uint32_t const* pword = top_line + (x >> 5); int const 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 scantailor-RELEASE_0_9_12_2/imageproc/SlicedHistogram.h000066400000000000000000000051321271170121200226600ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_SLICEDHISTOGRAM_H_ #define IMAGEPROC_SLICEDHISTOGRAM_H_ #include #include class QRect; namespace imageproc { class BinaryImage; /** * \brief Calculates and stores the number of black pixels * 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(BinaryImage const& 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(BinaryImage const& image, QRect const& area, Type type); size_t size() const { return m_data.size(); } void setSize(size_t size) { m_data.resize(size); } int const& operator[](size_t idx) const { return m_data[idx]; } int& operator[](size_t idx) { return m_data[idx]; } private: void processHorizontalLines(BinaryImage const& image, QRect const& area); void processVerticalLines(BinaryImage const& image, QRect const& area); std::vector m_data; }; } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/Sobel.h000066400000000000000000000170051271170121200206450ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_SOBEL_H_ #define IMAGEPROC_SOBEL_H_ /** * \file * * The Sobel operator treats a 2D grid of data points as a function f(x, y) * and approximates its gradient. It computes not the gradient itself, * but the gradient multiplied by 8. */ namespace imageproc { /** * Computes approximation of the horizontal gradient component, that is * the partial derivative with respect to x (multiplied by 8). * * \tparam T The type used for intermediate calculations. Must be signed. * \param width Horizontal size of a grid. Zero or negative value * will cause this function to return without doing anything. * \param height Vertical size of a grid. Zero or negative value * will cause this function to return without doing anything. * \param src Pointer or a random access iterator to the top-left corner * of the source grid. * \param src_stride The distance from a point on the source grid to the * point directly below it, in terms of iterator difference. * \param src_reader A functor that gets passed a dereferenced iterator * to the source grid and returns some type convertable to T. * It's called like this: * \code * SrcIt src_it = ...; * T const var(src_reader(*src_it)); * \endcode * Consider using boost::lambda for constructing such a functor, * possibly combined with one of the functors from ValueConf.h * \param tmp Pointer or a random access iterator to the top-left corner * of the temporary grid. The temporary grid will have the same * width and height as the source and destination grids. * Having the destination grid also serve as a temporary grid * is supported, provided it's able to store signed values. * Having all 3 to be the same is supported as well, subject * to the same condition. * \param tmp_stride The distance from a point on the temporary grid to the * point directly below it, in terms of iterator difference. * \param tmp_writer A functor that writes a value to the temporary grid. * It's called like this: * \code * TmpIt tmp_it = ...; * T val = ...; * tmp_writer(*tmp_it, val); * \endcode * \param tmp_reader A functor that gets passed a dereferenced iterator * to the temporary grid and returns some type convertable to T. * See \p src_reader for more info. * \param dst Pointer or a random access iterator to the top-left corner * of the destination grid. * \param dst_stride The distance from a point on the destination grid to the * point directly below it, in terms of iterator difference. * \param dst_writer A functor that writes a value to the destination grid. * See \p tmp_writer for more info. */ template< typename T, typename SrcIt, typename TmpIt, typename DstIt, typename SrcReader, typename TmpWriter, typename TmpReader, typename DstWriter > void horizontalSobel( int width, int height, SrcIt src, int src_stride, SrcReader src_reader, TmpIt tmp, int tmp_stride, TmpWriter tmp_writer, TmpReader tmp_reader, DstIt dst, int dst_stride, DstWriter dst_writer); /** * \see horizontalSobel() */ template< typename T, typename SrcIt, typename TmpIt, typename DstIt, typename SrcReader, typename TmpWriter, typename TmpReader, typename DstWriter > void verticalSobel( int width, int height, SrcIt src, int src_stride, SrcReader src_reader, TmpIt tmp, int tmp_stride, TmpWriter tmp_writer, TmpReader tmp_reader, DstIt dst, int dst_stride, DstWriter dst_writer); template< typename T, typename SrcIt, typename TmpIt, typename DstIt, typename SrcReader, typename TmpWriter, typename TmpReader, typename DstWriter > void horizontalSobel( int const width, int const height, SrcIt src, int src_stride, SrcReader src_reader, TmpIt tmp, int const tmp_stride, TmpWriter tmp_writer, TmpReader tmp_reader, DstIt dst, int const 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); T top(src_reader(*p_src)); if (height == 1) { tmp_writer(*p_tmp, top + top + top + top); continue; } T mid(src_reader(p_src[src_stride])); tmp_writer(*p_tmp, top + top + top + mid); for (int y = 1; y < height - 1; ++y) { p_src += src_stride; p_tmp += tmp_stride; T const bottom(src_reader(p_src[src_stride])); tmp_writer(*p_tmp, top + mid + mid + bottom); top = mid; mid = bottom; } p_src += src_stride; p_tmp += tmp_stride; tmp_writer(*p_tmp, top + mid + mid + mid); } // Horizontal pass: mid = right - left for (int y = 0; y < height; ++y) { T left(tmp_reader(*tmp)); if (width == 1) { dst_writer(*dst, left - left); } else { T mid(tmp_reader(tmp[1])); dst_writer(dst[0], mid - left); int x = 1; for (; x < width - 1; ++x) { T const right(tmp_reader(tmp[x + 1])); dst_writer(dst[x], right - left); left = mid; mid = right; } dst_writer(dst[x], mid - left); } tmp += tmp_stride; dst += dst_stride; } } template< typename T, typename SrcIt, typename TmpIt, typename DstIt, typename SrcReader, typename TmpWriter, typename TmpReader, typename DstWriter > void verticalSobel( int const width, int const height, SrcIt src, int src_stride, SrcReader src_reader, TmpIt tmp, int const tmp_stride, TmpWriter tmp_writer, TmpReader tmp_reader, DstIt dst, int const dst_stride, DstWriter dst_writer) { if (width <= 0 || height <= 0) { return; } TmpIt const tmp_orig(tmp); // 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); int x = 1; for (; x < width - 1; ++x) { T const 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; } // 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; } T mid(tmp_reader(p_tmp[tmp_stride])); dst_writer(*p_dst, mid - top); for (int y = 1; y < height - 1; ++y) { p_tmp += tmp_stride; p_dst += dst_stride; T const 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); } } } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/Transform.cpp000066400000000000000000000360401271170121200221070ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Transform.h" #include "Grayscale.h" #include "GrayImage.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace imageproc { namespace { struct XLess { bool operator()(QPointF const& lhs, QPointF const& rhs) const { return lhs.x() < rhs.x(); } }; struct YLess { bool operator()(QPointF const& lhs, QPointF const& rhs) const { return lhs.y() < rhs.y(); } }; class Gray { public: Gray() : m_grayLevel(0) {} void add(uint8_t const gray_level, unsigned const area) { m_grayLevel += gray_level * area; } uint8_t result(unsigned const total_area) const { unsigned const half_area = total_area >> 1; unsigned const res = (m_grayLevel + half_area) / total_area; return static_cast(res); } private: unsigned m_grayLevel; }; class RGB32 { public: RGB32() : m_red(0), m_green(0), m_blue(0) {} void add(uint32_t rgb, unsigned const area) { m_blue += (rgb & 0xFF) * area; rgb >>= 8; m_green += (rgb & 0xFF) * area; rgb >>= 8; m_red += (rgb & 0xFF) * area; } uint32_t result(unsigned const total_area) const { unsigned const half_area = total_area >> 1; uint32_t rgb = 0x0000FF00; rgb |= (m_red + half_area) / total_area; rgb <<= 8; rgb |= (m_green + half_area) / total_area; rgb <<= 8; rgb |= (m_blue + half_area) / total_area; return rgb; } private: unsigned m_red; unsigned m_green; unsigned m_blue; }; class ARGB32 { public: ARGB32() : m_alpha(0), m_red(0), m_green(0), m_blue(0) {} void add(uint32_t argb, unsigned const area) { m_blue += (argb & 0xFF) * area; argb >>= 8; m_green += (argb & 0xFF) * area; argb >>= 8; m_red += (argb & 0xFF) * area; argb >>= 8; m_alpha += argb * area; } uint32_t result(unsigned const total_area) const { unsigned const half_area = total_area >> 1; uint32_t argb = (m_alpha + half_area) / total_area; argb <<= 8; argb |= (m_red + half_area) / total_area; argb <<= 8; argb |= (m_green + half_area) / total_area; argb <<= 8; argb |= (m_blue + half_area) / total_area; return argb; } private: unsigned m_alpha; unsigned m_red; unsigned m_green; unsigned m_blue; }; static QSizeF calcSrcUnitSize(QTransform const& xform, QSizeF const& 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()); double const width = src_poly.back().x() - src_poly.front().x(); std::sort(src_poly.begin(), src_poly.end(), YLess()); double const height = src_poly.back().y() - src_poly.front().y(); QSizeF const min32(min * 32.0); return QSizeF( std::max(min32.width(), qreal(width)), std::max(min32.height(), qreal(height)) ); } template static void transformGeneric( StorageUnit const* const src_data, int const src_stride, QSize const src_size, StorageUnit* const dst_data, int const dst_stride, QTransform const& xform, QRect const& dst_rect, StorageUnit const outside_color, int const outside_flags, QSizeF const& min_mapping_area) { int const sw = src_size.width(); int const sh = src_size.height(); int const dw = dst_rect.width(); int const 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(); QSizeF const src32_unit_size(calcSrcUnitSize(inv_xform, min_mapping_area)); int const src32_unit_w = std::max(1, qRound(src32_unit_size.width())); int const src32_unit_h = std::max(1, qRound(src32_unit_size.height())); for (int dy = 0; dy < dh; ++dy, dst_line += dst_stride) { double const f_dy_center = dy + 0.5; double const f_sx32_base = f_dy_center * inv_xform.m21() + inv_xform.dx(); double const f_sy32_base = f_dy_center * inv_xform.m22() + inv_xform.dy(); for (int dx = 0; dx < dw; ++dx) { double const f_dx_center = dx + 0.5; double const f_sx32_center = f_sx32_base + f_dx_center * inv_xform.m11(); double const 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 { int const src_x = qBound(0, (src_left + src_right) >> 1, sw - 1); int const 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) { unsigned const top_fraction = 32 - (src32_top & 31); unsigned const hor_fraction = src32_right - src32_left; background_area += top_fraction * hor_fraction; unsigned const full_pixels_ver = -1 - src_top; background_area += hor_fraction * (full_pixels_ver << 5); src_top = 0; src32_top = 0; } if (src_bottom >= sh) { unsigned const bottom_fraction = src32_bottom - (src_bottom << 5); unsigned const hor_fraction = src32_right - src32_left; background_area += bottom_fraction * hor_fraction; unsigned const 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) { unsigned const left_fraction = 32 - (src32_left & 31); unsigned const vert_fraction = src32_bottom - src32_top; background_area += left_fraction * vert_fraction; unsigned const full_pixels_hor = -1 - src_left; background_area += vert_fraction * (full_pixels_hor << 5); src_left = 0; src32_left = 0; } if (src_right >= sw) { unsigned const right_fraction = src32_right - (src_right << 5); unsigned const vert_fraction = src32_bottom - src32_top; background_area += right_fraction * vert_fraction; unsigned const 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 { mixer.add(outside_color, background_area); } unsigned const left_fraction = 32 - (src32_left & 31); unsigned const top_fraction = 32 - (src32_top & 31); unsigned const right_fraction = src32_right - (src_right << 5); unsigned const 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)); unsigned const 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 { int const src_x = qBound(0, (src_left + src_right) >> 1, sw - 1); int const src_y = qBound(0, (src_top + src_bottom) >> 1, sh - 1); dst_line[dx] = src_data[src_y * src_stride + src_x]; } continue; } StorageUnit const* 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 StorageUnit const 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 unsigned const vert_fraction = src32_bottom - src32_top; unsigned const left_area = vert_fraction * left_fraction; unsigned const middle_area = vert_fraction << 5; unsigned const 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 unsigned const hor_fraction = src32_right - src32_left; unsigned const top_area = hor_fraction * top_fraction; unsigned const middle_area = hor_fraction << 5; unsigned const 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 unsigned const top_area = top_fraction << 5; unsigned const bottom_area = bottom_fraction << 5; unsigned const left_area = left_fraction << 5; unsigned const right_area = right_fraction << 5; unsigned const topleft_area = top_fraction * left_fraction; unsigned const topright_area = top_fraction * right_fraction; unsigned const bottomleft_area = bottom_fraction * left_fraction; unsigned const 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.result(src_area + background_area); } } } } // anonymous namespace QImage transform( QImage const& src, QTransform const& xform, QRect const& dst_rect, OutsidePixels const outside_pixels, QSizeF const& 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.format() == QImage::Format_Indexed8 && src.allGray()) { // 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()); 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 ); return gray_dst; } else { if (src.hasAlphaChannel() || qAlpha(outside_pixels.rgba()) != 0xff) { QImage const src_argb32(src.convertToFormat(QImage::Format_ARGB32)); QImage dst(dst_rect.size(), QImage::Format_ARGB32); transformGeneric( (uint32_t const*)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 ); return dst; } else { QImage const src_rgb32(src.convertToFormat(QImage::Format_RGB32)); QImage dst(dst_rect.size(), QImage::Format_RGB32); transformGeneric( (uint32_t const*)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 ); return dst; } } } GrayImage transformToGray( QImage const& src, QTransform const& xform, QRect const& dst_rect, OutsidePixels const outside_pixels, QSizeF const& min_mapping_area) { if (src.isNull() || dst_rect.isEmpty()) { return GrayImage(); } 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"); } GrayImage const gray_src(src); GrayImage dst(dst_rect.size()); 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 ); return dst; } } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/Transform.h000066400000000000000000000077441271170121200215650ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_TRANSFORM_H_ #define IMAGEPROC_TRANSFORM_H_ #include #include #include class QImage; class QRect; class QTransform; 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(QColor const& 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(QColor const& 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 & 0x00ffffff; } 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; }; /** * \brief Apply an affine transformation to the image. * * \param src The source image. * \param xform The transformation from source to destination. * Only affine transformations are supported. * \param dst_rect The area in source image coordinates to return * as a destination image. * \param background_color Used to fill areas not represented in the source image. * \param weak_background If set to true, \p background_color is only taken * into account if a target pixel maps to an area completely outside of * the source image. That is, if at least one source image pixel * influences a particular target pixel, then any background pixels * that may also influence that target pixel are ignored.\n * If set to false, source image pixels and background pixels are * treated equally. * \param min_mapping_area Defines the minimum rectangle in the source image * that maps to a destination pixel. This can be used to control * smoothing. * \return The transformed image. It's format may differ from the * source image format, for example Format_Indexed8 may * be transformed to Format_RGB32, if the source image * contains colors other than shades of gray. */ QImage transform( QImage const& src, QTransform const& xform, QRect const& dst_rect, OutsidePixels outside_pixels, QSizeF const& min_mapping_area = QSizeF(0.9, 0.9)); /** * \brief Apply an affine transformation to the image. * * Same as transform(), except the source image image is converted * to grayscale before transforming it. */ GrayImage transformToGray( QImage const& src, QTransform const& xform, QRect const& dst_rect, OutsidePixels outside_pixels, QSizeF const& min_mapping_area = QSizeF(0.9, 0.9)); } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/UpscaleIntegerTimes.cpp000066400000000000000000000065201271170121200240500ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "UpscaleIntegerTimes.h" #include "BinaryImage.h" #include #include #include #include #include namespace imageproc { namespace { inline uint32_t multiplyBit(uint32_t bit, int times) { return (uint32_t(0) - bit) >> (32 - times); } void expandImpl( BinaryImage& dst, BinaryImage const& src, int const xscale, int const yscale) { int const sw = src.width(); int const sh = src.height(); int const src_wpl = src.wordsPerLine(); int const dst_wpl = dst.wordsPerLine(); uint32_t const* 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) { uint32_t const src_word = src_line[sx >> 5]; int const src_bit = 31 - (sx & 31); uint32_t const 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; } uint32_t const* 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); } } } } // anonymous namespace BinaryImage upscaleIntegerTimes( BinaryImage const& src, int const xscale, int const yscale) { 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"); } BinaryImage dst(src.width() * xscale, src.height() * yscale); expandImpl(dst, src, xscale, yscale); return dst; } BinaryImage upscaleIntegerTimes( BinaryImage const& src, QSize const& dst_size, BWColor const padding) { if (src.isNull()) { BinaryImage dst(dst_size); dst.fill(padding); return dst; } int const xscale = dst_size.width() / src.width(); int const 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); QRect const rect(0, 0, src.width() * xscale, src.height() * yscale); dst.fillExcept(rect, padding); return dst; } } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/UpscaleIntegerTimes.h000066400000000000000000000030351271170121200235130ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_UPSCALE_INTEGER_TIMES_H_ #define IMAGEPROC_UPSCALE_INTEGER_TIMES_H_ #include "BWColor.h" class QSize; namespace imageproc { class BinaryImage; /** * \brief Upscale a binary image integer times in each direction. */ BinaryImage upscaleIntegerTimes(BinaryImage const& src, int xscale, int yscale); /** * \brief Upscale a binary image integer times in each direction * and add padding if necessary. * * The resulting image will have a size of \p dst_size, which is achieved * by upscaling the source image integer times in each direction and then * adding a padding to reach the requested size. */ BinaryImage upscaleIntegerTimes( BinaryImage const& src, QSize const& dst_size, BWColor padding); } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/tests/000077500000000000000000000000001271170121200205675ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/imageproc/tests/CMakeLists.txt000066400000000000000000000014111271170121200233240ustar00rootroot00000000000000INCLUDE_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 TestDentFinder.cpp TestBinarize.cpp TestPolygonRasterizer.cpp TestSeedFill.cpp TestSEDM.cpp TestRastLineFinder.cpp Utils.cpp Utils.h ) SOURCE_GROUP("Sources" FILES ${sources}) SET( libs imageproc math foundation ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} ${Boost_PRG_EXECUTION_MONITOR_LIBRARY} ${QT_QTGUI_LIBRARY} ${QT_QTCORE_LIBRARY} ${EXTRA_LIBS} ) ADD_EXECUTABLE(imageproc_tests ${sources}) TARGET_LINK_LIBRARIES(imageproc_tests ${libs}) scantailor-RELEASE_0_9_12_2/imageproc/tests/TestBinarize.cpp000066400000000000000000000023621271170121200237010ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Binarize.h" #include "BinaryImage.h" #include "Utils.h" #include #include #ifndef Q_MOC_RUN #include #endif namespace imageproc { namespace tests { using namespace utils; BOOST_AUTO_TEST_SUITE(BinarizeTestSuite); #if 0 BOOST_AUTO_TEST_CASE(test) { QImage img("test.png"); binarizeWolf(img).toQImage().save("out.png"); } #endif BOOST_AUTO_TEST_SUITE_END(); } // namespace tests } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/tests/TestBinaryImage.cpp000066400000000000000000000116651271170121200243330ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "BinaryImage.h" #include "BWColor.h" #include "Utils.h" #include #ifndef Q_MOC_RUN #include #endif #include namespace imageproc { namespace tests { 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_AUTO_TEST_CASE(test_from_to_qimage) { int const w = 50; int const h = 64; QImage qimg_argb32(w, h, QImage::Format_ARGB32); QImage qimg_mono(w, h, QImage::Format_Mono); qimg_mono.setNumColors(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) { int const 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); } BOOST_AUTO_TEST_CASE(test_full_fill) { BinaryImage white(100, 100); white.fill(WHITE); QImage q_white(100, 100, QImage::Format_Mono); q_white.fill(1); BOOST_REQUIRE(BinaryImage(q_white) == white); BinaryImage black(30, 30); black.fill(BLACK); QImage q_black(30, 30, QImage::Format_Mono); q_black.fill(0); BOOST_REQUIRE(BinaryImage(q_black) == black); } BOOST_AUTO_TEST_CASE(test_partial_fill_small) { QImage q_image(randomMonoQImage(100, 100)); QRect const 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.setNumColors(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)); QRect const 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.setNumColors(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)); QRect const 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.setNumColors(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_AUTO_TEST_CASE(test_content_bounding_box4) { static int const 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 }; BinaryImage const img(makeBinaryImage(inp, 9, 8)); BOOST_CHECK(img.contentBoundingBox() == QRect(1, 1, 6, 6)); } BOOST_AUTO_TEST_SUITE_END(); } // namespace tests } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/tests/TestConnCompEraser.cpp000066400000000000000000000053431271170121200250160ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ConnCompEraser.h" #include "ConnComp.h" #include "BinaryImage.h" #include "Utils.h" #include #include #include #ifndef Q_MOC_RUN #include #endif namespace imageproc { namespace tests { using namespace utils; BOOST_AUTO_TEST_SUITE(ConnCompEraserTestSuite); BOOST_AUTO_TEST_CASE(test_null_image) { ConnCompEraser eraser(BinaryImage(), CONN4); BOOST_CHECK(eraser.nextConnComp().isNull()); } BOOST_AUTO_TEST_CASE(test_small_image) { static int const 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.push_back(QRect(2, 0, 3, 6)); c4r.push_back(QRect(0, 3, 2, 1)); c4r.push_back(QRect(1, 5, 1, 1)); c4r.push_back(QRect(5, 2, 4, 3)); c4r.push_back(QRect(0, 6, 7, 2)); c4r.push_back(QRect(7, 6, 1, 1)); std::list c8r; c8r.push_back(QRect(0, 0, 9, 6)); c8r.push_back(QRect(0, 6, 8, 2)); BinaryImage img(makeBinaryImage(inp, 9, 8)); ConnComp cc; ConnCompEraser eraser4(img, CONN4); while (!(cc = eraser4.nextConnComp()).isNull()) { std::list::iterator const 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."); ConnCompEraser eraser8(img, CONN8); while (!(cc = eraser8.nextConnComp()).isNull()) { std::list::iterator const 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_AUTO_TEST_SUITE_END(); } // namespace tests } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/tests/TestConnCompEraserExt.cpp000066400000000000000000000110231271170121200254670ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License 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 #ifndef Q_MOC_RUN #include #endif namespace imageproc { namespace tests { using namespace utils; BOOST_AUTO_TEST_SUITE(ConnCompEraserExtTestSuite); BOOST_AUTO_TEST_CASE(test_null_image) { ConnCompEraser eraser(BinaryImage(), CONN4); BOOST_CHECK(eraser.nextConnComp().isNull()); } static bool checkAlignedImage( ConnCompEraserExt const& eraser, BinaryImage const& nonaligned) { BinaryImage const aligned(eraser.computeConnCompImageAligned()); int const 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; } BOOST_AUTO_TEST_CASE(test_small_image) { static int const 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 int const 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 int const out4_2[] = { 1, 1 }; c4i.push_back(makeBinaryImage(out4_2, 2, 1)); static int const 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 int const out4_4[] = { 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, }; c4i.push_back(makeBinaryImage(out4_4, 4, 3)); static int const out4_5[] = { 1 }; c4i.push_back(makeBinaryImage(out4_5, 1, 1)); static int const out4_6[] = { 1 }; c4i.push_back(makeBinaryImage(out4_6, 1, 1)); std::list c8i; static int const 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 int const 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()) { BinaryImage const cc_img(eraser4.computeConnCompImage()); std::list::iterator const 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()) { BinaryImage const cc_img(eraser8.computeConnCompImage()); std::list::iterator const 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_AUTO_TEST_SUITE_END(); } // namespace tests } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/tests/TestDentFinder.cpp000066400000000000000000000040221271170121200241530ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "DentFinder.h" #include "BinaryImage.h" #include "Utils.h" #include #ifndef Q_MOC_RUN #include #endif namespace imageproc { namespace tests { using namespace utils; BOOST_AUTO_TEST_SUITE(DentFinderTestSuite); BOOST_AUTO_TEST_CASE(test_null_image) { BinaryImage const null_img; BOOST_CHECK(DentFinder::findDentsAndHoles(null_img).isNull()); } BOOST_AUTO_TEST_CASE(test1) { static int const inp[] = { 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 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, 1, 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, 1, 1, 1, 0, 0, 0 }; static int const out[] = { 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const control(makeBinaryImage(out, 9, 9)); BOOST_CHECK(DentFinder::findDentsAndHoles(img) == control); } BOOST_AUTO_TEST_SUITE_END(); } // namespace tests } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/tests/TestGrayscale.cpp000066400000000000000000000043231271170121200240470ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Grayscale.h" #include "Utils.h" #include #ifndef Q_MOC_RUN #include #endif #include namespace imageproc { namespace tests { using namespace utils; BOOST_AUTO_TEST_SUITE(GrayscaleTestSuite); BOOST_AUTO_TEST_CASE(test_null_image) { BOOST_CHECK(toGrayscale(QImage()).isNull()); } BOOST_AUTO_TEST_CASE(test_mono_to_grayscale) { int const w = 50; int const 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) { int const rnd = rand() & 1; mono.setPixel(x, y, rnd ? 0 : 1); gray.setPixel(x, y, rnd ? 0 : 255); } } QImage const mono_lsb(mono.convertToFormat(QImage::Format_MonoLSB)); BOOST_REQUIRE(toGrayscale(mono) == gray); BOOST_CHECK(toGrayscale(mono_lsb) == gray); } BOOST_AUTO_TEST_CASE(test_argb32_to_grayscale) { int const w = 50; int const 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) { int const rnd = rand() & 1; argb32.setPixel(x, y, rnd ? 0x80303030 : 0x80606060); gray.setPixel(x, y, rnd ? 0x30 : 0x60); } } BOOST_CHECK(toGrayscale(argb32) == gray); } BOOST_AUTO_TEST_SUITE_END(); } // namespace tests } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/tests/TestMorphology.cpp000066400000000000000000001045651271170121200243050ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License 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 #ifndef Q_MOC_RUN #include #endif namespace imageproc { namespace tests { using namespace utils; BOOST_AUTO_TEST_SUITE(MorphologyTestSuite); BOOST_AUTO_TEST_CASE(test_dilate_1x1) { static int const 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 }; BinaryImage const img(makeBinaryImage(inp, 9, 9)); BOOST_CHECK(dilateBrick(img, QSize(1, 1), img.rect()) == img); } BOOST_AUTO_TEST_CASE(test_dilate_1x1_gray) { static int const 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 }; GrayImage const 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 int const 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 int const 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 }; BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const control(makeBinaryImage(out, 9, 9)); BOOST_CHECK(dilateBrick(img, QSize(1, 1), img.rect().translated(2, 2), BLACK) == control); } BOOST_AUTO_TEST_CASE(test_dilate_1x1_shift_gray) { static int const 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 int const 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 }; GrayImage const img(makeGrayImage(inp, 9, 9)); GrayImage const 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 int const 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 int const 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 }; GrayImage const img(makeGrayImage(inp, 9, 9)); GrayImage const control(makeGrayImage(out, 9, 9)); BOOST_CHECK(dilateGray(img, QSize(3, 1), img.rect()) == control); } BOOST_AUTO_TEST_CASE(test_dilate_1x3_gray) { static int const 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 int const 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 }; GrayImage const img(makeGrayImage(inp, 9, 9)); GrayImage const control(makeGrayImage(out, 9, 9)); BOOST_CHECK(dilateGray(img, QSize(1, 3), img.rect()) == control); } BOOST_AUTO_TEST_CASE(test_dilate_1x20_gray) { static int const 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 int const 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 }; GrayImage const img(makeGrayImage(inp, 9, 9)); GrayImage const control(makeGrayImage(out, 9, 9)); BOOST_CHECK(dilateGray(img, QSize(1, 20), img.rect()) == control); } BOOST_AUTO_TEST_CASE(test_dilate_3x3_white) { static int const 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 int const 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 }; BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const control(makeBinaryImage(out, 9, 9)); BOOST_CHECK(dilateBrick(img, QSize(3, 3), img.rect(), WHITE) == control); } BOOST_AUTO_TEST_CASE(test_dilate_3x3_gray) { static int const 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 int const 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 }; GrayImage const img(makeGrayImage(inp, 9, 9)); GrayImage const 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 int const 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 int const 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 }; GrayImage const img(makeGrayImage(inp, 9, 9)); GrayImage const control(makeGrayImage(out, 7, 7)); QRect const 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 int const 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 int const 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 }; GrayImage const img(makeGrayImage(inp, 9, 9)); GrayImage const control(makeGrayImage(out, 9, 9)); BOOST_CHECK(openGray(img, QSize(1, 2), 0x00) == control); } BOOST_AUTO_TEST_CASE(test_dilate_5x5_white) { static int const 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 int const 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 }; BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const control(makeBinaryImage(out, 9, 9)); BOOST_CHECK(dilateBrick(img, QSize(5, 5), img.rect(), WHITE) == control); } BOOST_AUTO_TEST_CASE(test_dilate_3x3_narrowing_white) { static int const 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 int const 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 }; BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const control(makeBinaryImage(out, 4, 9)); QRect const dst_rect(5, 0, 4, 9); BOOST_CHECK(dilateBrick(img, QSize(3, 3), dst_rect, WHITE) == control); } BOOST_AUTO_TEST_CASE(test_dilate_5x5_narrowing_white) { static int const 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 int const 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 }; BinaryImage const img(makeBinaryImage(inp, 11, 9)); BinaryImage const control(makeBinaryImage(out, 6, 5)); QRect const dst_rect(4, 1, 6, 5); BOOST_CHECK(dilateBrick(img, QSize(5, 5), dst_rect, WHITE) == control); } BOOST_AUTO_TEST_CASE(test_dilate_3x3_narrowing_black) { static int const 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 int const 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 }; BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const control(makeBinaryImage(out, 4, 9)); QRect const dst_rect(QRect(5, 0, 4, 9)); BOOST_CHECK(dilateBrick(img, QSize(3, 3), dst_rect, BLACK) == control); } BOOST_AUTO_TEST_CASE(test_dilate_3x3_widening_white) { static int const 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 int const 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, }; BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const control(makeBinaryImage(out, 11, 11)); QRect const dst_rect(img.rect().adjusted(-1, -1, 1, 1)); BOOST_CHECK(dilateBrick(img, QSize(3, 3), dst_rect, WHITE) == control); } BOOST_AUTO_TEST_CASE(test_dilate_3x3_widening_black) { static int const 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 int const 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, }; BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const control(makeBinaryImage(out, 11, 11)); QRect const dst_rect(img.rect().adjusted(-1, -1, 1, 1)); BOOST_CHECK(dilateBrick(img, QSize(3, 3), dst_rect, BLACK) == control); } BOOST_AUTO_TEST_CASE(test_dilate_3x1_out_of_brick_white) { static int const 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 int const 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 }; BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const control(makeBinaryImage(out, 9, 9)); Brick const brick(QSize(3, 1), QPoint(-1, 0)); BOOST_CHECK(dilateBrick(img, brick, img.rect(), WHITE) == control); } BOOST_AUTO_TEST_CASE(test_dilate_1x3_out_of_brick_black) { static int const 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 int const 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 }; BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const control(makeBinaryImage(out, 9, 9)); Brick const brick(QSize(1, 3), QPoint(0, -1)); BOOST_CHECK(dilateBrick(img, brick, img.rect(), BLACK) == control); } BOOST_AUTO_TEST_CASE(test_large_dilate) { BinaryImage img(110, 110); img.fill(WHITE); QRect const initial_rect(img.rect().center(), QSize(1, 1)); img.fill(initial_rect, BLACK); Brick const brick(QSize(80, 80)); QRect const extended_rect( initial_rect.adjusted( brick.minX(), brick.minY(), brick.maxX(), brick.maxY() ) ); BinaryImage control(img); control.fill(extended_rect, BLACK); BOOST_CHECK(dilateBrick(img, brick, img.rect(), WHITE) == control); } BOOST_AUTO_TEST_CASE(test_erode_1x1) { static int const 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 }; BinaryImage const 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 int const 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 int const 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 }; BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const control(makeBinaryImage(out, 9, 9)); Brick const brick(QSize(3, 3), QPoint(0, 1)); BOOST_CHECK(erodeBrick(img, brick, img.rect(), BLACK) == control); } BOOST_AUTO_TEST_CASE(test_erode_3x3_assymmetric_white) { static int const 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 int const 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 }; BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const control(makeBinaryImage(out, 9, 9)); Brick const brick(QSize(3, 3), QPoint(0, 1)); BOOST_CHECK(erodeBrick(img, brick, img.rect(), WHITE) == control); } BOOST_AUTO_TEST_CASE(test_erode_11x11_white) { static int const 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 int const 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 }; BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const control(makeBinaryImage(out, 9, 9)); Brick const brick(QSize(11, 11)); BOOST_CHECK(erodeBrick(img, brick, img.rect(), WHITE) == control); } BOOST_AUTO_TEST_CASE(test_open_2x2_white) { static int const 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 int const 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 }; BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const control(makeBinaryImage(out, 9, 9)); BOOST_CHECK(openBrick(img, QSize(2, 2), img.rect(), WHITE) == control); } BOOST_AUTO_TEST_CASE(test_open_2x2_black) { static int const 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 int const 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 }; BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const control(makeBinaryImage(out, 9, 9)); BOOST_CHECK(openBrick(img, QSize(2, 2), img.rect(), BLACK) == control); } BOOST_AUTO_TEST_CASE(test_open_2x2_shifted_white) { static int const 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 int const 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 }; BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const control(makeBinaryImage(out, 9, 9)); QRect const dst_rect(img.rect().translated(2, 1)); BOOST_CHECK(openBrick(img, QSize(2, 2), dst_rect, WHITE) == control); } BOOST_AUTO_TEST_CASE(test_open_2x2_shifted_black) { static int const 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 int const 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 }; BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const control(makeBinaryImage(out, 9, 9)); QRect const dst_rect(img.rect().translated(2, 1)); BOOST_CHECK(openBrick(img, QSize(2, 2), dst_rect, BLACK) == control); } BOOST_AUTO_TEST_CASE(test_open_2x2_narrowing) { static int const 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 int const 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 }; BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const control(makeBinaryImage(out, 4, 4)); QRect const 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_AUTO_TEST_CASE(test_close_2x2_white) { static int const 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 int const 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 }; BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const control(makeBinaryImage(out, 9, 9)); BOOST_CHECK(closeBrick(img, QSize(2, 2), img.rect(), WHITE) == control); } BOOST_AUTO_TEST_CASE(test_close_2x2_black) { static int const 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 int const 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 }; BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const control(makeBinaryImage(out, 9, 9)); BOOST_CHECK(closeBrick(img, QSize(2, 2), img.rect(), BLACK) == control); } BOOST_AUTO_TEST_CASE(test_close_2x2_shifted_white) { static int const 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 int const 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 }; BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const control(makeBinaryImage(out, 9, 9)); QRect const dst_rect(img.rect().translated(2, 1)); BOOST_CHECK(closeBrick(img, QSize(2, 2), dst_rect, WHITE) == control); } BOOST_AUTO_TEST_CASE(test_close_2x2_shifted_black) { static int const 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 int const 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 }; BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const control(makeBinaryImage(out, 9, 9)); QRect const dst_rect(img.rect().translated(2, 1)); BOOST_CHECK(closeBrick(img, QSize(2, 2), dst_rect, BLACK) == control); } BOOST_AUTO_TEST_CASE(test_close_2x2_narrowing) { static int const 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 int const 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 }; BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const control(makeBinaryImage(out, 4, 4)); QRect const 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_AUTO_TEST_CASE(test_hmm_1) { static int const 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 int const 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 char const pattern[] = "?X?" "X X" "?X?"; QPoint const origin(1, 1); BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const 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_AUTO_TEST_CASE(test_hmm_surroundings_1) { static int const 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 int const 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 int const 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 char const pattern[] = "?X?" "X X" "?X?"; QPoint const origin(1, 1); BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const control_w(makeBinaryImage(out_white, 9, 9)); BinaryImage const 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_AUTO_TEST_CASE(test_hmm_surroundings_2) { static int const 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 int const 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 char const pattern[] = "?X?" "X X" "?X?"; QPoint const origin(1, 0); BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const 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_AUTO_TEST_CASE(test_hmm_cornercase_1) { static int const 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 int const 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 char const pattern[] = "?X?" "X X" "?X?"; QPoint const origin(10, 0); BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const 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_AUTO_TEST_CASE(test_hmm_cornercase_2) { static int const 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 int const 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 char const pattern[] = "?X?" "X X" "?X?"; QPoint const origin(0, 9); BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const 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_AUTO_TEST_CASE(test_hmr_1) { static int const 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 int const 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 char const pattern[] = " - " "X+X" "XXX"; BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const control(makeBinaryImage(out, 9, 9)); BOOST_CHECK(hitMissReplace(img, WHITE, pattern, 3, 3) == control); BOOST_CHECK(hitMissReplace(img, BLACK, pattern, 3, 3) == control); } BOOST_AUTO_TEST_SUITE_END(); } // namespace tests } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/tests/TestOrthogonalRotation.cpp000066400000000000000000000120331271170121200257660ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "OrthogonalRotation.h" #include "BinaryImage.h" #include "Utils.h" #include #include #ifndef Q_MOC_RUN #include #endif namespace imageproc { namespace tests { using namespace utils; BOOST_AUTO_TEST_SUITE(OrthogonalRotationTestSuite); BOOST_AUTO_TEST_CASE(test_null_image) { BinaryImage const null_img; BOOST_CHECK(orthogonalRotation(null_img, 90).isNull()); } BOOST_AUTO_TEST_CASE(test_full_image) { static int const 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 int const 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 int const 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 int const 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 }; BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const out1_img(makeBinaryImage(out1, 9, 9)); BinaryImage const out2_img(makeBinaryImage(out2, 9, 9)); BinaryImage const 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 int const 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 int const 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 int const 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 int const 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 int const 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 }; QRect const rect(1, 2, 7, 7); BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const out1_img(makeBinaryImage(out1, 7, 7)); BinaryImage const out2_img(makeBinaryImage(out2, 7, 7)); BinaryImage const out3_img(makeBinaryImage(out3, 7, 7)); BinaryImage const 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(); } // namespace tests } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/tests/TestPolygonRasterizer.cpp000066400000000000000000000137321271170121200256430ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License 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 #ifndef Q_MOC_RUN #include #endif namespace imageproc { namespace tests { using namespace utils; BOOST_AUTO_TEST_SUITE(PolygonRasterizerTestSuite); static QPolygonF createShape(QSize const& image_size, double radius) { QPointF const center(0.5 * image_size.width(), 0.5 * image_size.height()); double const PI = 3.14159265; double angle = PI / 2.0; int const num_steps = 5; double const step = PI * 2.0 / num_steps; QPolygonF poly; poly.push_back(center + QPointF(cos(angle), sin(angle)) * radius); for (int i = 1; i < num_steps; ++i) { angle += step * 2; poly.push_back(center + QPointF(cos(angle), sin(angle)) * radius); } return poly; } static bool fuzzyCompare(BinaryImage const& img, QImage const& control) { // 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); // Are there pixels different in both cases? rasterOp >(control1, control2); return control1.countBlackPixels() == 0; } static bool testFillShape( QSize const& image_size, QPolygonF const& shape, Qt::FillRule 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); { 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); } static bool testFillExceptShape( QSize const& image_size, QPolygonF const& shape, Qt::FillRule 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); { 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); } BOOST_AUTO_TEST_CASE(test_complex_shape) { QSize const image_size(500, 500); // This one fits the image. QPolygonF const smaller_shape(createShape(image_size, 230)); // This one doesn't fit the image and will be clipped. QPolygonF const 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) { QSize const image_size(500, 500); QPolygonF const shape(QRectF(QPointF(0, 0), image_size)); QPolygonF const 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)); } BOOST_AUTO_TEST_SUITE_END(); } // namespace tests } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/tests/TestRastLineFinder.cpp000066400000000000000000000047051271170121200250120ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "RastLineFinder.h" #include #include #include #include #include #include namespace imageproc { namespace tests { BOOST_AUTO_TEST_SUITE(RastLineFinderTestSuite); static bool matchSupportPoints(std::vector const& idxs1, std::set const& 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.push_back(QPointF(-100, -100)); pts.push_back(QPointF(0, 0)); pts.push_back(QPointF(100, 100)); pts.push_back(QPointF(200, 200)); pts.push_back(QPointF(0, 100)); pts.push_back(QPointF(100, 0)); pts.push_back(QPointF(-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(); } // namespace tests } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/tests/TestRasterOp.cpp000066400000000000000000000127631271170121200237030ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "RasterOp.h" #include "BinaryImage.h" #include "Utils.h" #include #ifndef Q_MOC_RUN #include #endif #include #include #include #include namespace imageproc { namespace tests { using namespace utils; BOOST_AUTO_TEST_SUITE(RasterOpTestSuite); template static bool check_subimage_rop( QImage const& dst, QRect const& dst_rect, QImage const& src, QPoint const& src_pt) { BinaryImage dst_bi(dst); BinaryImage const 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)); QRect const src_rect(src_pt, dst_rect.size()); BinaryImage const 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 int const 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 int const 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 int const 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)); BinaryImage const mask_img(makeBinaryImage(mask, 9, 8)); typedef RopAnd Rop; rasterOp(img, img.rect(), mask_img, QPoint(0, 0)); BOOST_CHECK(img == makeBinaryImage(out, 9, 8)); } namespace { class Tester1 { public: Tester1(); bool testFullImage() const; bool testSubImage(QRect const& dst_rect, QPoint const& src_pt) const; private: typedef RopXor Rop; BinaryImage m_src; BinaryImage m_dstBefore; BinaryImage m_dstAfter; }; Tester1::Tester1() { int const w = 400; int const h = 300; std::vector src(w * h); for (size_t i = 0; i < src.size(); ++i) { src[i] = rand() & 1; } std::vector dst(w * h); for (size_t i = 0; i < dst.size(); ++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)); return dst == m_dstAfter; } bool Tester1::testSubImage(QRect const& dst_rect, QPoint const& src_pt) const { QImage const dst_before(m_dstBefore.toQImage()); QImage dst(dst_before); QImage const src(m_src.toQImage()); if (!check_subimage_rop(dst, dst_rect, src, src_pt)) { return false; } return surroundingsIntact(dst, dst_before, dst_rect); } } // anonymous 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))); } namespace { class Tester2 { public: Tester2(); bool testBlockMove(QRect const& rect, int dx, int dy); private: BinaryImage m_image; }; Tester2::Tester2() { m_image = randomBinaryImage(400, 300); } bool Tester2::testBlockMove(QRect const& rect, int const dx, int const dy) { BinaryImage dst(m_image); QRect const 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); } } // anonymous 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)); } BOOST_AUTO_TEST_SUITE_END(); } // namespace tests } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/tests/TestReduceThreshold.cpp000066400000000000000000000062761271170121200252320ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ReduceThreshold.h" #include "BinaryImage.h" #include "Utils.h" #include #ifndef Q_MOC_RUN #include #endif namespace imageproc { namespace tests { using namespace utils; BOOST_AUTO_TEST_SUITE(ReduceThresholdTestSuite); BOOST_AUTO_TEST_CASE(test_null_image) { BOOST_CHECK(ReduceThreshold(BinaryImage())(2).image().isNull()); } BOOST_AUTO_TEST_CASE(test_small_image) { int const 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, 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 }; int const out1[] = { 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; int const out2[] = { 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; int const out3[] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1 }; int const out4[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1 }; BinaryImage const 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_AUTO_TEST_CASE(test_lines) { static int const inp[] = { 0, 0, 0, 1, 1, 0, 1, 1, 0 }; static int const out1[] = { 0, 1, 1, 1 }; static int const out2[] = { 0, 1, 1, 1 }; static int const out3[] = { 0, 0, 0, 1 }; static int const out4[] = { 0, 0, 0, 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)); 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_AUTO_TEST_SUITE_END(); } // namespace tests } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/tests/TestSEDM.cpp000066400000000000000000000047671271170121200227010ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "SEDM.h" #include "BinaryImage.h" #include "BWColor.h" #include "Utils.h" #include #include #ifndef Q_MOC_RUN #include #endif #include namespace imageproc { namespace tests { using namespace utils; BOOST_AUTO_TEST_SUITE(SEDMTestSuite); bool verifySEDM(SEDM const& sedm, uint32_t const* control) { uint32_t const* 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; } void dumpMatrix(uint32_t const* data, QSize size) { int const width = size.width(); int const 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 int const 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 uint32_t const 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 }; BinaryImage const img(makeBinaryImage(inp, 9, 9)); SEDM const sedm(img, SEDM::DIST_TO_WHITE, SEDM::DIST_TO_NO_BORDERS); BOOST_CHECK(verifySEDM(sedm, out)); } BOOST_AUTO_TEST_SUITE_END(); } // namespace tests } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/tests/TestScale.cpp000066400000000000000000000054001271170121200231610ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Scale.h" #include "GrayImage.h" #include "Utils.h" #include #include #ifndef Q_MOC_RUN #include #endif #include #include #include namespace imageproc { namespace tests { using namespace utils; BOOST_AUTO_TEST_SUITE(ScaleTestSuite); BOOST_AUTO_TEST_CASE(test_null_image) { GrayImage const null_img; BOOST_CHECK(scaleToGray(null_img, QSize(1, 1)).isNull()); } static bool fuzzyCompare(QImage const& img1, QImage const& img2) { BOOST_REQUIRE(img1.size() == img2.size()); int const width = img1.width(); int const height = img1.height(); uint8_t const* line1 = img1.bits(); uint8_t const* line2 = img2.bits(); int const line1_bpl = img1.bytesPerLine(); int const line2_bpl = img2.bytesPerLine(); for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { if (abs(int(line1[x]) - int(line2[x])) > 1) { return false; } } line1 += line1_bpl; line2 += line2_bpl; } return true; } static bool checkScale(GrayImage const& img, QSize const& new_size) { GrayImage const scaled1(scaleToGray(img, new_size)); GrayImage const scaled2(img.toQImage().scaled( new_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation )); 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] = rand() % 256; } 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(); } // namespace tests } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/tests/TestSeedFill.cpp000066400000000000000000000131161271170121200236240ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License 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 #ifndef Q_MOC_RUN #include #endif namespace imageproc { namespace tests { 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 }; 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; BinaryImage const seed(makeBinaryImage(seed_data, 70, 2)); BinaryImage const 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 }; 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; BinaryImage const seed(makeBinaryImage(seed_data, 70, 2)); BinaryImage const mask(makeBinaryImage(mask_data, 70, 2)); BOOST_CHECK(seedFill(seed, mask, CONN8) == BinaryImage(70, 2, WHITE)); } BOOST_AUTO_TEST_CASE(test_regression_3) { static int const 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 int const 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 int const 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 }; BinaryImage const seed(makeBinaryImage(seed_data, 5, 5)); BinaryImage const mask(makeBinaryImage(mask_data, 5, 5)); BinaryImage const fill(makeBinaryImage(fill_data, 5, 5)); BOOST_REQUIRE(seedFill(seed, mask, CONN8) == fill); } BOOST_AUTO_TEST_CASE(test_regression_4) { static int const 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 int const 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 int const 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 }; BinaryImage const seed(makeBinaryImage(seed_data, 5, 5)); BinaryImage const mask(makeBinaryImage(mask_data, 5, 5)); BinaryImage const 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) { GrayImage const seed(randomGrayImage(5, 5)); GrayImage const mask(randomGrayImage(5, 5)); GrayImage const fill_new(seedFillGray(seed, mask, CONN4)); GrayImage const 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) { GrayImage const seed(randomGrayImage(5, 5)); GrayImage const mask(randomGrayImage(5, 5)); GrayImage const fill_new(seedFillGray(seed, mask, CONN8)); GrayImage const 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) { BinaryImage const bin_seed(randomBinaryImage(5, 5)); BinaryImage const bin_mask(randomBinaryImage(5, 5)); GrayImage const gray_seed(toGrayscale(bin_seed.toQImage())); GrayImage const gray_mask(toGrayscale(bin_mask.toQImage())); BinaryImage const fill_bin4(seedFill(bin_seed, bin_mask, CONN4)); BinaryImage const fill_bin8(seedFill(bin_seed, bin_mask, CONN8)); GrayImage const fill_gray4(seedFillGray(gray_seed, gray_mask, CONN4)); GrayImage const 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(); } // namespace tests } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/tests/TestShear.cpp000066400000000000000000000053271271170121200232040ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Shear.h" #include "BinaryImage.h" #include "BWColor.h" #include "Utils.h" #include #ifndef Q_MOC_RUN #include #endif namespace imageproc { namespace tests { using namespace utils; BOOST_AUTO_TEST_SUITE(ShearTestSuite); BOOST_AUTO_TEST_CASE(test_small_image) { static int const 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 int const 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 int const 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 }; BinaryImage const img(makeBinaryImage(inp, 9, 9)); BinaryImage const h_out_img(makeBinaryImage(h_out, 9, 9)); BinaryImage const v_out_img(makeBinaryImage(v_out, 9, 9)); BinaryImage const h_shear = hShear(img, -1.0, 0.5 * img.height(), WHITE); BOOST_REQUIRE(h_shear == h_out_img); BinaryImage const 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 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(); } // namespace tests } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/tests/TestSkewFinder.cpp000066400000000000000000000053571271170121200242060ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "SkewFinder.h" #include "BinaryImage.h" #include #include #include #include #include #include #ifndef Q_MOC_RUN #include #endif #include #include 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); 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; Skew const skew(skew_finder.findSkew(BinaryImage(image))); BOOST_REQUIRE(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); int const num_dots = image.width() * image.height() / 5; for (int i = 0; i < num_dots; ++i) { int const x = rand() % image.width(); int const y = rand() % image.height(); image.setPixel(x, y, 0); } SkewFinder skew_finder; skew_finder.setCoarseReduction(0); skew_finder.setFineReduction(0); Skew const skew(skew_finder.findSkew(BinaryImage(image))); BOOST_CHECK(skew.confidence() < Skew::GOOD_CONFIDENCE); } BOOST_AUTO_TEST_SUITE_END(); } // namespace tests } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/tests/TestSlicedHistogram.cpp000066400000000000000000000060131271170121200252140ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "SlicedHistogram.h" #include "BinaryImage.h" #include "Utils.h" #include #include #include #ifndef Q_MOC_RUN #include #endif namespace imageproc { namespace tests { using namespace utils; static bool checkHistogram( SlicedHistogram const& hist, int const* data_begin, int const* 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; } } return true; } BOOST_AUTO_TEST_SUITE(SlicedHistogramTestSuite); BOOST_AUTO_TEST_CASE(test_null_image) { BinaryImage const null_img; 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); } BOOST_AUTO_TEST_CASE(test_exceeding_area) { BinaryImage const img(1, 1); QRect const area(0, 0, 1, 2); BOOST_CHECK_THROW( SlicedHistogram(img, area, SlicedHistogram::ROWS), std::invalid_argument ); } BOOST_AUTO_TEST_CASE(test_small_image) { static int const 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 int const hor_counts[] = { 0, 1, 2, 3, 9, 2, 6, 3, 1 }; static int const ver_counts[] = { 2, 2, 4, 4, 5, 3, 2, 3, 2 }; BinaryImage const img(makeBinaryImage(inp, 9, 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)); 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)); } BOOST_AUTO_TEST_SUITE_END(); } // namespace tests } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/tests/TestTransform.cpp000066400000000000000000000037531271170121200241160ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Transform.h" #include "Grayscale.h" #include "Utils.h" #include #include #ifndef Q_MOC_RUN #include #endif #include #include #include namespace imageproc { namespace tests { using namespace utils; BOOST_AUTO_TEST_SUITE(TransformTestSuite); BOOST_AUTO_TEST_CASE(test_null_image) { QImage const null_img; QTransform const null_xform; QRect const unit_rect(0, 0, 1, 1); QColor const bgcolor(0xff, 0xff, 0xff); OutsidePixels const 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] = rand() % 256; } line += img.stride(); } QColor const bgcolor(0xff, 0xff, 0xff); OutsidePixels const outside_pixels(OutsidePixels::assumeColor(bgcolor)); QTransform const null_xform; BOOST_CHECK(transformToGray(img, null_xform, img.rect(), outside_pixels) == img); } BOOST_AUTO_TEST_SUITE_END(); } // namespace tests } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/tests/Utils.cpp000066400000000000000000000120461271170121200223760ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Utils.h" #include "BinaryImage.h" #include "Grayscale.h" #include #include #include #include #include #include namespace imageproc { namespace tests { namespace utils { BinaryImage randomBinaryImage(int const width, int const height) { BinaryImage image(width, height); uint32_t* pword = image.data(); uint32_t* const end = pword + image.height() * image.wordsPerLine(); for (; pword != end; ++pword) { uint32_t const w1 = rand() % (1 << 16); uint32_t const w2 = rand() % (1 << 16); *pword = (w1 << 16) | w2; } return image; } QImage randomMonoQImage(int const width, int const height) { QImage image(width, height, QImage::Format_Mono); image.setNumColors(2); image.setColor(0, 0xffffffff); image.setColor(1, 0xff000000); uint32_t* pword = (uint32_t*)image.bits(); assert(image.bytesPerLine() % 4 == 0); uint32_t* const end = pword + image.height() * (image.bytesPerLine() / 4); for (; pword != end; ++pword) { uint32_t const w1 = rand() % (1 << 16); uint32_t const 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); } } return img; } BinaryImage makeBinaryImage(int const* data, int const width, int const height) { return BinaryImage(makeMonoQImage(data, width, height)); } QImage makeMonoQImage(int const* data, int const width, int const height) { QImage img(width, height, QImage::Format_Mono); img.setNumColors(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; } QImage makeGrayImage(int const* data, int const width, int const 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]); } } return img; } void dumpBinaryImage(BinaryImage const& img, char const* name) { if (name) { std::cout << name << " = "; } if (img.isNull()) { std::cout << "NULL image" << std::endl; return; } int const width = img.width(); int const height = img.height(); uint32_t const* line = img.data(); int const 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(QImage const& img, char const* 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; } int const width = img.width(); int const 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(QImage const& img1, QImage const& img2, QRect const& rect) { assert(img1.size() == img2.size()); int const w = img1.width(); int const h = img1.height(); if (rect.left() != 0) { QRect const left_of(0, 0, rect.x(), h); if (img1.copy(left_of) != img2.copy(left_of)) { return false; } } if (rect.right() != img1.rect().right()) { QRect const 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) { QRect const top_of(0, 0, w, rect.y()); if (img1.copy(top_of) != img2.copy(top_of)) { return false; } } if (rect.bottom() != img1.rect().bottom()) { QRect const 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; } } // namespace utils } // namespace tests } // namespace imageproc scantailor-RELEASE_0_9_12_2/imageproc/tests/Utils.h000066400000000000000000000031171271170121200220420ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef IMAGEPROC_TESTS_UTILS_H_ #define IMAGEPROC_TESTS_UTILS_H_ class QImage; class QRect; namespace imageproc { class BinaryImage; namespace tests { namespace utils { BinaryImage randomBinaryImage(int width, int height); QImage randomMonoQImage(int width, int height); QImage randomGrayImage(int width, int height); BinaryImage makeBinaryImage(int const* data, int width, int height); QImage makeMonoQImage(int const* data, int width, int height); QImage makeGrayImage(int const* data, int width, int height); void dumpBinaryImage(BinaryImage const& img, char const* name = 0); void dumpGrayImage(QImage const& img, char const* name = 0); bool surroundingsIntact(QImage const& img1, QImage const& img2, QRect const& rect); } // namespace utils } // namespace uests } // namespace imageproc #endif scantailor-RELEASE_0_9_12_2/imageproc/tests/main.cpp000066400000000000000000000016251271170121200222230ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifdef HAVE_CONFIG_H #include #endif #define BOOST_AUTO_TEST_MAIN #include scantailor-RELEASE_0_9_12_2/interaction/000077500000000000000000000000001271170121200177765ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/interaction/CMakeLists.txt000066400000000000000000000011451271170121200225370ustar00rootroot00000000000000PROJECT(interaction) 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 ) SOURCE_GROUP(Sources FILES ${sources}) QT4_AUTOMOC(${sources}) ADD_LIBRARY(interaction STATIC ${sources}) TRANSLATION_SOURCES(scantailor ${sources})scantailor-RELEASE_0_9_12_2/interaction/DragHandler.cpp000066400000000000000000000050361271170121200226610ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "DragHandler.h" #include "ImageViewBase.h" #include #include #include #include DragHandler::DragHandler(ImageViewBase& image_view) : m_rImageView(image_view), m_interactionPermitter(&InteractionHandler::defaultInteractionPermitter) { init(); } DragHandler::DragHandler( ImageViewBase& image_view, boost::function const& explicit_interaction_permitter) : m_rImageView(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.") ); } bool DragHandler::isActive() const { return m_rImageView.interactionState().capturedBy(m_interaction); } void DragHandler::onMousePressEvent(QMouseEvent* event, InteractionState& interaction) { m_lastMousePos = event->pos(); 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(); } } void DragHandler::onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) { 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); } } } scantailor-RELEASE_0_9_12_2/interaction/DragHandler.h000066400000000000000000000034011271170121200223200ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DRAG_HANDLER_H_ #define DRAG_HANDLER_H_ #define BOOST_SIGNALS_NAMESPACE signal #include "InteractionHandler.h" #include "InteractionState.h" #include #include #ifndef Q_MOC_RUN #include #endif class ImageViewBase; class DragHandler : public InteractionHandler { Q_DECLARE_TR_FUNCTIONS(DragHandler) public: DragHandler(ImageViewBase& image_view); DragHandler(ImageViewBase& image_view, boost::function const& explicit_interaction_permitter); bool isActive() const; protected: virtual void onMousePressEvent(QMouseEvent* event, InteractionState& interaction); virtual void onMouseReleaseEvent(QMouseEvent* event, InteractionState& interaction); virtual void onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction); private: void init(); ImageViewBase& m_rImageView; InteractionState::Captor m_interaction; QPoint m_lastMousePos; boost::function m_interactionPermitter; }; #endif scantailor-RELEASE_0_9_12_2/interaction/DragWatcher.cpp000066400000000000000000000041371271170121200227020ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "DragWatcher.h" #include "DragHandler.h" #include #include #include DragWatcher::DragWatcher(DragHandler& drag_handler) : m_rDragHandler(drag_handler), m_dragMaxSqDist(0), m_dragInProgress(false) { } bool DragWatcher::haveSignificantDrag() const { if (!m_dragInProgress) { return false; } QDateTime const now(QDateTime::currentDateTime()); qint64 msec_passed = m_dragStartTime.time().msecsTo(now.time()); if (msec_passed < 0) { msec_passed += 60*60*24; } double const dist_score = sqrt((double)m_dragMaxSqDist) / 12.0; double const time_score = msec_passed / 500.0; return dist_score + time_score >= 1.0; } void DragWatcher::onMousePressEvent(QMouseEvent* event, InteractionState&) { updateState(event->pos()); } void DragWatcher::onMouseMoveEvent(QMouseEvent* event, InteractionState&) { updateState(event->pos()); } void DragWatcher::updateState(QPoint const mouse_pos) { if (m_rDragHandler.isActive()) { if (!m_dragInProgress) { m_dragStartTime = QDateTime::currentDateTime(); m_dragStartPos = mouse_pos; m_dragMaxSqDist = 0; } else { QPoint const delta(mouse_pos - m_dragStartPos); int const sqdist = delta.x() * delta.x() + delta.y() * delta.y(); if (sqdist > m_dragMaxSqDist) { m_dragMaxSqDist = sqdist; } } m_dragInProgress = true; } else { m_dragInProgress = false; } } scantailor-RELEASE_0_9_12_2/interaction/DragWatcher.h000066400000000000000000000033131271170121200223420ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DRAG_WATCHER_H_ #define DRAG_WATCHER_H_ #include "InteractionHandler.h" #include #include class DragHandler; class InteractionState; class QMouseEvent; /** * Objects of this class are intended to be used as a descendants followers * of DragHandler objects. The idea is to collect some statistics about * an ongoing drag and decide if the drag was significant or not, in which * case we could perform some other operation on mouse release. */ class DragWatcher : public InteractionHandler { public: DragWatcher(DragHandler& drag_handler); bool haveSignificantDrag() const; protected: virtual void onMousePressEvent(QMouseEvent* event, InteractionState& interaction); virtual void onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction); private: void updateState(QPoint mouse_pos); DragHandler& m_rDragHandler; QDateTime m_dragStartTime; QPoint m_dragStartPos; int m_dragMaxSqDist; bool m_dragInProgress; }; #endif scantailor-RELEASE_0_9_12_2/interaction/DraggableLineSegment.cpp000066400000000000000000000030061271170121200245040ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "DraggableLineSegment.h" #include "Proximity.h" #include "ImageViewBase.h" #include #include #include #include DraggableLineSegment::DraggableLineSegment() : m_proximityPriority(0) { } int DraggableLineSegment::proximityPriority() const { return m_proximityPriority; } Proximity DraggableLineSegment::proximity(QPointF const& mouse_pos) { return Proximity::pointAndLineSegment(mouse_pos, lineSegmentPosition()); } void DraggableLineSegment::dragInitiated(QPointF const& mouse_pos) { m_initialMousePos = mouse_pos; m_initialLinePos = lineSegmentPosition(); } void DraggableLineSegment::dragContinuation(QPointF const& mouse_pos) { lineSegmentMoveRequest(m_initialLinePos.translated(mouse_pos - m_initialMousePos)); } scantailor-RELEASE_0_9_12_2/interaction/DraggableLineSegment.h000066400000000000000000000040261271170121200241540ustar00rootroot00000000000000 /* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DRAGGABLE_LINE_SEGMENT_H_ #define DRAGGABLE_LINE_SEGMENT_H_ #include "DraggableObject.h" #include #include #ifndef Q_MOC_RUN #include #endif class ObjectDragHandler; class DraggableLineSegment : public DraggableObject { public: typedef boost::function< QLineF () > PositionCallback; typedef boost::function< void (QLineF const& line) > MoveRequestCallback; DraggableLineSegment(); void setProximityPriority(int priority) { m_proximityPriority = priority; } virtual int proximityPriority() const; virtual Proximity proximity(QPointF const& mouse_pos); virtual void dragInitiated(QPointF const& mouse_pos); virtual void dragContinuation(QPointF const& mouse_pos); void setPositionCallback(PositionCallback const& callback) { m_positionCallback = callback; } void setMoveRequestCallback(MoveRequestCallback const& callback) { m_moveRequestCallback = callback; } protected: virtual QLineF lineSegmentPosition() const { return m_positionCallback(); } virtual void lineSegmentMoveRequest(QLineF const& line) { m_moveRequestCallback(line); } private: PositionCallback m_positionCallback; MoveRequestCallback m_moveRequestCallback; QPointF m_initialMousePos; QLineF m_initialLinePos; int m_proximityPriority; }; #endif scantailor-RELEASE_0_9_12_2/interaction/DraggableObject.h000066400000000000000000000117241271170121200231530ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DRAGGABLE_OBJECT_H_ #define DRAGGABLE_OBJECT_H_ #include "InteractionState.h" #include "Proximity.h" #include class ObjectDragHandler; class QPoint; class QPointF; class QPainter; class DraggableObject { public: typedef boost::function< void (QPainter& painter, InteractionState const& interaction) > PaintCallback; typedef boost::function< Proximity (InteractionState const& interaction) > ProximityThresholdCallback; typedef boost::function< int () > ProximityPriorityCallback; typedef boost::function< Proximity (QPointF const& mouse_pos) > ProximityCallback; typedef boost::function< void (QPointF const& mouse_pos) > DragInitiatedCallback; typedef boost::function< void (QPointF const& mouse_pos) > DragContinuationCallback; typedef boost::function< void (QPointF const& mouse_pos) > 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() {} virtual void paint(QPainter& painter, InteractionState const& interaction) { m_paintCallback(painter, interaction); } void setPaintCallback(PaintCallback const& 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(InteractionState const& interaction) const { return m_proximityThresholdCallback(interaction); } void setProximityThresholdCallback(ProximityThresholdCallback const& 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(ProximityPriorityCallback const& callback) { m_proximityPriorityCallback = callback; } /** * \return The proximity from the mouse position in widget coordinates to * any draggable part of the object. */ virtual Proximity proximity(QPointF const& widget_mouse_pos) { return m_proximityCallback(widget_mouse_pos); } void setProximityCallback(ProximityCallback const& callback) { m_proximityCallback = callback; } /** * \brief Called when dragging is initiated, that is when the mouse button is pressed. */ virtual void dragInitiated(QPointF const& mouse_pos) { m_dragInitiatedCallback(mouse_pos); } void setDragInitiatedCallback(DragInitiatedCallback const& callback) { m_dragInitiatedCallback = callback; } /** * \brief Handles a request to move to a particular position in widget coordinates. */ virtual void dragContinuation(QPointF const& mouse_pos) { m_dragContinuationCallback(mouse_pos); } void setDragContinuationCallback(DragInitiatedCallback const& callback) { m_dragContinuationCallback = callback; } /** * \brief Called when dragging is finished, that is when the mouse button is released. */ virtual void dragFinished(QPointF const& mouse_pos) { m_dragFinishedCallback(mouse_pos); } void setDragFinishedCallback(DragFinishedCallback const& callback) { m_dragFinishedCallback = callback; } private: static void defaultPaint(QPainter&, InteractionState const&) {} static Proximity defaultProximityThreshold(InteractionState const& interaction) { return interaction.proximityThreshold(); } static int defaultProximityPriority() { return 0; } static void defaultDragFinished(QPointF const&) {} PaintCallback m_paintCallback; ProximityThresholdCallback m_proximityThresholdCallback; ProximityPriorityCallback m_proximityPriorityCallback; ProximityCallback m_proximityCallback; DragInitiatedCallback m_dragInitiatedCallback; DragContinuationCallback m_dragContinuationCallback; DragFinishedCallback m_dragFinishedCallback; }; #endif scantailor-RELEASE_0_9_12_2/interaction/DraggablePoint.cpp000066400000000000000000000030521271170121200233640ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "DraggablePoint.h" #include "Proximity.h" #include "ImageViewBase.h" DraggablePoint::DraggablePoint() : m_hitAreaRadius(), m_proximityPriority(1) { } Proximity DraggablePoint::proximityThreshold(InteractionState const& state) const { if (m_hitAreaRadius == 0.0) { return state.proximityThreshold(); } else { return Proximity::fromDist(m_hitAreaRadius); } } int DraggablePoint::proximityPriority() const { return m_proximityPriority; } Proximity DraggablePoint::proximity(QPointF const& mouse_pos) { return Proximity(pointPosition(), mouse_pos); } void DraggablePoint::dragInitiated(QPointF const& mouse_pos) { m_pointRelativeToMouse = pointPosition() - mouse_pos; } void DraggablePoint::dragContinuation(QPointF const& mouse_pos) { pointMoveRequest(mouse_pos + m_pointRelativeToMouse); } scantailor-RELEASE_0_9_12_2/interaction/DraggablePoint.h000066400000000000000000000044461271170121200230410ustar00rootroot00000000000000 /* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DRAGGABLE_POINT_H_ #define DRAGGABLE_POINT_H_ #include "DraggableObject.h" #include #ifndef Q_MOC_RUN #include #endif class DraggablePoint : public DraggableObject { public: typedef boost::function< QPointF () > PositionCallback; typedef boost::function< void (QPointF const&) > MoveRequestCallback; 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; } void setHitRadius(double radius) { m_hitAreaRadius = radius; } virtual Proximity proximityThreshold(InteractionState const& interaction) const; void setProximityPriority(int priority) { m_proximityPriority = priority; } virtual int proximityPriority() const; virtual Proximity proximity(QPointF const& mouse_pos); virtual void dragInitiated(QPointF const& mouse_pos); virtual void dragContinuation(QPointF const& mouse_pos); void setPositionCallback(PositionCallback const& callback) { m_positionCallback = callback; } void setMoveRequestCallback(MoveRequestCallback const& callback) { m_moveRequestCallback = callback; } protected: virtual QPointF pointPosition() const { return m_positionCallback(); } virtual void pointMoveRequest(QPointF const& widget_pos) { m_moveRequestCallback(widget_pos); } private: PositionCallback m_positionCallback; MoveRequestCallback m_moveRequestCallback; QPointF m_pointRelativeToMouse; double m_hitAreaRadius; int m_proximityPriority; }; #endif scantailor-RELEASE_0_9_12_2/interaction/InteractionHandler.cpp000066400000000000000000000175401271170121200242660ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "InteractionHandler.h" #include "InteractionState.h" #include "NonCopyable.h" #include #include #include #include #include #ifndef Q_MOC_RUn #include #include #include #endif #include #define DISPATCH(list, call) { \ HandlerList::iterator it(list->begin()); \ HandlerList::iterator const end(list->end()); \ while (it != end) { \ (it++)->call; \ } \ } #define RETURN_IF_ACCEPTED(event) { \ if (event->isAccepted()) { \ return; \ } \ } namespace { class ScopedClearAcceptance { DECLARE_NON_COPYABLE(ScopedClearAcceptance) public: ScopedClearAcceptance(QEvent* event); ~ScopedClearAcceptance(); private: QEvent* m_pEvent; bool m_wasAccepted; }; ScopedClearAcceptance::ScopedClearAcceptance(QEvent* event) : m_pEvent(event), m_wasAccepted(event->isAccepted()) { m_pEvent->setAccepted(false); } ScopedClearAcceptance::~ScopedClearAcceptance() { if (m_wasAccepted) { m_pEvent->setAccepted(true); } } } // anonymous namespace InteractionHandler::InteractionHandler() : m_ptrPreceeders(new HandlerList), m_ptrFollowers(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)); } void InteractionHandler::paint( QPainter& painter, InteractionState const& interaction) { // Keep them alive in case this object gets destroyed. IntrusivePtr preceeders(m_ptrPreceeders); IntrusivePtr followers(m_ptrFollowers); DISPATCH(preceeders, paint(painter, interaction)); painter.save(); onPaint(painter, interaction); painter.restore(); DISPATCH(followers, paint(painter, interaction)); } void InteractionHandler::proximityUpdate( QPointF const& screen_mouse_pos, InteractionState& interaction) { // Keep them alive in case this object gets destroyed. IntrusivePtr preceeders(m_ptrPreceeders); IntrusivePtr followers(m_ptrFollowers); 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); // Keep them alive in case this object gets destroyed. IntrusivePtr preceeders(m_ptrPreceeders); IntrusivePtr followers(m_ptrFollowers); 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. IntrusivePtr preceeders(m_ptrPreceeders); IntrusivePtr followers(m_ptrFollowers); 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); // Keep them alive in case this object gets destroyed. IntrusivePtr preceeders(m_ptrPreceeders); IntrusivePtr followers(m_ptrFollowers); 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. IntrusivePtr preceeders(m_ptrPreceeders); IntrusivePtr followers(m_ptrFollowers); DISPATCH(preceeders, mouseReleaseEvent(event, interaction)); RETURN_IF_ACCEPTED(event); onMouseReleaseEvent(event, interaction); ScopedClearAcceptance guard(event); DISPATCH(followers, mouseReleaseEvent(event, interaction)); } void InteractionHandler::mouseMoveEvent(QMouseEvent* event, InteractionState& interaction) { RETURN_IF_ACCEPTED(event); // Keep them alive in case this object gets destroyed. IntrusivePtr preceeders(m_ptrPreceeders); IntrusivePtr followers(m_ptrFollowers); 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); // Keep them alive in case this object gets destroyed. IntrusivePtr preceeders(m_ptrPreceeders); IntrusivePtr followers(m_ptrFollowers); 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. IntrusivePtr preceeders(m_ptrPreceeders); IntrusivePtr followers(m_ptrFollowers); 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); } void InteractionHandler::makePeerFollower(InteractionHandler& handler) { using namespace boost::intrusive; handler.unlink(); HandlerList::node_algorithms::link_after(this, &handler); } void InteractionHandler::makeFirstPreceeder(InteractionHandler& handler) { handler.unlink(); m_ptrPreceeders->push_front(handler); } void InteractionHandler::makeLastPreceeder(InteractionHandler& handler) { handler.unlink(); m_ptrPreceeders->push_back(handler); } void InteractionHandler::makeFirstFollower(InteractionHandler& handler) { handler.unlink(); m_ptrFollowers->push_front(handler); } void InteractionHandler::makeLastFollower(InteractionHandler& handler) { handler.unlink(); m_ptrFollowers->push_back(handler); } bool InteractionHandler::defaultInteractionPermitter(InteractionState const& interaction) { return !interaction.captured(); } scantailor-RELEASE_0_9_12_2/interaction/InteractionHandler.h000066400000000000000000000066441271170121200237360ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef INTERACTION_HANDLER_H_ #define INTERACTION_HANDLER_H_ #include "NonCopyable.h" #include "RefCountable.h" #include "IntrusivePtr.h" #include class InteractionState; class QPainter; class QKeyEvent; class QMouseEvent; class QWheelEvent; class QContextMenuEvent; class QPointF; class InteractionHandler : public boost::intrusive::list_base_hook< boost::intrusive::link_mode > { DECLARE_NON_COPYABLE(InteractionHandler) public: InteractionHandler(); virtual ~InteractionHandler(); void paint(QPainter& painter, InteractionState const& interaction); void proximityUpdate(QPointF const& screen_mouse_pos, InteractionState& interaction); void keyPressEvent(QKeyEvent* event, InteractionState& interaction); void keyReleaseEvent(QKeyEvent* event, InteractionState& interaction); void mousePressEvent(QMouseEvent* event, InteractionState& interaction); void mouseReleaseEvent(QMouseEvent* event, InteractionState& interaction); void mouseMoveEvent(QMouseEvent* event, InteractionState& interaction); void wheelEvent(QWheelEvent* event, InteractionState& interaction); void contextMenuEvent(QContextMenuEvent* event, InteractionState& interaction); void makePeerPreceeder(InteractionHandler& handler); void makePeerFollower(InteractionHandler& handler); void makeFirstPreceeder(InteractionHandler& handler); void makeLastPreceeder(InteractionHandler& handler); void makeFirstFollower(InteractionHandler& handler); void makeLastFollower(InteractionHandler& handler); protected: virtual void onPaint( QPainter& painter, InteractionState const& interaction) {} virtual void onProximityUpdate( QPointF const& screen_mouse_pos, InteractionState& interaction) {} virtual void onKeyPressEvent( QKeyEvent* event, InteractionState& interaction) {} virtual void onKeyReleaseEvent( QKeyEvent* event, InteractionState& interaction) {} virtual void onMousePressEvent( QMouseEvent* event, InteractionState& interaction) {} virtual void onMouseReleaseEvent( QMouseEvent* event, InteractionState& interaction) {} virtual void onMouseMoveEvent( QMouseEvent* event, InteractionState& interaction) {} virtual void onWheelEvent( QWheelEvent* event, InteractionState& interaction) {} virtual void onContextMenuEvent( QContextMenuEvent* event, InteractionState& interaction) {} static bool defaultInteractionPermitter(InteractionState const& interaction); private: class HandlerList : public RefCountable, public boost::intrusive::list< InteractionHandler, boost::intrusive::constant_time_size > { }; IntrusivePtr m_ptrPreceeders; IntrusivePtr m_ptrFollowers; }; #endif scantailor-RELEASE_0_9_12_2/interaction/InteractionState.cpp000066400000000000000000000057641271170121200237760ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "InteractionState.h" #include InteractionState::Captor& InteractionState::Captor::operator=(Captor& other) { swap_nodes(other); other.unlink(); return *this; } InteractionState::Captor& InteractionState::Captor::operator=(CopyHelper other) { return (*this = *other.captor); } InteractionState::InteractionState() : 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); } bool InteractionState::capturedBy(Captor const& captor) const { return !m_captorList.empty() && &m_captorList.back() == &captor; } void InteractionState::resetProximity() { m_proximityLeader.clear(); m_bestProximity = Proximity(); m_bestProximityPriority = std::numeric_limits::min(); } void InteractionState::updateProximity( Captor& captor, Proximity const& 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; } } } bool InteractionState::proximityLeader(Captor const& captor) const { return !m_proximityLeader.empty() && &m_proximityLeader.front() == &captor; } bool InteractionState::betterProximity(Proximity const& proximity, int const priority) const { if (priority != m_bestProximityPriority) { return priority > m_bestProximityPriority; } 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(); } } 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; } } scantailor-RELEASE_0_9_12_2/interaction/InteractionState.h000066400000000000000000000074041271170121200234340ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef INTERACTION_STATE_H_ #define INTERACTION_STATE_H_ #include "NonCopyable.h" #include "Proximity.h" #ifndef Q_MOC_RUN #include #endif #include #include class Proximity; class InteractionState { DECLARE_NON_COPYABLE(InteractionState) public: class Captor : public boost::intrusive::list_base_hook< boost::intrusive::link_mode > { friend class InteractionState; private: struct CopyHelper { Captor* captor; CopyHelper(Captor* cap) : captor(cap) {} }; public: Captor() {} Captor(Captor& other) { swap_nodes(other); } Captor(CopyHelper other) { swap_nodes(*other.captor); } Captor& operator=(Captor& other); Captor& operator=(CopyHelper other); operator CopyHelper() { return CopyHelper(this); } void release() { unlink(); } QCursor const& proximityCursor() const { return m_proximityCursor; } void setProximityCursor(QCursor const& cursor) { m_proximityCursor = cursor; } QCursor const& interactionCursor() const { return m_interactionCursor; } void setInteractionCursor(QCursor const& cursor) { m_interactionCursor = cursor; } QString const& proximityStatusTip() const { return m_proximityStatusTip; } void setProximityStatusTip(QString const& tip) { m_proximityStatusTip = tip; } QString const& interactionStatusTip() const { return m_interactionStatusTip; } void setInteractionStatusTip(QString const& tip) { m_interactionStatusTip = tip; } QString const& interactionOrProximityStatusTip() const { return m_interactionStatusTip.isNull() ? m_proximityStatusTip : m_interactionStatusTip; } private: QCursor m_proximityCursor; QCursor m_interactionCursor; QString m_proximityStatusTip; QString m_interactionStatusTip; }; InteractionState(); void capture(Captor& captor); bool captured() const { return !m_captorList.empty(); } bool capturedBy(Captor const& captor) const; void resetProximity(); void updateProximity( Captor& captor, Proximity const& proximity, int priority = 0, Proximity proximity_threshold = Proximity()); bool proximityLeader(Captor const& captor) const; Proximity const& proximityThreshold() const { return m_proximityThreshold; } QCursor cursor() const; QString statusTip() const; QString const& defaultStatusTip() const { return m_defaultStatusTip; } void setDefaultStatusTip(QString const& status_tip) { m_defaultStatusTip = status_tip; } bool redrawRequested() const { return m_redrawRequested; } void setRedrawRequested(bool requested) { m_redrawRequested = requested; } private: typedef boost::intrusive::list< Captor, boost::intrusive::constant_time_size > CaptorList; /** * Returns true if the provided proximity is better than the stored one. */ bool betterProximity(Proximity const& 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; }; #endif scantailor-RELEASE_0_9_12_2/interaction/InteractiveXSpline.cpp000066400000000000000000000165711271170121200242740ustar00rootroot00000000000000 /* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "InteractiveXSpline.h" #include "Proximity.h" #include "VecNT.h" #include "MatrixCalc.h" #include #include #include #ifndef Q_MOC_RUN #include #endif struct InteractiveXSpline::NoOp { void operator()() const {} }; struct InteractiveXSpline::IdentTransform { QPointF operator()(QPointF const& 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.")); } void InteractiveXSpline::setSpline(XSpline const& spline) { int const 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) ); 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.")); } 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_modifiedCallback(); } void InteractiveXSpline::setStorageTransform( Transform const& from_storage, Transform const& to_storage) { m_fromStorage = from_storage; m_toStorage = to_storage; } void InteractiveXSpline::setModifiedCallback(ModifiedCallback const& callback) { m_modifiedCallback = callback; } void InteractiveXSpline::setDragFinishedCallback(DragFinishedCallback const& callback) { m_dragFinishedCallback = callback; } bool InteractiveXSpline::curveIsProximityLeader( InteractionState const& state, QPointF* pt, double* t) const { if (state.proximityLeader(m_curveProximity)) { if (pt) { *pt = m_curveProximityPointScreen; } if (t) { *t = m_curveProximityT; } return true; } return false; } void InteractiveXSpline::onProximityUpdate( QPointF const& screen_mouse_pos, InteractionState& interaction) { m_curveProximityPointStorage = m_spline.pointClosestTo( m_toStorage(screen_mouse_pos), &m_curveProximityT ); m_curveProximityPointScreen = m_fromStorage(m_curveProximityPointStorage); Proximity const 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; } } void InteractiveXSpline::onMousePressEvent( QMouseEvent* event, InteractionState& interaction) { if (interaction.captured()) { return; } if (interaction.proximityLeader(m_curveProximity)) { int const segment = int(m_curveProximityT * m_spline.numSegments()); int const pnt_idx = segment + 1; m_spline.insertControlPoint(pnt_idx, m_curveProximityPointStorage, 1); setSpline(m_spline); m_controlPoints[pnt_idx].handler.forceEnterDragState(interaction, event->pos()); event->accept(); 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: int const 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; } } break; } } QPointF InteractiveXSpline::controlPointPosition(int idx) const { return m_fromStorage(m_spline.controlPointPosition(idx)); } void InteractiveXSpline::controlPointMoveRequest(int idx, QPointF const& pos) { QPointF const storage_pt(m_toStorage(pos)); int const 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. int const origin_idx = idx == 0 ? num_control_points - 1 : 0; QPointF const origin(m_spline.controlPointPosition(origin_idx)); QPointF const old_pos(m_spline.controlPointPosition(idx)); if (Vec2d(old_pos - origin).squaredNorm() > 1.0) { // rotationAndScale() would throw an exception if old_pos == origin. Vec4d const 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); m_spline.moveControlPoint(i, pt + origin); } } else { // Move the endpoint and distribute midpoints uniformly. QLineF const line(origin, storage_pt); double const 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(); } void InteractiveXSpline::dragFinished() { m_dragFinishedCallback(); } Vec4d InteractiveXSpline::rotationAndScale(QPointF const& from, QPointF const& 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; } scantailor-RELEASE_0_9_12_2/interaction/InteractiveXSpline.h000066400000000000000000000065371271170121200237420ustar00rootroot00000000000000 /* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #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 #ifndef Q_MOC_RUN #include #include #endif #include class InteractiveXSpline : public InteractionHandler { Q_DECLARE_TR_FUNCTIONS(InteractiveXSpline) public: typedef boost::function Transform; typedef boost::function ModifiedCallback; typedef boost::function DragFinishedCallback; InteractiveXSpline(); void setSpline(XSpline const& spline); XSpline const& spline() const { return m_spline; } void setStorageTransform(Transform const& from_storage, Transform const& to_storage); void setModifiedCallback(ModifiedCallback const& callback); void setDragFinishedCallback(DragFinishedCallback const& 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( InteractionState const& state, QPointF* pt = 0, double* t = 0) const; protected: virtual void onProximityUpdate( QPointF const& screen_mouse_pos, InteractionState& interaction); virtual void onMouseMoveEvent( QMouseEvent* event, InteractionState& interaction); virtual void onMousePressEvent( QMouseEvent* event, InteractionState& interaction); virtual void onKeyPressEvent( QKeyEvent* event, InteractionState& interaction); private: struct NoOp; struct IdentTransform; struct ControlPoint { DraggablePoint point; ObjectDragHandler handler; ControlPoint() {} }; QPointF controlPointPosition(int idx) const; void controlPointMoveRequest(int idx, QPointF const& pos); void dragFinished(); static Vec4d rotationAndScale(QPointF const& from, QPointF const& 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; }; #endif scantailor-RELEASE_0_9_12_2/interaction/ObjectDragHandler.cpp000066400000000000000000000061621271170121200240110ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ObjectDragHandler.h" #include #include #include ObjectDragHandler::ObjectDragHandler(DraggableObject* obj) : m_pObj(obj) { setProximityCursor(Qt::OpenHandCursor); setInteractionCursor(Qt::ClosedHandCursor); } void ObjectDragHandler::setProximityCursor(QCursor const& cursor) { m_interaction.setProximityCursor(cursor); } void ObjectDragHandler::setInteractionCursor(QCursor const& cursor) { m_interaction.setInteractionCursor(cursor); } void ObjectDragHandler::setProximityStatusTip(QString const& tip) { m_interaction.setProximityStatusTip(tip); } void ObjectDragHandler::setInteractionStatusTip(QString const& tip) { m_interaction.setInteractionStatusTip(tip); } bool ObjectDragHandler::interactionInProgress( InteractionState const& interaction) const { return interaction.capturedBy(m_interaction); } bool ObjectDragHandler::proximityLeader(InteractionState const& interaction) const { 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); } void ObjectDragHandler::onPaint( QPainter& painter, InteractionState const& interaction) { m_pObj->paint(painter, interaction); } void ObjectDragHandler::onProximityUpdate( QPointF const& screen_mouse_pos, InteractionState& interaction) { interaction.updateProximity( m_interaction, m_pObj->proximity(screen_mouse_pos), m_pObj->proximityPriority(), m_pObj->proximityThreshold(interaction) ); } void ObjectDragHandler::onMousePressEvent( QMouseEvent* event, InteractionState& interaction) { if (interaction.captured() || event->button() != Qt::LeftButton) { return; } if (interaction.proximityLeader(m_interaction)) { interaction.capture(m_interaction); m_pObj->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()); } } void ObjectDragHandler::onMouseMoveEvent( QMouseEvent* event, InteractionState& interaction) { if (interaction.capturedBy(m_interaction)) { m_pObj->dragContinuation(QPointF(0.5, 0.5) + event->pos()); } } scantailor-RELEASE_0_9_12_2/interaction/ObjectDragHandler.h000066400000000000000000000042361271170121200234560ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef OBJECT_DRAG_HANDLER_H_ #define OBJECT_DRAG_HANDLER_H_ #include "NonCopyable.h" #include "InteractionHandler.h" #include "InteractionState.h" #include "DraggableObject.h" #include #include class QPainter; class QCursor; class QString; class ObjectDragHandler : public InteractionHandler { DECLARE_NON_COPYABLE(ObjectDragHandler) public: ObjectDragHandler(DraggableObject* obj = 0); void setObject(DraggableObject* obj) { m_pObj = obj; } void setProximityCursor(QCursor const& cursor); void setInteractionCursor(QCursor const& cursor); void setProximityStatusTip(QString const& tip); void setInteractionStatusTip(QString const& tip); bool interactionInProgress(InteractionState const& interaction) const; bool proximityLeader(InteractionState const& interaction) const; void forceEnterDragState(InteractionState& interaction, QPoint widget_mouse_pos); protected: virtual void onPaint( QPainter& painter, InteractionState const& interaction); virtual void onProximityUpdate( QPointF const& screen_mouse_pos, InteractionState& interaction); virtual void onMousePressEvent( QMouseEvent* event, InteractionState& interaction); virtual void onMouseReleaseEvent( QMouseEvent* event, InteractionState& interaction); virtual void onMouseMoveEvent( QMouseEvent* event, InteractionState& interaction); private: DraggableObject* m_pObj; InteractionState::Captor m_interaction; }; #endif scantailor-RELEASE_0_9_12_2/interaction/ZoomHandler.cpp000066400000000000000000000056651271170121200227400ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ZoomHandler.h" #include "InteractionState.h" #include "ImageViewBase.h" #include #include #include #include ZoomHandler::ZoomHandler(ImageViewBase& image_view) : m_rImageView(image_view), m_interactionPermitter(&InteractionHandler::defaultInteractionPermitter), m_focus(CURSOR) { } ZoomHandler::ZoomHandler( ImageViewBase& image_view, boost::function const& explicit_interaction_permitter) : m_rImageView(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". double const delta_clicks = event->delta() / 120; double const dist = -delta_clicks * 30; // 30px per "click" m_rImageView.moveTowardsIdealPosition(dist); return; } double const degrees = event->delta() / 8.0; zoom *= 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() } 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() } scantailor-RELEASE_0_9_12_2/interaction/ZoomHandler.h000066400000000000000000000033061271170121200223730ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef ZOOM_HANDLER_H_ #define ZOOM_HANDLER_H_ #include "InteractionHandler.h" #include "InteractionState.h" #include #include #ifndef Q_MOC_RUN #include #endif class ImageViewBase; class ZoomHandler : public InteractionHandler { Q_DECLARE_TR_FUNCTIONS(ZoomHandler) public: enum Focus { CENTER, CURSOR }; ZoomHandler(ImageViewBase& image_view); ZoomHandler(ImageViewBase& image_view, boost::function const& explicit_interaction_permitter); Focus focus() const { return m_focus; } void setFocus(Focus focus) { m_focus = focus; } protected: virtual void onWheelEvent(QWheelEvent* event, InteractionState& interaction); virtual void onKeyPressEvent(QKeyEvent* event, InteractionState& interaction); private: ImageViewBase& m_rImageView; boost::function m_interactionPermitter; InteractionState::Captor m_interaction; Focus m_focus; }; #endif scantailor-RELEASE_0_9_12_2/main-cli.cpp000066400000000000000000000035541271170121200176630ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich Copyright (C) 2011 Petr Kovar 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include "CommandLine.h" #include "ConsoleBatch.h" int main(int argc, char **argv) { QCoreApplication app(argc, argv); #ifdef _WIN32 // 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); if (cli.hasHelp() || cli.outputDirectory().isEmpty() || (cli.images().size()==0 && cli.projectFile().isEmpty())) { cli.printHelp(); return 0; } std::auto_ptr cbatch; try { if (!cli.projectFile().isEmpty()) { cbatch.reset(new ConsoleBatch(cli.projectFile())); } else { cbatch.reset(new ConsoleBatch(cli.images(), cli.outputDirectory(), cli.getLayoutDirection())); } cbatch->process(); } catch(std::exception const& e) { std::cerr << e.what() << std::endl; exit(1); } if (cli.hasOutputProject()) cbatch->saveProject(cli.outputProjectFile()); } scantailor-RELEASE_0_9_12_2/main.cpp000066400000000000000000000120041271170121200171040ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "config.h" #include "Application.h" #include "MainWindow.h" #include "PngMetadataLoader.h" #include "TiffMetadataLoader.h" #include "JpegMetadataLoader.h" #include #include #include #include #include #include #include #include #include #include #include "CommandLine.h" //#ifdef Q_WS_WIN //// Import static plugins //#Q_IMPORT_PLUGIN(qjpeg) //#endif #ifdef ENABLE_CRASH_REPORTER #include "google-breakpad/client/windows/handler/exception_handler.h" #include static wchar_t crash_reporter_path[MAX_PATH]; static bool getCrashReporterPath(wchar_t* buf, DWORD buflen) { DWORD i = GetModuleFileNameW(0, buf, buflen); if (i == buflen) { return false; } for (; buf[i] != L'\\'; --i) { if (i == 0) { return false; } } ++i; // Move to the character after the backslash. static wchar_t const crash_reporter_exe[] = L"CrashReporter.exe"; int const to_copy = sizeof(crash_reporter_exe)/sizeof(crash_reporter_exe[0]); for (int j = 0; j < to_copy; ++j, ++i) { if (i == buflen) { return false; } buf[i] = crash_reporter_exe[j]; } return true; } static bool crashCallback(wchar_t const* dump_path, wchar_t const* id, void* context, EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion, bool succeeded) { if (!succeeded) { return false; } static wchar_t command_line[1024] = L"CrashReporter.exe "; wchar_t* p = command_line; p = lstrcatW(p, L"\""); p = lstrcatW(p, dump_path); p = lstrcatW(p, L"\" "); p = lstrcatW(p, L"\""); p = lstrcatW(p, id); p = lstrcatW(p, L"\""); static PROCESS_INFORMATION pinfo; static STARTUPINFOW startupinfo = { sizeof(STARTUPINFOW), 0, 0, 0, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; if (CreateProcessW(crash_reporter_path, command_line, 0, 0, FALSE, CREATE_UNICODE_ENVIRONMENT|CREATE_NEW_CONSOLE, 0, 0, &startupinfo, &pinfo)) { return true; } // CraeateProcessW() failed. Maybe crash_reporter_path got corrupted? // Let's try to re-create it. getCrashReporterPath( crash_reporter_path, sizeof(crash_reporter_path)/sizeof(crash_reporter_path[0]) ); if (CreateProcessW(crash_reporter_path, command_line, 0, 0, FALSE, CREATE_UNICODE_ENVIRONMENT|CREATE_NEW_CONSOLE, 0, 0, &startupinfo, &pinfo)) { return true; } return false; } #endif // ENABLE_CRASH_REPORTER int main(int argc, char** argv) { #ifdef ENABLE_CRASH_REPORTER getCrashReporterPath( crash_reporter_path, sizeof(crash_reporter_path)/sizeof(crash_reporter_path[0]) ); google_breakpad::ExceptionHandler eh( QDir::tempPath().toStdWString().c_str(), 0, &crashCallback, 0, google_breakpad::ExceptionHandler::HANDLER_ALL ); #endif Application app(argc, argv); #ifdef _WIN32 // Get rid of all references to Qt's installation directory. app.setLibraryPaths(QStringList(app.applicationDirPath())); #endif // parse command line arguments CommandLine cli(app.arguments()); CommandLine::set(cli); if (cli.hasHelp()) { cli.printHelp(); return 0; } QString const translation("scantailor_"+QLocale::system().name()); QTranslator translator; // Try loading from the current directory. if (!translator.load(translation)) { // Now try loading from where it's supposed to be. QString path(QString::fromUtf8(TRANSLATIONS_DIR_ABS)); path += QChar('/'); path += translation; if (!translator.load(path)) { path = QString::fromUtf8(TRANSLATIONS_DIR_REL); path += QChar('/'); path += translation; translator.load(path); } } app.installTranslator(&translator); // This information is used by QSettings. app.setApplicationName("Scan Tailor"); app.setOrganizationName("Scan Tailor"); app.setOrganizationDomain("scantailor.sourceforge.net"); QSettings settings; PngMetadataLoader::registerMyself(); TiffMetadataLoader::registerMyself(); JpegMetadataLoader::registerMyself(); MainWindow* main_wnd = new MainWindow(); main_wnd->setAttribute(Qt::WA_DeleteOnClose); if (settings.value("mainWindow/maximized") == false) { main_wnd->show(); } else { main_wnd->showMaximized(); } if (!cli.projectFile().isEmpty()) { main_wnd->openProject(cli.projectFile()); } return app.exec(); } scantailor-RELEASE_0_9_12_2/math/000077500000000000000000000000001271170121200164105ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/math/ArcLengthMapper.cpp000066400000000000000000000154221271170121200221340ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ArcLengthMapper.h" #include #include #include 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; } ArcLengthMapper::ArcLengthMapper() : m_prevFX() { } void ArcLengthMapper::addSample(double x, double fx) { double arc_len = 0; if (!m_samples.empty()) { double const dx = x - m_samples.back().x; double const dy = fx - m_prevFX; assert(dx > 0); arc_len = m_samples.back().arcLen + sqrt(dx*dx + dy*dy); } m_samples.push_back(Sample(x, arc_len)); m_prevFX = fx; } double ArcLengthMapper::totalArcLength() const { 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; } assert(total_arc_len != 0); double const scale = total_arc_len / m_samples.back().arcLen; BOOST_FOREACH(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; } 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(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; int right_idx = m_samples.size() - 1; double left_arc_len = m_samples[left_idx].arcLen; while (left_idx + 1 < right_idx) { int const mid_idx = (left_idx + right_idx) >> 1; double const 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); } double ArcLengthMapper::xToArcLen(double x, Hint& hint) const { 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(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; int right_idx = m_samples.size() - 1; double left_x = m_samples[left_idx].x; while (left_idx + 1 < right_idx) { int const mid_idx = (left_idx + right_idx) >> 1; double const 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); } 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; } double const left_arc_len = m_samples[segment].arcLen; double const right_arc_len = m_samples[segment + 1].arcLen; 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; } double const left_x = m_samples[segment].x; double const right_x = m_samples[segment + 1].x; 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) double const x0 = m_samples[segment].x; double const a0 = m_samples[segment].arcLen; double const x1 = m_samples[segment + 1].x; double const a1 = m_samples[segment + 1].arcLen; double const 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) double const x0 = m_samples[segment].x; double const a0 = m_samples[segment].arcLen; double const x1 = m_samples[segment + 1].x; double const a1 = m_samples[segment + 1].arcLen; double const a = a0 + (a1 - a0) * (x - x0) / (x1 - x0); return a; } scantailor-RELEASE_0_9_12_2/math/ArcLengthMapper.h000066400000000000000000000060701271170121200216000ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef ARC_LENGTH_MAPPER_H_ #define ARC_LENGTH_MAPPER_H_ #include /** * \brief Maps from x to arclen(f(x)) and back. * * Suppose we have a discrete function where we only know its * values for a given set of arguments. This class provides a way * to calculate the both arc length corresponding to an arbitrary x, * and x corresponding to an arbitrary arc length. * We consider the arc length between two adjacent samples * to be monotonously increasing, that is we consider adjacent samples * 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; }; #endif scantailor-RELEASE_0_9_12_2/math/CMakeLists.txt000066400000000000000000000027031271170121200211520ustar00rootroot00000000000000PROJECT("Math library") 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 ) 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 ) 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 ) SOURCE_GROUP("Sources\\Differentiation Framework" FILES ${ADIFF_SOURCES}) ADD_LIBRARY(math STATIC ${GENERIC_SOURCES} ${SPFIT_SOURCES} ${ADIFF_SOURCES}) ADD_SUBDIRECTORY(spfit/tests) ADD_SUBDIRECTORY(adiff/tests) scantailor-RELEASE_0_9_12_2/math/HomographicTransform.h000066400000000000000000000054361271170121200227250ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef HOMOGRAPHIC_TRANSFORM_H_ #define HOMOGRAPHIC_TRANSFORM_H_ #include "VecNT.h" #include "MatrixCalc.h" #include template class HomographicTransform; template class HomographicTransformBase { public: typedef VecNT Vec; typedef VecNT<(N+1)*(N+1), T> Mat; explicit HomographicTransformBase(Mat const& mat) : m_mat(mat) {} HomographicTransform inv() const; Vec operator()(Vec const& from) const; Mat const& mat() const { return m_mat; } private: Mat m_mat; }; template class HomographicTransform : public HomographicTransformBase { public: explicit HomographicTransform( typename HomographicTransformBase::Mat const& mat) : HomographicTransformBase(mat) {} }; /** An optimized, both in terms of API and performance, 1D version. */ template class HomographicTransform<1, T> : public HomographicTransformBase<1, T> { public: explicit HomographicTransform( typename HomographicTransformBase<1, T>::Mat const& mat) : HomographicTransformBase<1, T>(mat) {} T operator()(T from) const; // Prevent it's shadowing by the above one. using HomographicTransformBase<1, T>::operator(); }; template HomographicTransform HomographicTransformBase::inv() const { StaticMatrixCalc mc; Mat inv_mat; mc(m_mat, N+1, N+1).inv().write(inv_mat); return HomographicTransform(inv_mat); } template typename HomographicTransformBase::Vec HomographicTransformBase::operator()(Vec const& from) const { StaticMatrixCalc mc; VecNT const hsrc(from, T(1)); VecNT hdst; (mc(m_mat, N+1, N+1)*mc(hsrc, N+1, 1)).write(hdst); VecNT res(&hdst[0]); res /= hdst[N]; return res; } template T HomographicTransform<1, T>::operator()(T from) const { // Optimized version for 1D case. T const* m = this->mat().data(); return (from * m[0] + m[2]) / (from * m[1] + m[3]); } #endif scantailor-RELEASE_0_9_12_2/math/LineBoundedByRect.cpp000066400000000000000000000033441271170121200224210ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "LineBoundedByRect.h" #include "LineIntersectionScalar.h" #include "NumericTraits.h" #include bool lineBoundedByRect(QLineF& line, QRectF const& rect) { QLineF const 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; BOOST_FOREACH(QLineF const& 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; } } if (max > min) { line = QLineF(line.pointAt(min), line.pointAt(max)); return true; } else { return false; } } scantailor-RELEASE_0_9_12_2/math/LineBoundedByRect.h000066400000000000000000000022251271170121200220630ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef LINE_BOUNDED_BY_RECT_H_ #define LINE_BOUNDED_BY_RECT_H_ #include #include /** * If \p line (not line segment!) intersects with \p rect, * writes intersection points as the new \p line endpoints * and returns true. Otherwise returns false and leaves * \p line unmodified. */ bool lineBoundedByRect(QLineF& line, QRectF const& rect); #endif scantailor-RELEASE_0_9_12_2/math/LineIntersectionScalar.cpp000066400000000000000000000047451271170121200235320ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "LineIntersectionScalar.h" #include #include bool lineIntersectionScalar(QLineF const& line1, QLineF const& line2, double& s1, double& s2) { QPointF const p1(line1.p1()); QPointF const p2(line2.p1()); QPointF const v1(line1.p2() - line1.p1()); QPointF const 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| double const det_A = v2.x() * v1.y() - v1.x() * v2.y(); if (fabs(det_A) < std::numeric_limits::epsilon()) { return false; } double const r_det_A = 1.0 / det_A; QPointF const 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; } bool lineIntersectionScalar(QLineF const& line1, QLineF const& line2, double& s1) { QPointF const p1(line1.p1()); QPointF const p2(line2.p1()); QPointF const v1(line1.p2() - line1.p1()); QPointF const 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| double const det_A = v2.x() * v1.y() - v1.x() * v2.y(); if (fabs(det_A) < std::numeric_limits::epsilon()) { return false; } QPointF const b(p2 - p1); s1 = (v2.x()*b.y() - b.x() * v2.y()) / det_A; return true; } scantailor-RELEASE_0_9_12_2/math/LineIntersectionScalar.h000066400000000000000000000026051271170121200231700ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef LINE_INTERSECTION_SCALAR_H_ #define LINE_INTERSECTION_SCALAR_H_ #include /** * Finds such scalars s1 and s2, so that "line1.pointAt(s1)" and "line2.pointAt(s2)" * would be the intersection point between line1 and line2. Returns false if the * lines are parallel or if any of the lines have zero length and therefore no direction. */ bool lineIntersectionScalar(QLineF const& line1, QLineF const& line2, double& s1, double& s2); /** * Same as the one above, but doesn't bother to calculate s2. */ bool lineIntersectionScalar(QLineF const& line1, QLineF const& line2, double& s1); #endif scantailor-RELEASE_0_9_12_2/math/LinearFunction.cpp000066400000000000000000000027761271170121200220500ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "LinearFunction.h" #include #include #include LinearFunction::LinearFunction(size_t num_vars) : a(num_vars) , b(0) { } void LinearFunction::reset() { a.fill(0); b = 0; } double LinearFunction::evaluate(double const* x) const { size_t const num_vars = numVars(); double sum = b; for (size_t i = 0; i < num_vars; ++i) { sum += a[i] * x[i]; } return sum; } void LinearFunction::swap(LinearFunction& other) { a.swap(other.a); std::swap(b, other.b); } LinearFunction& LinearFunction::operator+=(LinearFunction const& other) { a += other.a; b += other.b; return *this; } LinearFunction& LinearFunction::operator*=(double scalar) { a *= scalar; b *= scalar; return *this; } scantailor-RELEASE_0_9_12_2/math/LinearFunction.h000066400000000000000000000032421271170121200215020ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef LINEAR_FUNCTION_H_ #define LINEAR_FUNCTION_H_ #include "VecT.h" #include /** * A linear function from arbitrary number of variables * expressed in matrix form: * \code * F(x) = a^T * x + b * \endcode */ class LinearFunction { // Member-wise copying is OK. public: VecT a; double b; /** * Constructs a linear function of the given number of variables, * initializing everything to zero. */ LinearFunction(size_t num_vars = 0); /** * Resets everything to zero, so that F(x) = 0 */ void reset(); size_t numVars() const { return a.size(); } /** * Evaluates a^T * x + b */ double evaluate(double const* x) const; void swap(LinearFunction& other); LinearFunction& operator+=(LinearFunction const& other); LinearFunction& operator*=(double scalar); }; inline void swap(LinearFunction& f1, LinearFunction& f2) { f1.swap(f2); } #endif scantailor-RELEASE_0_9_12_2/math/LinearSolver.cpp000066400000000000000000000021111271170121200215140ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #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"); } } scantailor-RELEASE_0_9_12_2/math/LinearSolver.h000066400000000000000000000144451271170121200211760ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef LINEAR_SOLVER_H_ #define LINEAR_SOLVER_H_ #include "NonCopyable.h" #include "StaticPool.h" #include #include #include #include #include #include #include /** * \brief Solves Ax = b using LU decomposition. * * Overdetermined systems are supported. Solving them will succeed * provided the system is consistent. * * \note All matrices are assumed to be in column-major order. * * \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(T const* A, T* X, T const* 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(T const* A, T* X, T const* B) const; private: size_t m_rowsAB; size_t m_colsArowsX; size_t m_colsBX; }; template void LinearSolver::solve(T const* A, T* X, T const* B, T* tbuffer, size_t* pbuffer) const { using namespace std; // To catch different overloads of abs() T const epsilon(sqrt(numeric_limits::epsilon())); size_t const 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(abs(p_col[perm[i]])); for (size_t j = i + 1; j < m_rowsAB; ++j) { T const abs_pivot(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"); } size_t const phys_pivot_row(perm[virt_pivot_row]); perm[virt_pivot_row] = perm[i]; perm[i] = phys_pivot_row; T const* const p_pivot = p_col + phys_pivot_row; T const r_pivot(T(1) / *p_pivot); // Eliminate entries below the pivot. for (size_t j = i + 1; j < m_rowsAB; ++j) { T const* p1 = p_pivot; T* p2 = p_col + perm[j]; if (abs(*p2) <= epsilon) { // We consider it's already zero. *p2 = T(); continue; } T const 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; T const* 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) { int const phys_row = perm[virt_row]; T right(p_b_col[phys_row]); // Move already calculated factors to the right side. T const* 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) { int const phys_row = perm[virt_row]; T right(p_b_col[phys_row]); // Move everything to the right side, then verify it's zero. T const* 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 (abs(right) > epsilon) { throw std::runtime_error("LinearSolver: inconsistent overdetermined system"); } } p_y_col += m_colsArowsX; p_b_col += m_rowsAB; } // Now solve Ux = y T* p_x_col = X; p_y_col = y_data; T const* 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 = m_colsArowsX - 1; virt_row >= 0; --virt_row) { T right(p_y_col[virt_row]); // Move already calculated factors to the right side. T const* p_lu = p_lu_last_col + perm[virt_row]; // Go right to left, stop at diagonal. for (int lu_col = 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; } } template void LinearSolver::solve(T const* A, T* X, T const* B) const { 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()); } #endif scantailor-RELEASE_0_9_12_2/math/MatrixCalc.h000066400000000000000000000201421271170121200206070ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef MATRIX_CALC_H_ #define MATRIX_CALC_H_ #include "NonCopyable.h" #include "StaticPool.h" #include "DynamicPool.h" #include "LinearSolver.h" #include "MatMNT.h" #include "MatT.h" #include "VecNT.h" #include "VecT.h" #include #include template class MatrixCalc; namespace mcalc { template class AbstractAllocator { public: virtual T* allocT(size_t size) = 0; virtual size_t* allocP(size_t size) = 0; }; template class StaticPoolAllocator : public AbstractAllocator { public: virtual T* allocT(size_t size) { return m_poolT.alloc(size); } virtual size_t* allocP(size_t size) { return m_poolP.alloc(size); } private: StaticPool m_poolP; StaticPool m_poolT; }; template class DynamicPoolAllocator : public AbstractAllocator { public: virtual T* allocT(size_t size) { return m_poolT.alloc(size); } virtual size_t* allocP(size_t size) { return m_poolP.alloc(size); } private: DynamicPool m_poolP; DynamicPool m_poolT; }; template class Mat { template friend class ::MatrixCalc; template friend Mat operator+(Mat const& m1, Mat const& m2); template friend Mat operator-(Mat const& m1, Mat const& m2); template friend Mat operator*(Mat const& m1, Mat const& m2); template friend Mat operator*(OT scalar, Mat const& m); template friend Mat operator*(Mat const& m, OT scalar); template friend Mat operator/(Mat const& m, OT scalar); public: Mat inv() const; Mat solve(Mat const& b) const; Mat solve(T const* data, int rows, int cols) const; Mat trans() const; Mat write(T* buf) const; template Mat write(VecNT& vec) const; Mat transWrite(T* buf) const; template Mat transWrite(VecNT& vec) const; Mat operator-() const; T const* rawData() const { return data; } private: Mat(AbstractAllocator* alloc, T const* data, int rows, int cols) : alloc(alloc), data(data), rows(rows), cols(cols) {} AbstractAllocator* alloc; T const* data; int rows; int cols; }; } // namespace mcalc template > class MatrixCalc { DECLARE_NON_COPYABLE(MatrixCalc) public: MatrixCalc() {} mcalc::Mat operator()(T const* data, int rows, int cols) { return mcalc::Mat(&m_alloc, data, rows, cols); } template mcalc::Mat operator()(VecNT const& vec, int rows, int cols) { return mcalc::Mat(&m_alloc, vec.data(), rows, cols); } template mcalc::Mat operator()(MatMNT const& mat) { return mcalc::Mat(&m_alloc, mat.data(), mat.ROWS, mat.COLS); } mcalc::Mat operator()(MatT const& mat) { return mcalc::Mat(&m_alloc, mat.data(), mat.rows(), mat.cols()); } template mcalc::Mat operator()(VecNT const& vec) { return mcalc::Mat(&m_alloc, vec.data(), vec.SIZE, 1); } mcalc::Mat operator()(VecT const& vec) { return mcalc::Mat(&m_alloc, vec.data(), vec.size(), 1); } private: Alloc m_alloc; }; template class StaticMatrixCalc : public MatrixCalc > { }; template class DynamicMatrixCalc : public MatrixCalc > { }; /*========================== Implementation =============================*/ namespace mcalc { template Mat Mat::inv() const { assert(cols == rows); T* ident_data = alloc->allocT(rows * cols); Mat ident(alloc, ident_data, rows, cols); int const 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 Mat Mat::solve(Mat const& b) const { 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); return Mat(alloc, x_data, cols, b.cols); } template Mat Mat::solve(T const* data, int rows, int cols) const { return solve(Mat(alloc, data, rows, cols)); } template Mat Mat::trans() const { if (cols == 1 || rows == 1) { return Mat(alloc, data, cols, rows); } T* p_trans = alloc->allocT(cols * rows); transWrite(p_trans); return Mat(alloc, p_trans, cols, rows); } template Mat Mat::write(T* buf) const { int const todo = rows * cols; for (int i = 0; i < todo; ++i) { buf[i] = data[i]; } return *this; } template template Mat Mat::write(VecNT& vec) const { assert(N >= size_t(rows * cols)); return write(vec.data()); } template Mat Mat::transWrite(T* buf) const { T* p_trans = buf; for (int i = 0; i < rows; ++i) { T const* p_src = data + i; for (int j = 0; j < cols; ++j) { *p_trans = *p_src; ++p_trans; p_src += rows; } } return *this; } template template Mat Mat::transWrite(VecNT& vec) const { assert(N >= rows * cols); return transWrite(vec.data()); } /** Unary minus. */ template Mat Mat::operator-() const { T* p_res = alloc->allocT(rows * cols); Mat res(alloc, p_res, rows, cols); int const todo = rows * cols; for (int i = 0; i < todo; ++i) { p_res[i] = -data[i]; } return res; } template Mat operator+(Mat const& m1, Mat const& m2) { 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); int const todo = m1.rows * m1.cols; for (int i = 0; i < todo; ++i) { p_res[i] = m1.data[i] + m2.data[i]; } return res; } template Mat operator-(Mat const& m1, Mat const& m2) { 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); int const todo = m1.rows * m1.cols; for (int i = 0; i < todo; ++i) { p_res[i] = m1.data[i] - m2.data[i]; } return res; } template Mat operator*(Mat const& m1, Mat const& 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) { T const* p_m1 = m1.data + rrow; T const* 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; } template Mat operator*(T scalar, Mat const& m) { T* p_res = m.alloc->allocT(m.rows * m.cols); Mat res(m.alloc, p_res, m.rows, m.cols); int const todo = m.rows * m.cols; for (int i = 0; i < todo; ++i) { p_res[i] = m.data[i] * scalar; } return res; } template Mat operator*(Mat const& m, T scalar) { return scalar * m; } template Mat operator/(Mat const& m, T scalar) { return m * (1.0f / scalar); } } // namespace mcalc #endif scantailor-RELEASE_0_9_12_2/math/PolylineIntersector.cpp000066400000000000000000000107461271170121200231410ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "PolylineIntersector.h" #include "ToLineProjector.h" #include 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; } PolylineIntersector::PolylineIntersector(std::vector const& polyline) : m_polyline(polyline), m_numSegments(polyline.size() - 1) { } QPointF PolylineIntersector::intersect(QLineF const& line, Hint& hint) const { QLineF const normal(line.normalVector()); if (intersectsSegment(normal, hint.m_lastSegment)) { return intersectWithSegment(line, hint.m_lastSegment); } int 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); } // Check the next segment in opposite direction. if (intersectsSegment(normal, (segment = hint.m_lastSegment - hint.m_direction))) { hint.update(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. QPointF const origin(normal.p1()); Vec2d const nv(normal.p2() - normal.p1()); int left_idx = 0; int right_idx = m_polyline.size() - 1; double left_dot = nv.dot(m_polyline[left_idx] - origin); while (left_idx + 1 < right_idx) { int const mid_idx = (left_idx + right_idx) >> 1; double const 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); return intersectWithSegment(line, left_idx); } bool PolylineIntersector::intersectsSegment(QLineF const& normal, int segment) const { if (segment < 0 || segment >= m_numSegments) { return false; } QLineF const seg_line(m_polyline[segment], m_polyline[segment + 1]); return intersectsSpan(normal, seg_line); } bool PolylineIntersector::intersectsSpan(QLineF const& normal, QLineF const& span) const { Vec2d const v1(normal.p2() - normal.p1()); Vec2d const v2(span.p1() - normal.p1()); Vec2d const v3(span.p2() - normal.p1()); return v1.dot(v2) * v1.dot(v3) <= 0; } QPointF PolylineIntersector::intersectWithSegment(QLineF const& line, int segment) const { QLineF const 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( QLineF const& line, QPointF& intersection, Hint& hint) const { QLineF const normal(line.normalVector()); QPointF const origin(normal.p1()); Vec2d const nv(normal.p2() - normal.p1()); Vec2d const front_vec(m_polyline.front() - origin); Vec2d const back_vec(m_polyline.back() - origin); double const front_dot = nv.dot(front_vec); double const back_dot = nv.dot(back_vec); if (front_dot * back_dot <= 0) { return false; } ToLineProjector const proj(line); if (fabs(front_dot) < 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; } scantailor-RELEASE_0_9_12_2/math/PolylineIntersector.h000066400000000000000000000031511271170121200225760ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef POLYLINE_INTERSECTOR_H_ #define POLYLINE_INTERSECTOR_H_ #include "VecNT.h" #include #include #include class PolylineIntersector { public: class Hint { friend class PolylineIntersector; public: Hint(); private: void update(int new_segment); int m_lastSegment; int m_direction; }; PolylineIntersector(std::vector const& polyline); QPointF intersect(QLineF const& line, Hint& hint) const; private: bool intersectsSegment(QLineF const& normal, int segment) const; bool intersectsSpan(QLineF const& normal, QLineF const& span) const; QPointF intersectWithSegment(QLineF const& line, int segment) const; bool tryIntersectingOutsideOfPolyline( QLineF const& line, QPointF& intersection, Hint& hint) const; std::vector m_polyline; int m_numSegments; }; #endif scantailor-RELEASE_0_9_12_2/math/QuadraticFunction.cpp000066400000000000000000000050511271170121200225400ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "QuadraticFunction.h" #include #include #include 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; } double QuadraticFunction::evaluate(double const* x) const { size_t const 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; } QuadraticFunction::Gradient QuadraticFunction::gradient() const { size_t const 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; return grad; } void QuadraticFunction::recalcForTranslatedArguments(double const* translation) { size_t const 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 double const 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); } QuadraticFunction& QuadraticFunction::operator+=(QuadraticFunction const& other) { A += other.A; b += other.b; c += other.c; return *this; } QuadraticFunction& QuadraticFunction::operator*=(double scalar) { A *= scalar; b *= scalar; c *= scalar; return *this; } scantailor-RELEASE_0_9_12_2/math/QuadraticFunction.h000066400000000000000000000046561271170121200222170ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef QUADRATIC_FUNCTION_H_ #define QUADRATIC_FUNCTION_H_ #include "MatT.h" #include "VecT.h" #include /** * A quadratic function from arbitrary number of variables * expressed in matrix form: * \code * F(x) = x^T * A * x + b^T * x + c * \endcode * With N being the number of variables, we have:\n * x: vector of N variables.\n * A: NxN matrix of coefficients.\n * b: vector of N coefficients.\n * 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. */ MatT A; VecT b; double c; /** * Constructs a quadratic functiono of the given number of variables, * initializing everything to zero. */ QuadraticFunction(size_t num_vars = 0); /** * Resets everything to zero, so that F(x) = 0 */ void reset(); size_t numVars() const { return b.size(); } /** * Evaluates x^T * A * x + b^T * x + c */ double evaluate(double const* x) 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(double const* translation); void swap(QuadraticFunction& other); QuadraticFunction& operator+=(QuadraticFunction const& other); QuadraticFunction& operator*=(double scalar); }; inline void swap(QuadraticFunction& f1, QuadraticFunction& f2) { f1.swap(f2); } #endif scantailor-RELEASE_0_9_12_2/math/SidesOfLine.cpp000066400000000000000000000022531271170121200212620ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "SidesOfLine.h" qreal sidesOfLine(QLineF const& line, QPointF const& p1, QPointF const& p2) { QPointF const normal(line.normalVector().p2() - line.p1()); QPointF const vec1(p1 - line.p1()); QPointF const vec2(p2 - line.p1()); qreal const dot1 = normal.x() * vec1.x() + normal.y() * vec1.y(); qreal const dot2 = normal.x() * vec2.x() + normal.y() * vec2.y(); return dot1 * dot2; } scantailor-RELEASE_0_9_12_2/math/SidesOfLine.h000066400000000000000000000026611271170121200207320ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SIDES_OF_LINE_H_ #define SIDES_OF_LINE_H_ #include #include /** * This function allows you to check if a pair of points is on the same * or different sides of a line. * * Returns: * \li Negative value, if points are on different sides of line. * \li Positive value, if points are on the same side of line. * \li Zero, if one or both of the points are on the line. * * \note Line's endpoints don't matter - consider the whole line, * not a line segment. If the line is really a point, zero will * always be returned. */ qreal sidesOfLine(QLineF const& line, QPointF const& p1, QPointF const& p2); #endif scantailor-RELEASE_0_9_12_2/math/ToLineProjector.cpp000066400000000000000000000034421271170121200222010ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ToLineProjector.h" #include #include #include #include ToLineProjector::ToLineProjector(QLineF const& line) : m_origin(line.p1()), m_vec(line.p2() - line.p1()), m_mat(m_vec) { using namespace std; // At*A*x = At*b double const AtA = m_mat.dot(m_mat); if (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(QPointF const& pt) const { Vec2d const b(pt - m_origin); return m_mat.dot(b); } QPointF ToLineProjector::projectionPoint(QPointF const& pt) const { return m_origin + m_vec * projectionScalar(pt); } QPointF ToLineProjector::projectionVector(QPointF const& pt) const { return projectionPoint(pt) - pt; } double ToLineProjector::projectionDist(QPointF const& pt) const { return sqrt(projectionSqDist(pt)); } double ToLineProjector::projectionSqDist(QPointF const& pt) const { return Vec2d(projectionPoint(pt) - pt).squaredNorm(); } scantailor-RELEASE_0_9_12_2/math/ToLineProjector.h000066400000000000000000000040701271170121200216440ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef TO_LINE_PROJECTOR_H_ #define TO_LINE_PROJECTOR_H_ #include "VecNT.h" #include #include /** * \brief Projects points onto a line (not a line segment). * * Projecting means finding the point on a line that is closest * to the given point. */ class ToLineProjector { public: /** * \brief Initializes line projector. * * If line endpoints match, all points will * be projected to that point. */ ToLineProjector(QLineF const& line); /** * \brief Finds the projection point. */ QPointF projectionPoint(QPointF const& pt) const; /** * \brief Equivalent to projectionPoint(pt) - pt. */ QPointF projectionVector(QPointF const& 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(QPointF const& pt) const; /** * Returns the distance from \p pt to the projection point. */ double projectionDist(QPointF const& pt) const; /** * Returns the squared distance from \p pt to the projection point. * This function is faster than projectionDist(). */ double projectionSqDist(QPointF const& pt) const; private: QPointF m_origin; QPointF m_vec; Vec2d m_mat; }; #endif scantailor-RELEASE_0_9_12_2/math/XSpline.cpp000066400000000000000000000570011271170121200205010ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "XSpline.h" #include "VecNT.h" #include "MatT.h" #include "NumericTraits.h" #include "ToLineProjector.h" #include "adiff/SparseMap.h" #include "adiff/Function.h" #include #include #ifndef Q_MOC_RUN #include #endif #include #include #include #include #include struct XSpline::TensionDerivedParams { static double const t0; static double const t1; static double const t2; static double const 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); double value(double u) const; double firstDerivative(double u) const; double secondDerivative(double u) const; private: double m_c1; double m_c2; double m_c3; double m_c4; double m_c5; }; class XSpline::HBlendFunc { public: HBlendFunc(double q); double value(double u) const; double firstDerivative(double u) const; double secondDerivative(double u) const; 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; bool hasNonZeroCoeffs(int idx) const { double sum = fabs(zeroDerivCoeffs[idx]) + fabs(firstDerivCoeffs[idx]) + fabs(secondDerivCoeffs[idx]); return sum > std::numeric_limits::epsilon(); } }; int XSpline::numControlPoints() const { return m_controlPoints.size(); } int XSpline::numSegments() const { return std::max(m_controlPoints.size() - 1, 0); } double XSpline::controlPointIndexToT(int idx) const { assert(idx >= 0 || idx <= (int)m_controlPoints.size()); return double(idx) / (m_controlPoints.size() - 1); } void XSpline::appendControlPoint(QPointF const& pos, double tension) { m_controlPoints.push_back(ControlPoint(pos, tension)); } void XSpline::insertControlPoint(int idx, QPointF const& pos, double 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); } QPointF XSpline::controlPointPosition(int idx) const { return m_controlPoints[idx].pos; } void XSpline::moveControlPoint(int idx, QPointF const& 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()); 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; } QPointF XSpline::pointAt(double t) const { int const 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 { double const t2 = t * num_segments; double const segment = floor(t2); return pointAtImpl((int)segment, t2 - segment); } } QPointF XSpline::pointAtImpl(int segment, double t) const { LinearCoefficient coeffs[4]; int const num_coeffs = linearCombinationFor(coeffs, segment, t); QPointF pt(0, 0); for (int i = 0; i < num_coeffs; ++i) { LinearCoefficient const& c = coeffs[i]; pt += m_controlPoints[c.controlPointIdx].pos * c.coeff; } return pt; } void XSpline::sample( VirtualFunction3& sink, SamplingParams const& 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; } QPointF const from_pt(pointAt(from_t)); QPointF const to_pt(pointAt(to_t)); sink(from_pt, from_t, HEAD_SAMPLE); int const num_segments = numSegments(); if (num_segments == 0) { return; } double const 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); } void XSpline::maybeAddMoreSamples( VirtualFunction3& sink, double max_sqdist_to_spline, double max_sqdist_between_samples, double num_segments, double r_num_segments, double prev_t, QPointF const& prev_pt, double next_t, QPointF const& next_pt) const { double const prev_next_sqdist = Vec2d(next_pt - prev_pt).squaredNorm(); if (prev_next_sqdist < 1e-6) { // Too close. Projecting anything on such a small line segment is dangerous. return; } SampleFlags flags = DEFAULT_SAMPLE; double mid_t = 0.5 * (prev_t + next_t); double const nearby_junction_t = 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; } QPointF const mid_pt(pointAt(mid_t)); if (flags != JUNCTION_SAMPLE) { QPointF const 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 ); } void XSpline::linearCombinationAt(double t, std::vector& coeffs) const { assert(t >= 0 && t <= 1); int const 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 { double const t2 = t * num_segments; double const segment = 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, m_controlPoints.size() - 1); ControlPoint pts[4]; for (int i = 0; i < 4; ++i) { pts[i] = m_controlPoints[idxs[i]]; } TensionDerivedParams const 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::PointAndDerivs XSpline::pointAndDtsAt(double t) const { assert(t >= 0 && t <= 1); PointAndDerivs pd; DecomposedDerivs const derivs(decomposedDerivs(t)); for (int i = 0; i < derivs.numControlPoints; ++i) { QPointF const& 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(double const t) const { assert(t >= 0 && t <= 1); int const 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 { double const t2 = t * num_segments; double const segment = floor(t2); return decomposedDerivsImpl((int)segment, t2 - segment); } } XSpline::DecomposedDerivs XSpline::decomposedDerivsImpl(int const segment, double const 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, m_controlPoints.size() - 1); ControlPoint pts[4]; for (int i = 0; i < 4; ++i) { pts[i] = m_controlPoints[derivs.controlPoints[i]]; } TensionDerivedParams const 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 double const 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) double const ta = 1.0 / (tdp.t0 - tdp.T0p); double const tb = -tdp.T0p * ta; double const 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) double const ta = 1.0 / (tdp.t1 - tdp.T1p); double const tb = -tdp.T1p * ta; double const 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) double const ta = 1.0 / (tdp.t2 - tdp.T2m); double const tb = -tdp.T2m * ta; double const 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) double const ta = 1.0 / (tdp.t3 - tdp.T3m); double const tb = -tdp.T3m * ta; double const 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); } } double const sum = A.sum(); double const sum2 = sum * sum; double const sum4 = sum2 * sum2; double const d_sum = dA.sum(); double const dd_sum = ddA.sum(); for (int i = 0; i < 4; ++i) { derivs.zeroDerivCoeffs[i] = A[i] / sum; double const d1 = dA[i] * sum - A[i] * d_sum; derivs.firstDerivCoeffs[i] = d1 / sum2; // Derivative of: A[i] / sum // Derivative of: dA[i] * sum double const dd1 = ddA[i] * sum + dA[i] * d_sum; // Derivative of: A[i] * d_sum double const dd2 = dA[i] * d_sum + A[i] * dd_sum; // Derivative of (dA[i] * sum - A[i] * d_sum) / sum2 double const 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; int const 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; } QuadraticFunction XSpline::controlPointsAttractionForce() const { return controlPointsAttractionForce(0, numSegments()); } QuadraticFunction XSpline::controlPointsAttractionForce(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); int const 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) { 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); Function<2> const dx(next_x - prev_x); Function<2> const 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; return f; } QuadraticFunction XSpline::junctionPointsAttractionForce() const { 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); int const 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); BOOST_FOREACH(LinearCoefficient const& coeff, coeffs) { QPointF const 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) { Function<2> const dx(next_x - prev_x); Function<2> const 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; return f; } QPointF XSpline::pointClosestTo(QPointF const to, double* t, double accuracy) const { if (m_controlPoints.empty()) { if (t) { *t = 0; } return QPointF(); } int const 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); double const sqdist = sqDistToLine(to, QLineF(prev_pt, next_pt)); if (sqdist < best_sqdist) { best_segment = seg; best_sqdist = sqdist; } } // Continue with a binary search. double const 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) { double const mid_t = 0.5 * (prev_t + next_t); QPointF const mid_pt(pointAtImpl(best_segment, mid_t)); ToLineProjector const projector(QLineF(prev_pt, next_pt)); double const pt = projector.projectionScalar(to); double const 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; } } // 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 prev_pt; } else { if (t) { *t = (best_segment + next_t) / num_segments; } return next_pt; } } QPointF XSpline::pointClosestTo(QPointF const to, double accuracy) const { return pointClosestTo(to, 0, accuracy); } std::vector XSpline::toPolyline(SamplingParams const& params, double from_t, double to_t) const { struct Sink : public VirtualFunction3 { std::vector polyline; virtual void operator()(QPointF pt, double, SampleFlags) { polyline.push_back(pt); } }; Sink sink; sample(sink, params, from_t, to_t); return sink.polyline; } double XSpline::sqDistToLine(QPointF const& pt, QLineF const& line) { ToLineProjector const projector(line); double const 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 =====================*/ // We make t lie in [0 .. 1] within a segment, // which gives us delta == 1 and the following tk's: double const XSpline::TensionDerivedParams::t0 = -1; double const XSpline::TensionDerivedParams::t1 = 0; double const XSpline::TensionDerivedParams::t2 = 1; double const XSpline::TensionDerivedParams::t3 = 2; static double square(double v) { return v*v; } XSpline::TensionDerivedParams::TensionDerivedParams( double const tension1, double const tension2) { // tension1, tension2 lie in [-1 .. 1] // Tk+ = t(k+1) + s(k+1) // Tk- = t(k-1) - s(k-1) double const s1 = std::max(tension1, 0); double const s2 = std::max(tension2, 0); T0p = t1 + s1; T1p = t2 + s2; T2m = t1 - s1; T3m = t2 - s2; // q's lie in [0 .. 0.5] double const s_1 = -0.5 * std::min(tension1, 0); double const 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) { } double XSpline::GBlendFunc::value(double u) const { double const u2 = u * u; double const u3 = u2 * u; double const u4 = u3 * u; double const u5 = u4 * u; return m_c1 * u + m_c2 * u2 + m_c3 * u3 + m_c4 * u4 + m_c5 * u5; } double XSpline::GBlendFunc::firstDerivative(double u) const { double const u2 = u * u; double const u3 = u2 * u; double const u4 = u3 * u; 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 { double const u2 = u * u; double const u3 = u2 * u; 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) { } double XSpline::HBlendFunc::value(double u) const { double const u2 = u * u; double const u3 = u2 * u; double const u4 = u3 * u; double const u5 = u4 * u; return m_c1 * u + m_c2 * u2 + m_c4 * u4 + m_c5 * u5; } double XSpline::HBlendFunc::firstDerivative(double u) const { double const u2 = u * u; double const u3 = u2 * u; double const u4 = u3 * u; return m_c1 + 2 * m_c2 * u + 4 * m_c4 * u3 + 5 * m_c5 * u4; } double XSpline::HBlendFunc::secondDerivative(double u) const { double const u2 = u * u; double const u3 = u2 * u; return 2 * m_c2 + 12 * m_c4 * u2 + 20 * m_c5 * u3; } /*======================== PointAndDerivs ========================*/ double XSpline::PointAndDerivs::signedCurvature() const { double const cross = firstDeriv.x()*secondDeriv.y() - firstDeriv.y()*secondDeriv.x(); double tlen = sqrt(firstDeriv.x()*firstDeriv.x() + firstDeriv.y()*firstDeriv.y()); return cross / (tlen * tlen * tlen); } scantailor-RELEASE_0_9_12_2/math/XSpline.h000066400000000000000000000157201271170121200201500ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef XSPLINE_H_ #define XSPLINE_H_ #include "spfit/FittableSpline.h" #include "QuadraticFunction.h" #include "VirtualFunction.h" #include "NumericTraits.h" #include #include #include /** * \brief An open X-Spline. * * [1] Blanc, C., Schlick, C.: X-splines: a spline model designed for the end-user. * 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; }; virtual int numControlPoints() const; /** * 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(QPointF const& 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, QPointF const& pos, double tension); void eraseControlPoint(int idx); virtual QPointF controlPointPosition(int idx) const; virtual void moveControlPoint(int idx, QPointF const& pos); 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() */ virtual void linearCombinationAt(double t, std::vector& coeffs) const; /** * 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() */ virtual void sample( VirtualFunction3& sink, SamplingParams const& params = SamplingParams(), double from_t = 0.0, double to_t = 1.0) const; std::vector toPolyline( SamplingParams const& 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; ControlPoint() : tension(0) {} ControlPoint(QPointF const& p, double tns) : pos(p), tension(tns) {} }; struct TensionDerivedParams; class GBlendFunc; class HBlendFunc; struct DecomposedDerivs; QPointF pointAtImpl(int segment, double t) const; int linearCombinationFor(LinearCoefficient* coeffs, int segment, double t) const; DecomposedDerivs decomposedDerivs(double t) const; DecomposedDerivs decomposedDerivsImpl(int segment, double t) const; void maybeAddMoreSamples( VirtualFunction3& sink, double max_sqdist_to_spline, double max_sqdist_between_samples, double num_segments, double r_num_segments, double prev_t, QPointF const& prev_pt, double next_t, QPointF const& next_pt) const; static double sqDistToLine(QPointF const& pt, QLineF const& line); std::vector m_controlPoints; }; inline void swap(XSpline& o1, XSpline& o2) { o1.swap(o2); } #endif scantailor-RELEASE_0_9_12_2/math/adiff/000077500000000000000000000000001271170121200174615ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/math/adiff/Function.cpp000066400000000000000000000155711271170121200217630ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Function.h" #include #include #include namespace adiff { // Note: // 1. u in this file has the same meaning as in [1]. // 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) { } Function<2>::Function(SparseMap<2> const& sparse_map) : value() , firstDerivs(sparse_map.numNonZeroElements()) , secondDerivs(sparse_map.numNonZeroElements()) { } Function<2>::Function(size_t arg_idx, double val, SparseMap<2> const& 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. size_t const num_vars = sparse_map.numVars(); // arg_idx row for (size_t i = 0; i < num_vars; ++i) { size_t const 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) { size_t const u = sparse_map.nonZeroElementIdx(i, arg_idx); if (u != sparse_map.ZERO_ELEMENT) { firstDerivs[u] = 1.0; } } } VecT Function<2>::gradient(SparseMap<2> const& sparse_map) const { size_t const num_vars = sparse_map.numVars(); VecT grad(num_vars); for (size_t i = 0; i < num_vars; ++i) { size_t const u = sparse_map.nonZeroElementIdx(i, i); if (u != sparse_map.ZERO_ELEMENT) { grad[i] = firstDerivs[u]; } } return grad; } MatT Function<2>::hessian(SparseMap<2> const& sparse_map) const { size_t const 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; size_t const ij = sparse_map.nonZeroElementIdx(i, j); if (ij != sparse_map.ZERO_ELEMENT) { if (i == j) { Fij = secondDerivs[ij]; } else { size_t const ii = sparse_map.nonZeroElementIdx(i, i); size_t const 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; } void Function<2>::swap(Function<2>& other) { std::swap(value, other.value); firstDerivs.swap(other.firstDerivs); secondDerivs.swap(other.secondDerivs); } Function<2>& Function<2>::operator+=(Function<2> const& other) { size_t const p = firstDerivs.size(); assert(secondDerivs.size() == p); assert(other.firstDerivs.size() == p); assert(other.secondDerivs.size() == p); value += other.value; for (size_t u = 0; u < p; ++u) { firstDerivs[u] += other.firstDerivs[u]; secondDerivs[u] += other.secondDerivs[u]; } return *this; } Function<2>& Function<2>::operator-=(Function<2> const& other) { size_t const p = firstDerivs.size(); assert(secondDerivs.size() == p); assert(other.firstDerivs.size() == p); assert(other.secondDerivs.size() == p); value -= other.value; for (size_t u = 0; u < p; ++u) { firstDerivs[u] -= other.firstDerivs[u]; secondDerivs[u] -= other.secondDerivs[u]; } return *this; } Function<2>& Function<2>::operator*=(double scalar) { size_t const p = firstDerivs.size(); value *= scalar; for (size_t u = 0; u < p; ++u) { firstDerivs[u] *= scalar; } return *this; } Function<2> operator+(Function<2> const& f1, Function<2> const& f2) { size_t const 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.firstDerivs[u]; res.secondDerivs[u] = f1.secondDerivs[u] + f2.secondDerivs[u]; } return res; } Function<2> operator-(Function<2> const& f1, Function<2> const& f2) { size_t const 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.firstDerivs[u]; res.secondDerivs[u] = f1.secondDerivs[u] - f2.secondDerivs[u]; } return res; } Function<2> operator*(Function<2> const& f1, Function<2> const& f2) { size_t const 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]; } return res; } Function<2> operator*(Function<2> const& f, double scalar) { Function<2> res(f); res *= scalar; return res; } Function<2> operator*(double scalar, Function<2> const& f) { Function<2> res(f); res *= scalar; return res; } Function<2> operator/(Function<2> const& num, Function<2> const& den) { size_t const 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; double const den2 = den.value * den.value; double const den4 = den2 * den2; for (size_t u = 0; u < p; ++u) { // Derivative of: (num.value / den.value) double const 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]) double const d2 = num.secondDerivs[u] * den.value - num.value * den.secondDerivs[u]; // Derivative of: den2 double const d3 = 2.0 * den.value * den.firstDerivs[u]; // Derivative of: (d1 / den2) res.secondDerivs[u] = (d2 * den2 - d1 * d3) / den4; } return res; } } // namespace adiff scantailor-RELEASE_0_9_12_2/math/adiff/Function.h000066400000000000000000000054561271170121200214310ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef ADIFF_FUNCTION_H_ #define ADIFF_FUNCTION_H_ #include "SparseMap.h" #include "MatT.h" #include "VecT.h" #include namespace adiff { /** * \brief Represents a multivariable function and its derivatives. * * It would be nice to have a generic version, * but for now it's only specialized for ORD == 2. */ template class Function; template<> class Function<2> { // 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; /** * 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(SparseMap<2> const& 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, SparseMap<2> const& sparse_map); VecT gradient(SparseMap<2> const& sparse_map) const; MatT hessian(SparseMap<2> const& sparse_map) const; void swap(Function& other); Function& operator+=(Function const& other); Function& operator-=(Function const& other); Function& operator*=(double scalar); }; inline void swap(Function<2>& f1, Function<2>& f2) { f1.swap(f2); } Function<2> operator+(Function<2> const& f1, Function<2> const& f2); Function<2> operator-(Function<2> const& f1, Function<2> const& f2); Function<2> operator*(Function<2> const& f1, Function<2> const& f2); Function<2> operator*(Function<2> const& f, double scalar); Function<2> operator*(double scalar, Function<2> const& f); Function<2> operator/(Function<2> const& num, Function<2> const& den); } // namespace adiff #endif scantailor-RELEASE_0_9_12_2/math/adiff/SparseMap.cpp000066400000000000000000000026741271170121200220710ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "SparseMap.h" namespace adiff { size_t const 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) { } 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; } } 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); } } } size_t SparseMap<2>::nonZeroElementIdx(size_t i, size_t j) const { return m_map(i, j); } } // namespace adiff scantailor-RELEASE_0_9_12_2/math/adiff/SparseMap.h000066400000000000000000000045721271170121200215350ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef ADIFF_SPARSITY_H_ #define ADIFF_SPARSITY_H_ #include "MatT.h" #include 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 class SparseMap; /** * The second order sparse map specified which elements of the Hessian * matrix are non-zero. */ template<> class SparseMap<2> { // Member-wise copying is OK. public: static size_t const 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); /** * 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 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 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; }; } // namespace adiff #endif scantailor-RELEASE_0_9_12_2/math/adiff/references.txt000066400000000000000000000003121271170121200223370ustar00rootroot00000000000000[1] C. H. Bischof, G. F. Corliss, and A. Griewank. Structured second- and higher-order derivatives through univariate Taylor series. http://softlib.rice.edu/pub/CRPC-TRs/reports/CRPC-TR92237.pdfscantailor-RELEASE_0_9_12_2/math/adiff/tests/000077500000000000000000000000001271170121200206235ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/math/adiff/tests/CMakeLists.txt000066400000000000000000000005501271170121200233630ustar00rootroot00000000000000INCLUDE_DIRECTORIES(BEFORE ..) SET( sources ${CMAKE_SOURCE_DIR}/tests/main.cpp TestHessians.cpp ) SOURCE_GROUP("Sources" FILES ${sources}) SET( libs math ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} ${Boost_PRG_EXECUTION_MONITOR_LIBRARY} ${QT_QTCORE_LIBRARY} ${EXTRA_LIBS} ) ADD_EXECUTABLE(adiff_tests ${sources}) TARGET_LINK_LIBRARIES(adiff_tests ${libs}) scantailor-RELEASE_0_9_12_2/math/adiff/tests/TestHessians.cpp000066400000000000000000000062771271170121200237600ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Function.h" #include "SparseMap.h" #include "MatT.h" #include "VecT.h" #include #include #include #include namespace adiff { namespace tests { BOOST_AUTO_TEST_SUITE(AutomaticDifferentiationTestSuite); BOOST_AUTO_TEST_CASE(test1) { // F(x) = x^2 | x = 3 SparseMap<2> sparse_map(1); sparse_map.markAllNonZero(); Function<2> const x(0, 3, sparse_map); Function<2> const res(x * x); VecT const gradient(res.gradient(sparse_map)); MatT const hessian(res.hessian(sparse_map)); // 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_AUTO_TEST_CASE(test2) { // F(x, y) = x^2 | x = 3 SparseMap<2> sparse_map(2); sparse_map.markAllNonZero(); Function<2> const x(0, 3, sparse_map); Function<2> const res(x * x); VecT const gradient(res.gradient(sparse_map)); MatT const 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(); Function<2> const x(0, 2, sparse_map); Function<2> const y(1, 3, sparse_map); Function<2> const xxx(x * x * x); Function<2> const yy(y * y); Function<2> const res(xxx * yy); VecT const gradient(res.gradient(sparse_map)); MatT const 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(); } // namespace tests } // namespace adiff scantailor-RELEASE_0_9_12_2/math/spfit/000077500000000000000000000000001271170121200175355ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/math/spfit/ConstraintSet.cpp000066400000000000000000000111031271170121200230350ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ConstraintSet.h" #include "FittableSpline.h" #include #include namespace spfit { ConstraintSet::ConstraintSet(FittableSpline const* spline) : m_pSpline(spline) { assert(spline); } void ConstraintSet::constrainControlPoint(int cp_idx, QPointF const& pos) { assert(cp_idx >= 0 && cp_idx < m_pSpline->numControlPoints()); QPointF const 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); } void ConstraintSet::constrainSplinePoint(double t, QPointF const& pos) { std::vector coeffs; m_pSpline->linearCombinationAt(t, coeffs); // Fix the x coordinate. LinearFunction f(m_pSpline->numControlPoints() * 2); f.b = -pos.x(); BOOST_FOREACH(FittableSpline::LinearCoefficient const& coeff, coeffs) { int const 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(); BOOST_FOREACH(FittableSpline::LinearCoefficient const& coeff, coeffs) { int const 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); } void ConstraintSet::constrainControlPoint(int cp_idx, QLineF const& line) { assert(cp_idx >= 0 && cp_idx < m_pSpline->numControlPoints()); if (line.p1() == line.p2()) { constrainControlPoint(cp_idx, line.p1()); return; } double const dx = line.p2().x() - line.p1().x(); double const 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.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. QPointF const cp(m_pSpline->controlPointPosition(cp_idx)); f.b += cp.x() * dy; f.b += cp.y() * dx; m_constraints.push_back(f); } void ConstraintSet::constrainSplinePoint(double t, QLineF const& line) { if (line.p1() == line.p2()) { constrainSplinePoint(t, line.p1()); return; } std::vector coeffs; m_pSpline->linearCombinationAt(t, coeffs); double const dx = line.p2().x() - line.p1().x(); double const 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; BOOST_FOREACH(FittableSpline::LinearCoefficient const& 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. QPointF const cp(m_pSpline->controlPointPosition(coeff.controlPointIdx)); f.b += cp.x() * coeff.coeff * dy - cp.y() * coeff.coeff * dx; } m_constraints.push_back(f); } } // namespace spfit scantailor-RELEASE_0_9_12_2/math/spfit/ConstraintSet.h000066400000000000000000000030251271170121200225060ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SPFIT_CONSTRAINT_SET_H_ #define SPFIT_CONSTRAINT_SET_H_ #include "LinearFunction.h" #include #include #include #include namespace spfit { class FittableSpline; class ConstraintSet { // Member-wise copying is OK. public: ConstraintSet(FittableSpline const* spline); std::list const& constraints() const { return m_constraints; } void constrainControlPoint(int cp_idx, QPointF const& pos); void constrainControlPoint(int cp_idx, QLineF const& line); void constrainSplinePoint(double t, QPointF const& pos); void constrainSplinePoint(double t, QLineF const& line); private: FittableSpline const* m_pSpline; std::list m_constraints; }; } // namespace spfit #endif scantailor-RELEASE_0_9_12_2/math/spfit/FittableSpline.h000066400000000000000000000074461271170121200226260ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SPFIT_FITTABLE_SPLINE_H_ #define SPFIT_FITTABLE_SPLINE_H_ #include "VirtualFunction.h" #include "NumericTraits.h" #include "FlagOps.h" #include #include 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() {} virtual int numControlPoints() const = 0; virtual QPointF controlPointPosition(int idx) const = 0; virtual void moveControlPoint(int idx, QPointF const& 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( VirtualFunction3& sink, SamplingParams const& params, double from_t = 0.0, double to_t = 1.0) const = 0; }; DEFINE_FLAG_OPS(FittableSpline::SampleFlags) } // namespace spfit #endif scantailor-RELEASE_0_9_12_2/math/spfit/FrenetFrame.cpp000066400000000000000000000024621271170121200224430ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "FrenetFrame.h" #include namespace spfit { FrenetFrame::FrenetFrame(Vec2d const& origin, Vec2d const& tangent_vector, YAxisDirection ydir) : m_origin(origin) { double const sqlen = tangent_vector.squaredNorm(); if (sqlen > 1e-6) { m_unitTangent = tangent_vector / 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 scantailor-RELEASE_0_9_12_2/math/spfit/FrenetFrame.h000066400000000000000000000041071271170121200221060ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SPFIT_FRENET_FRAME_H_ #define SPFIT_FRENET_FRAME_H_ #include "VecNT.h" 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(Vec2d const& origin, Vec2d const& tangent_vector, YAxisDirection ydir = Y_POINTS_DOWN); Vec2d const& origin() const { return m_origin; } Vec2d const& unitTangent() const { return m_unitTangent; } Vec2d const& unitNormal() const { return m_unitNormal; } private: Vec2d m_origin; Vec2d m_unitTangent; Vec2d m_unitNormal; }; } // namespace spfit #endif scantailor-RELEASE_0_9_12_2/math/spfit/LinearForceBalancer.cpp000066400000000000000000000042351271170121200240660ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "LinearForceBalancer.h" #include namespace spfit { LinearForceBalancer::LinearForceBalancer(double internal_external_ratio) : 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(); } void LinearForceBalancer::setTargetRatio(double internal_external_ratio) { m_targetRatio = internal_external_ratio; recalcRateOfChange(); } void LinearForceBalancer::setIterationsToTarget(int iterations) { 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 (fabs(internal_force) > 1e-6) { lambda = m_currentRatio * external_force / internal_force; } return lambda; } void LinearForceBalancer::nextIteration() { 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; } } } // namespace spfit scantailor-RELEASE_0_9_12_2/math/spfit/LinearForceBalancer.h000066400000000000000000000043221271170121200235300ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SPFIT_LINEAR_FORCE_BALANCER_H_ #define SPFIT_LINEAR_FORCE_BALANCER_H_ namespace spfit { /** * \brief Implements one possible strategy of choosing the internal / external force ratio. * * The strategy is starting with some value and linearly changing it (usually decreasing) * 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. */ LinearForceBalancer(double internal_external_ratio); double currentRatio() const { return m_currentRatio; } void setCurrentRatio(double internal_external_ratio); double targetRatio() const { return m_targetRatio; } 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); 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(); private: void recalcRateOfChange(); double m_currentRatio; double m_targetRatio; double m_rateOfChange; int m_iterationsToTarget; }; } // namespace spfit #endif scantailor-RELEASE_0_9_12_2/math/spfit/ModelShape.h000066400000000000000000000026161271170121200217340ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SPFIT_MODEL_SHAPE_H_ #define SPFIT_MODEL_SHAPE_H_ #include "SqDistApproximant.h" #include "FittableSpline.h" #include namespace spfit { /** * \brief A shape we are trying to fit a spline to. * * Could be a polyline or maybe a point cloud. */ class ModelShape { public: virtual ~ModelShape() {} /** * 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( QPointF const& pt, FittableSpline::SampleFlags flags) const = 0; }; } // namespace spfit #endif scantailor-RELEASE_0_9_12_2/math/spfit/OptimizationResult.cpp000066400000000000000000000027001271170121200241250ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "OptimizationResult.h" #include #include 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. } double OptimizationResult::improvementPercentage() const { double improvement = m_forceBefore - m_forceAfter; improvement /= (m_forceBefore + std::numeric_limits::epsilon()); return improvement * 100; // Convert to percents. } } // namespace spfit scantailor-RELEASE_0_9_12_2/math/spfit/OptimizationResult.h000066400000000000000000000030451271170121200235750ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SPFIT_OPTIMIZATION_RESULT_H_ #define SPFIT_OPTIMIZATION_RESULT_H_ #include 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; }; } // namespace spfit #endif scantailor-RELEASE_0_9_12_2/math/spfit/Optimizer.cpp000066400000000000000000000124351271170121200222300ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Optimizer.h" #include "MatrixCalc.h" #include #include #include #include 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) { } void Optimizer::setConstraints(std::list const& constraints) { size_t const num_constraints = constraints.size(); size_t const 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. std::list::const_iterator 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); } void Optimizer::addExternalForce(QuadraticFunction const& force) { m_externalForce += force; } void Optimizer::addExternalForce(QuadraticFunction const& force, std::vector const& sparse_map) { size_t const num_vars = force.numVars(); for (size_t i = 0; i < num_vars; ++i) { int const ii = sparse_map[i]; for (size_t j = 0; j < num_vars; ++j) { int const jj = sparse_map[j]; m_externalForce.A(ii, jj) += force.A(i, j); } m_externalForce.b[ii] += force.b[i]; } m_externalForce.c += force.c; } void Optimizer::addInternalForce(QuadraticFunction const& force) { m_internalForce += force; } void Optimizer::addInternalForce( QuadraticFunction const& force, std::vector const& sparse_map) { size_t const num_vars = force.numVars(); for (size_t i = 0; i < num_vars; ++i) { int const ii = sparse_map[i]; for (size_t j = 0; j < num_vars; ++j) { int const jj = sparse_map[j]; m_internalForce.A(ii, jj) += force.A(i, j); } 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() QuadraticFunction::Gradient const 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); } } double const total_force_before = m_internalForce.c; DynamicMatrixCalc mc; try { mc(m_A).solve(mc(m_b)).write(m_x.data()); } catch (std::runtime_error const&) { m_externalForce.reset(); m_internalForce.reset(); m_x.fill(0); // To make undoLastStep() work as expected. return OptimizationResult(total_force_before, total_force_before); } double const 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); } void Optimizer::undoLastStep() { adjustConstraints(-1.0); m_x.fill(0); } /** * direction == 1 is used for forward adjustment, * direction == -1 is used for undoing the last step. */ void Optimizer::adjustConstraints(double direction) { size_t const 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); } } // namespace spfit scantailor-RELEASE_0_9_12_2/math/spfit/Optimizer.h000066400000000000000000000057661271170121200217060ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SPFIT_OPTIMIZER_H_ #define SPFIT_OPTIMIZER_H_ #include "OptimizationResult.h" #include "FittableSpline.h" #include "SqDistApproximant.h" #include "VirtualFunction.h" #include "VecNT.h" #include "MatT.h" #include "VecT.h" #include "LinearFunction.h" #include "QuadraticFunction.h" #include #include namespace spfit { class Optimizer { // Member-wise copying is OK. public: 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(std::list const& constraints); void addExternalForce(QuadraticFunction const& force); void addExternalForce(QuadraticFunction const& force, std::vector const& sparse_map); void addInternalForce(QuadraticFunction const& force); void addInternalForce(QuadraticFunction const& force, std::vector const& 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); double const* 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); } } // namespace spfit #endif scantailor-RELEASE_0_9_12_2/math/spfit/PolylineModelShape.cpp000066400000000000000000000114431271170121200240010ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "PolylineModelShape.h" #include "FrenetFrame.h" #include "NumericTraits.h" #include "XSpline.h" #include "VecNT.h" #include "ToLineProjector.h" #include #ifndef Q_MOC_RUN #include #endif #include #include #include #include namespace spfit { PolylineModelShape::PolylineModelShape(std::vector const& 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; BOOST_FOREACH(QPointF const& pt, polyline) { spline.appendControlPoint(pt, -1); } int const num_control_points = spline.numControlPoints(); double const 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( QPointF const& 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. int const num_segments = int(m_vertices.size()) - 1; for (int i = 0; i < num_segments; ++i) { QPointF const pt1(m_vertices[i].point); QPointF const pt2(m_vertices[i + 1].point); QLineF const segment(pt1, pt2); double const s = ToLineProjector(segment).projectionScalar(pt); if (s > 0 && s < 1) { QPointF const foot_point(segment.pointAt(s)); Vec2d const vec(pt - foot_point); double const 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. int const num_points = m_vertices.size(); for (int i = 0; i < num_points; ++i) { QPointF const vtx(m_vertices[i].point); Vec2d const vec(pt - vtx); double const 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); XSpline::PointAndDerivs const& pd1 = m_vertices[segment_idx]; XSpline::PointAndDerivs const& pd2 = m_vertices[segment_idx + 1]; FrenetFrame const frenet_frame(best_foot_point, pd2.point - pd1.point); double const k1 = pd1.signedCurvature(); double const k2 = pd2.signedCurvature(); double const weighted_k = k1 + segment_t * (k2 - k1); 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); XSpline::PointAndDerivs const& pd = m_vertices[vertex_idx]; FrenetFrame const frenet_frame(best_foot_point, pd.firstDeriv); 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()); } } SqDistApproximant PolylineModelShape::calcApproximant( QPointF const& pt, FittableSpline::SampleFlags const sample_flags, Flags const polyline_flags, FrenetFrame const& frenet_frame, double const 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); } } } // namespace spfit scantailor-RELEASE_0_9_12_2/math/spfit/PolylineModelShape.h000066400000000000000000000033751271170121200234530ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SPFIT_POLYLINE_MODEL_SHAPE_H_ #define SPFIT_POLYLINE_MODEL_SHAPE_H_ #include "NonCopyable.h" #include "ModelShape.h" #include "SqDistApproximant.h" #include "XSpline.h" #include "VecNT.h" #include "FlagOps.h" #include #include namespace spfit { class PolylineModelShape : public ModelShape { DECLARE_NON_COPYABLE(PolylineModelShape) public: enum Flags { DEFAULT_FLAGS = 0, POLYLINE_FRONT = 1 << 0, POLYLINE_BACK = 1 << 1 }; PolylineModelShape(std::vector const& polyline); virtual SqDistApproximant localSqDistApproximant( QPointF const& pt, FittableSpline::SampleFlags sample_flags) const; protected: virtual SqDistApproximant calcApproximant( QPointF const& pt, FittableSpline::SampleFlags sample_flags, Flags polyline_flags, FrenetFrame const& frenet_frame, double signed_curvature) const; private: std::vector m_vertices; }; DEFINE_FLAG_OPS(PolylineModelShape::Flags) } // namespace spfit #endif scantailor-RELEASE_0_9_12_2/math/spfit/SplineFitter.cpp000066400000000000000000000147101271170121200226540ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "SplineFitter.h" #include "OptimizationResult.h" #include "Optimizer.h" #include "ConstraintSet.h" #include "ModelShape.h" #include "VirtualFunction.h" #include #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. { } void SplineFitter::splineModified() { Optimizer(m_pSpline->numControlPoints() * 2).swap(m_optimizer); } void SplineFitter::setConstraints(ConstraintSet const& constraints) { m_optimizer.setConstraints(constraints.constraints()); } void SplineFitter::setSamplingParams(FittableSpline::SamplingParams const& params) { m_samplingParams = params; } void SplineFitter::addAttractionForce( Vec2d const& spline_point, std::vector const& coeffs, SqDistApproximant const& sqdist_approx) { int const num_coeffs = coeffs.size(); int const 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. double const a = sqdist_approx.A(i, j); // Now let's multiply Li by Lj for (int m = 0; m < num_coeffs; ++m) { double const c1 = coeffs[m].coeff; int const Li_idx = m * 2 + i; for (int n = 0; n < num_coeffs; ++n) { double const c2 = coeffs[n].coeff; int const 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. double const b = sqdist_approx.b[i]; for (int m = 0; m < num_coeffs; ++m) { int const Li_idx = m * 2 + i; f.b[Li_idx] += b * coeffs[m].coeff; } } // 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) { int const cp_idx = coeffs[i].controlPointIdx; QPointF const 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] : 0); // 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); } void SplineFitter::addAttractionForces(ModelShape const& model_shape, double from_t, double to_t) { class SampleProcessor : public VirtualFunction3 { public: SampleProcessor(SplineFitter& owner, ModelShape const& model_shape) : m_rOwner(owner), m_rModelShape(model_shape) {} virtual void operator()(QPointF pt, double t, FittableSpline::SampleFlags flags) { m_rOwner.m_pSpline->linearCombinationAt(t, m_rOwner.m_tempCoeffs); SqDistApproximant const approx(m_rModelShape.localSqDistApproximant(pt, flags)); m_rOwner.addAttractionForce(pt, m_rOwner.m_tempCoeffs, approx); } private: SplineFitter& m_rOwner; ModelShape const& m_rModelShape; }; SampleProcessor sample_processor(*this, model_shape); m_pSpline->sample(sample_processor, m_samplingParams, from_t, to_t); } void SplineFitter::addExternalForce(QuadraticFunction const& force) { m_optimizer.addExternalForce(force); } void SplineFitter::addExternalForce(QuadraticFunction const& force, std::vector const& sparse_map) { m_optimizer.addExternalForce(force, sparse_map); } void SplineFitter::addInternalForce(QuadraticFunction const& force) { m_optimizer.addInternalForce(force); } void SplineFitter::addInternalForce( QuadraticFunction const& force, std::vector const& sparse_map) { m_optimizer.addInternalForce(force, sparse_map); } OptimizationResult SplineFitter::optimize(double internal_force_weight) { OptimizationResult const res(m_optimizer.optimize(internal_force_weight)); int const num_control_points = m_pSpline->numControlPoints(); for (int i = 0; i < num_control_points; ++i) { Vec2d const delta(m_optimizer.displacementVector() + i * 2); m_pSpline->moveControlPoint(i, m_pSpline->controlPointPosition(i) + delta); } return res; } void SplineFitter::undoLastStep() { int const num_control_points = m_pSpline->numControlPoints(); for (int i = 0; i < num_control_points; ++i) { Vec2d const delta(m_optimizer.displacementVector() + i * 2); m_pSpline->moveControlPoint(i, m_pSpline->controlPointPosition(i) - delta); } m_optimizer.undoLastStep(); // Zeroes the displacement vector among other things. } } // namespace spfit scantailor-RELEASE_0_9_12_2/math/spfit/SplineFitter.h000066400000000000000000000053101271170121200223150ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SPFIT_SPLINE_FITTER_H_ #define SPFIT_SPLINE_FITTER_H_ #include "NonCopyable.h" #include "FittableSpline.h" #include "Optimizer.h" #include "VecNT.h" #include namespace spfit { class ConstraintSet; class ModelShape; struct SqDistApproximant; class OptimizationResult; class SplineFitter { DECLARE_NON_COPYABLE(SplineFitter) 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(); void setConstraints(ConstraintSet const& constraints); void setSamplingParams(FittableSpline::SamplingParams const& sampling_params); void addAttractionForce(Vec2d const& spline_point, std::vector const& coeffs, SqDistApproximant const& sqdist_approx); void addAttractionForces(ModelShape const& model_shape, double from_t = 0.0, double to_t = 1.0); void addExternalForce(QuadraticFunction const& force); void addExternalForce(QuadraticFunction const& force, std::vector const& sparse_map); void addInternalForce(QuadraticFunction const& force); void addInternalForce(QuadraticFunction const& force, std::vector const& sparce_map); /** \see Optimizer::externalForce() */ double externalForce() const { return m_optimizer.externalForce(); } /** \see Optimizer::internalForce() */ double internalForce() const { return m_optimizer.internalForce(); } OptimizationResult optimize(double internal_force_weight); 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; }; } // namespace spfit #endif scantailor-RELEASE_0_9_12_2/math/spfit/SqDistApproximant.cpp000066400000000000000000000075651271170121200237100ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "SqDistApproximant.h" #include "FrenetFrame.h" #include "MatrixCalc.h" #include #include #include namespace spfit { SqDistApproximant::SqDistApproximant( Vec2d const& origin, Vec2d const& u, Vec2d const& v, double m, double n) { assert(fabs(u.squaredNorm() - 1.0) < 1e-06 && "u is not normalized"); assert(fabs(v.squaredNorm() - 1.0) < 1e-06 && "v is not normalized"); assert(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(Vec2d const& pt) { return weightedPointDistance(pt, 1); } SqDistApproximant SqDistApproximant::weightedPointDistance(Vec2d const& pt, double weight) { return SqDistApproximant(pt, Vec2d(1, 0), Vec2d(0, 1), weight, weight); } SqDistApproximant SqDistApproximant::lineDistance(QLineF const& line) { return weightedLineDistance(line, 1); } SqDistApproximant SqDistApproximant::weightedLineDistance(QLineF const& line, double weight) { Vec2d u(line.p2() - line.p1()); double const sqlen = u.squaredNorm(); if (sqlen > 1e-6) { u /= sqrt(sqlen); } else { return pointDistance(line.p1()); } // Unit normal to line. Vec2d const v(-u[1], u[0]); return SqDistApproximant(line.p1(), u, v, 0, weight); } SqDistApproximant SqDistApproximant::curveDistance( Vec2d const& reference_point, FrenetFrame const& frenet_frame, double signed_curvature) { return weightedCurveDistance(reference_point, frenet_frame, signed_curvature, 1); } SqDistApproximant SqDistApproximant::weightedCurveDistance( Vec2d const& reference_point, FrenetFrame const& frenet_frame, double const signed_curvature, double const weight) { double const abs_curvature = fabs(signed_curvature); double m = 0; if (abs_curvature > std::numeric_limits::epsilon()) { Vec2d const to_reference_point(reference_point - frenet_frame.origin()); double const p = 1.0 / abs_curvature; double const d = 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(Vec2d const& pt) const { StaticMatrixCalc mc; return (mc(pt, 1, 2)*mc(A)*mc(pt, 2, 1) + mc(b, 1, 2)*mc(pt, 2, 1)).rawData()[0] + c; } } // namespace spfit scantailor-RELEASE_0_9_12_2/math/spfit/SqDistApproximant.h000066400000000000000000000055321271170121200233450ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SQDIST_APPROXIMANT_H_ #define SQDIST_APPROXIMANT_H_ #include "VecNT.h" #include "MatMNT.h" #include namespace spfit { class FrenetFrame; /** * A quadratic function of the form:\n * F(x) = x^T * A * x + b^T * x + c\n * where: * \li x: a column vector representing a point in 2D space. * \li A: a 2x2 positive semidefinite matrix. * \li b: a 2 element column vector. * \li c: some constant. * * The function is meant to approximate the squared distance * to the target model. It's only accurate in a neighbourhood * of some unspecified point. * The iso-curves of the function are rotated ellipses in a general case. * * \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(Vec2d const& origin, Vec2d const& u, Vec2d const& v, double m, double n); static SqDistApproximant pointDistance(Vec2d const& pt); static SqDistApproximant weightedPointDistance(Vec2d const& pt, double weight); static SqDistApproximant lineDistance(QLineF const& line); static SqDistApproximant weightedLineDistance(QLineF const& line, double weight); static SqDistApproximant curveDistance( Vec2d const& reference_point, FrenetFrame const& frenet_frame, double signed_curvature); static SqDistApproximant weightedCurveDistance( Vec2d const& reference_point, FrenetFrame const& frenet_frame, double signed_curvature, double weight); double evaluate(Vec2d const& pt) const; }; } // namespace spfit #endif scantailor-RELEASE_0_9_12_2/math/spfit/references.txt000066400000000000000000000006371271170121200224250ustar00rootroot00000000000000[1] Pottmann H, Leopoldseder S. A concept for parametric surface fitting which avoids the parametrization problem. http://scholar.google.com/scholar?cluster=7639013126651979063&hl=en&as_sdt=0,5&as_vis=1 [2] W. Wang, H. Pottmann, Y. Liu, Fitting B-spline curves to point clouds by squared distance minimization. http://scholar.google.com/scholar?cluster=5549848162867282704&hl=en&as_sdt=0,5&as_vis=1scantailor-RELEASE_0_9_12_2/math/spfit/tests/000077500000000000000000000000001271170121200206775ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/math/spfit/tests/CMakeLists.txt000066400000000000000000000005611271170121200234410ustar00rootroot00000000000000INCLUDE_DIRECTORIES(BEFORE ..) SET( sources ${CMAKE_SOURCE_DIR}/tests/main.cpp TestSqDistApproximant.cpp ) SOURCE_GROUP("Sources" FILES ${sources}) SET( libs math ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} ${Boost_PRG_EXECUTION_MONITOR_LIBRARY} ${QT_QTCORE_LIBRARY} ${EXTRA_LIBS} ) ADD_EXECUTABLE(spfit_tests ${sources}) TARGET_LINK_LIBRARIES(spfit_tests ${libs}) scantailor-RELEASE_0_9_12_2/math/spfit/tests/TestSqDistApproximant.cpp000066400000000000000000000060441271170121200257010ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "SqDistApproximant.h" #include "VecNT.h" #include "ToLineProjector.h" #include #include #ifndef Q_MOC_RUN #include #include #endif #include #include namespace spfit { namespace tests { BOOST_AUTO_TEST_SUITE(SqDistApproximantTestSuite); static double const PI = 3.14159265; static double frand(double from, double to) { double const rand_0_1 = rand() / double(RAND_MAX); return from + (to - from) * rand_0_1; } BOOST_AUTO_TEST_CASE(test_point_distance) { for (int i = 0; i < 100; ++i) { Vec2d const origin(frand(-50, 50), frand(-50, 50)); SqDistApproximant const approx(SqDistApproximant::pointDistance(origin)); for (int j = 0; j < 10; ++j) { Vec2d const pt(frand(-50, 50), frand(-50, 50)); double const 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) { Vec2d const pt1(frand(-50, 50), frand(-50, 50)); double const angle = frand(0, 2.0 * PI); Vec2d const delta(cos(angle), sin(angle)); QLineF const line(pt1, pt1 + delta); SqDistApproximant const approx(SqDistApproximant::lineDistance(line)); ToLineProjector const proj(line); for (int j = 0; j < 10; ++j) { Vec2d const pt(frand(-50, 50), frand(-50, 50)); double const 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) { Vec2d const origin(frand(-50, 50), frand(-50, 50)); double const angle = frand(0, 2.0 * PI); Vec2d const u(cos(angle), sin(angle)); Vec2d v(-u[1], u[0]); if (rand() & 1) { v = -v; } double const m = frand(0, 3); double const n = frand(0, 3); SqDistApproximant const approx(origin, u, v, m, n); for (int j = 0; j < 10; ++j) { Vec2d const pt(frand(-50, 50), frand(-50, 50)); double const u_proj = u.dot(pt - origin); double const v_proj = v.dot(pt - origin); double const 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(); } // namespace tests } // namespace spfit scantailor-RELEASE_0_9_12_2/packaging/000077500000000000000000000000001271170121200174035ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/packaging/osx/000077500000000000000000000000001271170121200202145ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/packaging/osx/Info.plist.in000066400000000000000000000015321271170121200225720ustar00rootroot00000000000000 CFBundleDevelopmentRegion English CFBundleInfoDictionaryVersion 6.0 CFBundleExecutable ScanTailor CFBundleName ScanTailor CFBundlePackageType APPL CFBundleIdentifier net.sourceforge.Scantailor CFBundleSignature sctl CSResourcesFileMapped CFBundleIconFile ScanTailor.icns CFBundleShortVersionString ScanTailor-@VERSION@ LSMinimumSystemVersion 10.4.0 scantailor-RELEASE_0_9_12_2/packaging/osx/ScanTailor.icns000066400000000000000000004643751271170121200231540ustar00rootroot00000000000000icnshýis32®Áæéîó÷ÿõñòðñêòº;s©—£ «–«ÏÂÁÆ¾ÒŠŠªÔÉÔÌËc–ëÞìêæéÄ»·ÿäÒ¾ú‡¸ôÀÂÆÆÿį¶ì¤Õ´æy·ç·z¥ÇþȹÃâxÏÒù~¹ðì•ÏÿÿËöú ‚°¶êí”ÉýÿËÆ·þóðË{²õÿ}ÅÿÿÌpÏ¿÷¤ØÅ»ƒ¥òý—ÍÿÿËзü³ÀÂðŒjìúëõûÿÌÄ›ÿöøøìKÉÿýÿúÿÌçÊùéôäòžËÌþ÷øúâÿÿùúöôÿ£—ú‚Îÿ÷ûýýüÿÿüÿÙ¦¥ÿÏ“âþüÿþþýÿøþ·‘ÊþÿÈØÿùÿþ€ÿýÿã¼îÿù‚ÿ²ÂÜàåêïûìççääÜá¶5j•€‰„~’´œ ›´ƒe—оƼ¼K‡àÓÞàÛâ´’¢ÿÝͺót°öÆÅÌÆÿ¦œç™Ñª×b­ç³v¢Âý«œªÝtÏÊíi°òì–Ñÿÿ°©ž÷™ˆ}ªi®ìì”Êüÿ°± ûæíÂ}gª÷ÿ}Çÿÿ²p¸£ï›×¼³nôý—Îþÿ°½û«·¹åv`îúìõøÿµÃ‘úéëéÞlAËÿþÿúÿÈéÉïáéÙë–|ÊËþ÷øúäÿÿúøõòÿ¤—ú‚Îÿ÷ûûýüÿÿüÿئ¥ÿÏ“âþüÿþþýÿøþ·‘ÊþÿÈØÿùÿþ€ÿýÿã¼îÿù‚ÿ­¿áãêïôÿîççääÛâ±)\Šx{†s޵œœ šµ|c€º——ƒ‚yáÑÞàÜã³’Œï·§’²5¢÷ÄÆÌÆÿ¦†Æªƒ)Ÿê±x¢Âþ«ž‘Ãj¬˜ª,¢õë–Ðÿÿ°ª‰Ø…xnˆ-¡îì”Éüÿ°±‹Úº½•r-›úÿ~Æÿÿ²pºŽÎ…¬ŽŠ2÷ü˜Îþÿ°½ˆÛ’˜¦9Oð÷ìõøÿ´½sÖ·³¦–//ÎÿþÿùÿÃæ¹ÚÃÁ«¼yËÌþ÷øúáÿÿùöðêþª™ù‚Îÿ÷ûüýüÿÿüÿØ¥¤ÿÏ“âþüÿþþüþöþµ‘ËþÿÈØÿùÿþ€ÿþÿã¼îÿù‚ÿs8mkA1  <¬Í©¨Ÿœ•“–˜šš¦ŸØ…ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿaüîùöøùùøøøø÷ùîü`ÿøÿþÿÿÿÿÿÿÿþÿ÷ÿRÿöÿýþþþþþþþýÿöÿJÿøÿþÿÿþþÿþÿþÿ÷ÿAÿùÿýþþÿþþÿþýÿöÿ5ÿûÿþþÿýýÿþÿþÿ÷ÿ0ûô÷öööûûõüøõ÷îú0ÿÿÿÿÿÿÿÿÿüÿÿÿÿÿƒ¢²Àȧ–ÕÁûÝ×Ì¿¡!,J~"˜V11!RK@ E_17( <il32 ·ÿ~/±Íêïùú„ÿþøÿþýÿû÷ôïãè{:7=CHLSX_dmoy~ƒat’Š‘˜‘Œ•”‘“‘ŽoEÒÕ»¯ ˜“‰…„o\.©ìÄû¾ÂÂÇÍÏØÝôØŽÑNÜÿúÿþýù÷úüÿןN½ÿùÿþýýüú÷ûÿüÿÙuÅMÜùîòèìö÷ðçèÃDÀÿòýÿþÿüýúÿÛÙNÛþôõüðÍÀÒîóË”HÆÿÿØ °±³± ÆÿùÿÛ~ÔLÚüîþ³~ˆƒO¨úÄ”GÇÿÿOVÕ7*Òq4üûÿÜàOÜøû¯wêõÿÄ úÅ”HÂÿî¶éÿtPÿýÐãÿÿÞÝPÜøøl¹ÿãéïëðÊ’GÆÿÿȺÿiGÿçÿÿûÿÝçWÙýîm‡åóúöôïÉ“GÅÿþ®ÃÿoKÿóÿþüÿÞæVÛùø“Kh~“´ÊðË’HÂÿðµ×ÿpIÿòÿÿüÿßî\Øýóñ£r`SPS›ÎIÁÿàÊîÿrHÿóÿÿüÿßïZ×þñõüìãÒ¿}Y­˜EÃÿÚçóÿsHÿóÿÿüÿá÷`ÔÿðùìôøöûÐX•BÇÿãüóÿrIÿóÿÿüÿäx÷cÔþóãôïêãøÒ[¸–DÂÿãÿñÿnCÿóÿÿüÿåxþmÓúþ˜ ÿúÿæk§ÔE·óÕÿüì>"ÙÿþÿüÿæyÿmÏýùÄK~€²øÆ•C¦×Õÿÿìçëæÿýÿüÿçwÿ€ÉÿðóÔ¸±ÄìùìË‘Ly«âÿüÿþ€ÿ:þÿüÿè~ÿJ»ÿî÷ùþþüðèñËŒKY¦÷ÿþüûüûÿþÿýÿìbüJ·ÿíõîêéêîïíÌ“^2hïýþ‚ÿjþÿüÿçhÿP”ÿïúööõóñîòÊ…%_’€ÿñÿýýþþÿÿýÿécÿȹðæêêëìíëéæÇ XŠÿ†»ÿôýÿþÿþýøüß²þÿýù÷òïìèíééòðÿy}üô}¢ÿòçïíïíïðòýÿúúÿþ‚ÿþÿøÿjÿùð!GÖþûÿþÿú€ÿþƒýÿøÿÏEû^õøÿ¦ˆ¤¿ÿøÿýþýýÿÿþþƒÿþÿöÿXº÷Uÿüþõ–ì˜ÿþ‚ÿþ…ÿþÿøÿÒSÿ”†ÿöüÿ¸“‚Õÿùÿþˆÿþÿóÿœ‰ÿOÖÿùÿùÿ¹oûþ‹ÿþÿôÿ£œÃSÿôÿþÿúýÿþŒÿþÿúÿÜLBÚÿûÿÿþÿ÷õþ¤ÿÿ |-²ºÓÝêðøû€ÿþÿûñÿþ€ÿ þÿûóëàÍÌw<9:-$m'(.1:ET17VC@>719:6<@IUˆPÑͼ® —’‹„}zbM¨ë¾¸­°¶¸ÂËÒßèÿØ“– áìîîðîêéìîòÈ‹4¶ÿøÿþÿÿþü÷úÿûÿÜ2•ßèççßâêëäÚÛ³z-»ÿðýÿþÿüþþÿä© ßìêèóåøÈáç¼1ÀÿÿÙ¡±±´± Æÿûÿäª Ýëæñ¬z„€P¡ìµ/ÂÿÿPVÕ7*Òq4üýÿæºÞçò¨tÞéø»™ì¶0½ÿí¶éÿtPÿýÐâÿýè½Þèïi¶üØÞâÞã»0ÁÿÿȺÿiGÿçÿÿýÿèÉÛìålƒÚçíêçâº0Àÿþ®ÃÿoKÿóÿýþÿéÍÛêîŽMg|­Àã¼~1¾ÿðµ×ÿpIÿòÿÿþÿ騨íéåp`UQT–¾}2¼ÿßÊíÿrHÿóÿÿþÿêÝÖïçéðàØÈ¶zZ¡„.¿ÿÚçóÿsHÿóÿÿþÿìè%ÑðæíáèëéîÅY‹ˆ,ÃÿãüóÿrIÿóÿÿþÿðë'ÐðéÙéãÞØëÇ[ª‚-½ÿãÿñÿnCÿóÿÿþÿñö-Ííó“šùðòÚi Ä}-±ôÕÿüì>"Ùÿþÿþÿòú,Çðî¼L{‹‹}«ë·‚+ ØÔÿÿìçëæÿýÿþÿòÿ2¼÷äè˰©¼àëà¼~4s­âÿüÿþ€ÿ:þÿÿþôÿ4°úâëíóóïãÜä»x3R§öÿþüûüûÿþÿþÿò%üO±÷âêâßÞÞââༀE&jîþþ‚ÿ,þÿûÿæoÿOýäïêëéçåâåºqX“€ÿñÿýýþþÿÿýÿê`ÿÊ·çÝßÞ€ß:ÞÜÚ»›XŠÿ†»ÿôýÿþÿþýøüß³þÿýú÷ñîéäèäæðñÿy}üô}¢ÿòçïíïíïðòýÿúúÿþ‚ÿþÿøÿjÿùð!GÖþûÿþÿúÿÿ€þýüÿ÷ÿÐEû^õøÿ¦ˆ¤¿ÿøÿýþýýÿÿþþƒÿþÿöÿXº÷Uÿüþõ–ì˜ÿþ‚ÿþ„ÿþþÿøÿÒSÿ”†ÿöüÿ¸“‚Õÿùÿþˆÿþÿóÿœ‰ÿOÖÿùÿùÿ¹oûþ‹ÿþÿôÿ£œÃSÿôÿþÿúýÿþŒÿþÿúÿÜLBÚÿûÿÿþÿ÷õþ¤ÿÿ sª¸ÔÝêïøú€ÿþÿûñÿþ€ÿþÿûóëàÍÍj+!3&""##()03=HV/5VC@>719:6<@JT€€H»´¢“„{skb[V?*Ÿí½¹¬°¶¸ÂËÒßèÿÚŽ˜ÅÍÊÇþ·²±­¬…Q­ÿ÷ÿþÿÿþü÷úÿûÿÜ-• ÅËÅÁ¶´µ°¨žšuD³ÿðýÿþÿüþþÿäªÄÍÈÂÅ·“™££|I¸ÿþÙ¡±±´± Æÿûÿä«ÃÌÄÈ’nspU¥xI¹ÿÿPUÕ7*Òq4üýÿæ» ÃÊÍ“k²µ¹’z¦xHµÿì¶éÿtPÿýÐâÿýè¾ ÃËÊfšÇªª¨¡ zH¹ÿÿȺÿiGÿçÿÿýÿèÊÀÏÃiw®³³¬¦ zH¸ÿý¯ÃÿoKÿóÿýþÿéÎÁÌÊTeqz‰ {G¶ÿï¶×ÿpIÿòÿÿþÿéÙ¾ÏÇÀˆj`ZYZx|FµÿßÊíÿrHÿóÿÿþÿêÞ¼ÐÅÃ󩜎n`nJ·ÿÙçóÿsHÿóÿÿþÿìé"·ÒÄÆ¸¹·±¯’^dK»ÿâüóÿrIÿóÿÿþÿðì$·ÑÆ·½¶®¦­•\rJµÿâÿñÿnCÿóÿÿþÿñö+³ÐÍ„†Å¹·£a}G§öÔÿüì>"Ùÿþÿþÿòú*®ÓÉ¢Ppyvm„¤xJ–ÛÓÿÿìçëæÿý€ÿóÿ2¥ØÂ¨”Œ”¦©ž|Gh¯áÿüÿþ€ÿ þÿÿþôÿ ˜ÚÀÅÁÁ¼´© ¡zAFªõÿþüûüûÿþ€ÿôû/›ÙÁù²­©§¢ž~Jlíþþ‚ÿjþÿüÿêTÿ2qÞÀÇÀ¼¸²¬¦£w7N”€ÿñÿýýþþÿÿýÿíLÿ¨ÒÀ¼µ¯ª¥Ÿ›—Š‹W‹ÿ†»ÿôýÿþÿþýøüà¯ÿÿþü÷ðèßÕÕÑÕçòÿy}üô}¢ÿòçïíïíîðòþÿùùÿþ‚ÿþÿøÿŽjÿùð!GÖþûÿþÿù€ÿþþýüüûûýöÿÐFû]õøÿ¦ˆ¤¿ÿøÿýþýýÿÿþþƒÿþÿöÿXº÷Uÿüþõ–ì˜ÿþ‚ÿþƒÿþþýÿ÷ÿÒSÿ”†ÿöüÿ¸“‚Õÿùÿþˆÿþÿóÿœ‰ÿOÖÿùÿùÿ¹oûþ‹ÿþÿôÿ£œÃSÿôÿþÿúýÿþŒÿþÿúÿÜLBÚÿûÿÿþÿ÷õþ¤ÿl8mks³V= #sÃëÿýÿýõìäÞÖÎÆ¼±¬Êܽ¼»ÁÄÇÎÒÛãí÷ÿøÿ~ÿîþýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿðÿ_ÿ÷ÿþþýüûúùùø÷÷ùûøøøøùùúúûüýþÿûÿUÿöÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿLÿøÿþþþþþþþþþþþþþþþþþþþþþþþþþÿýÿCÿùÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿ;ÿúÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿ2ÿûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿ+ÿýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿ#üþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿ÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿñÿþÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿëÿýÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿãÿüÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÛÿûÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÓÿúÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÉÿùÿýÿÿÿÿÿÿÿþþþÿÿÿÿÿÿÿÿÿÿÿÿþÿûÿÑÿúÿþÿþþþþþÿÿÿÿÿþþþþþþþþþþþýÿûÿÔÿõüýÿÿÿÿÿÿÿúõöÿÿÿÿþÿÿÿÿÿÿÿÿÿýùÇÿùÿþýù÷ööøýÿÿÿþúõùüóö÷ööööøýüÿ,ÅÖîýÿÿÿÿþÿɉ£ÿüÿýüÿÿÿÿÿÿÿÿþñ['AVb^K$Ÿ¢)rµç—¦§™zaH- nø¡Ç“  '«› SuI;  •?˜M K'`l 8Vp" WhŸ#9 RV6œ ¦ª it32v¼ÿÿÿÿùÿ ú8J¼›¹èáûÿþ…ÿþùø÷ööõöø€÷ûýý‚þŒÿþûõ÷öûÿ‰þýø÷øø€ùöö÷øùþþ„ÿ þË # &3­ÿ4&<üÿçÊÔÐ܇5P`hfj<Ä79,"2ALOR@Xso‚vl[€‰xfg¢›”Šˆ|˜ÎÅÈÅÁºàøó÷õõ÷öü‹ÿ÷敇¢äÿþÿø‡õôöË¿ÅÆËÉÑ™‚‘œ—YdqƒkShqƒYBaclt|„nw3E)+%#"$!5>FQ\gxvv€‰‹†‡‡„|~‡zrhcWqvqg^ZKx—Š„}m‘Áµ¸¹»º{ŠlkxˆoO”‹…ƒ‚ƒ……‡‹‘“–”šbNWW\`dhnquIx~[Vahqw|uv‡—šš™¢¤ –—ŠRk{‹qŒ’€‚6H,AJSMJD:1,(!  #',/9ENU_iqsx€Š’˜™˜œž€¡£›”“Œ†‡ˆ~€‚‚zzƒ€tw£ ““‡ŒŠ‡‡€ˆ ‰‹‹Ž—œ›žŸ ¡¡­¯«©¥££¥¡š—“ˆŠ‰€†‚—£ª¨¤{›¡‡5G3F[mje``^ZYTQQJEA=82.(#!  "")159;=AGQ[bgjmlklg]OC:* /=KRqkuŠ„Œ”–˜›žŸž˜•”“‘‘Ž‹‰‡…~‚…†‚€Š—ž¡¥ª¬µ¯¶±²¯³‘|«®œ‹5J3CmŠ‘‰ˆ~}vqohhgcaa]ZZ[XUSPONLIEA?:65421.+&# !$'&(%@Ç hiKeY[hjhjpttutux|~€‚‚„‡Š—œŸ Ÿ ¤¨¬±´µ·¶··µ€·&º¶»µº¹¿´“»»¦ 045=¯¼¯¦£š›•“‘‹Œ‹„‚y€s;pnljhhggfgfdb`^\[ZYZZ[[ZVROOJE?93;6Mýÿþ÷Úд¤œ‘‘”’“˜›œžŸ¡£¦§M¨¨ª¬°´¸»º¸·¶¶¸º»»¼¿¾ÂÂÀÆÂÌÒäÔ³“ÆÁ©G'yo ;•×ùðçÛÖËź¶¯§§¥šœž›•’’‘‘ŽŒŠˆ…~}|€{€zywvqkc`\XYYXPNPYdff^]EÍøêðìîèÙÊ»¬ŽpbOP0ƒÿøÿþ€ÿ$þÿùÿ»2£ìÿÿûÿ¬„ÿøÿþùÁ_(þý‡ÿþÿö±‘r¥r×ÿkŒ™$‰Ö÷÷ö‚õóøïÿÆI¬qUY9¦jZô¹…]ˆŠUgm\âæñ€íèÙÊ»¬ŽpbOP0‚ÿøÿþ€ÿþÿöÿ„ ™ýþÿúöùõÿRÝÿóùø€ÿ êkÛÿûÿþ„ÿþÿö²‘s¥r×ÿc‹™$‰Ö÷÷öõ1ôôñ÷ä^HWj¶X˜úÅØúõÿêûÑCIYdlXÈ÷êïíèÙÊ»¬ŽpbOP0‚ÿøÿþ€ÿ'þÿúÿL$Ïÿùøýÿþÿ÷ÿ} åÿýÿþÿùùûÿ«¥ÿøÿþ„ÿþÿö³‘r¤s×ÿ“‡›%‰Ö÷÷öõ1ô÷÷óÏRbDmùíùóùõïîêðíîÔkdlC`ííïííèÙÊ»¬ŽpbOP0‚ÿøÿþƒÿùÞðö€ÿþýÿõÿŒ åÿüÿþ€ÿ þôÿ¹wÿ÷ÿþ„ÿþÿö³r¤s×ÿ¬„œ%‰Ö÷÷ö€õQ÷ñóœTkm@cáññðñíïòòóðòðùëèwA„ëíïííèÙÊ»¬ŽpbOP0‚ÿøÿþÿÿþÿúÿËÏôšÿûÿþÿþÿöÿ— æÿýÿþÿ€þÿðÿs#ÿø†ÿþÿø³r¤t×ÿ¨%‰Ö÷÷‚öAõô÷ðÿ8_d³æúñóóòóòññïðïðíîì{7ùéñìîèÙÊ»¬ŽpbOP0‚ÿøÿþÿÿþÿ÷ÿ®zÿ˃ÿý€ÿ þÿöÿ— åÿýÿþ€ÿþÿþÿïcïÿý…ÿþÿù´‘q¤t×ÿ¨}Ÿ%‰Ö÷÷öõõôöî¶²reWÕýíôñòòñðññðññëõ}ùéðìîèÙÊ»¬ŽpbOP0ÿøÿþ‚ÿþÿøÿ²m÷ÿþÿÿþÿöÿ˜ åÿýÿþ‚ÿþÿü‹ÿú´‘q¤u×ÿ£y¡&‰Ö÷÷öõöô÷èK^l_E³ýïôñòòñðïïîñðìïíîíîèÙÊ»¬ŽpbOP0ÿøÿþƒÿýùÿ“nÝÿüÿþþÿöÿ˜ åÿýÿþ„ÿýùŠÿû´‘o¤u×ÿt¢'‰Ö÷÷ö õõöòùsDjZAßùðó€òñ‚ðïïîíîíîèÙÊ»¬ŽpbOP0ÿøÿþƒÿýÿæƒvÆÿûÿþþÿöÿ˜ åÿýÿþ“ÿý´‘o¤v×ÿšo¤ (‰Ö÷÷ö õô÷ðÿ‹LoY™ñòóòñð‚ïîíîèÙÊ»¬ŽpbOP0€ÿøÿþÿþÿúÿǯÿûÿþþÿöÿ˜ åÿýÿþ…ÿþŠÿþ´‘n¤v×ÿ–j¦!)ˆÖ÷÷ö õõöôòŠ]l`³òñóñ€òñðï‚îíîèÙÊ»¬ŽpbOP0€ÿøÿþƒÿüÿ§††£ÿýÿÿþÿöÿ˜ åÿýÿþ”ÿ´‘m¤w×ÿœi§#)ˆÖ÷÷ö õøïÿ­@hbh:¨ÿî÷€ñðñðññ€ð‚ïîíîèÙÊ»¬ŽpbOP0€ÿøÿþ‚ÿþÿó“‘™ü€ÿ þÿöÿ˜ åÿýÿþ’ÿþÿµm¤w×ÿØz¡)*ˆÖ÷÷ö õøïÿ§>jchRXçòí€ôóñóòðñ€ðïîíîèÙÊ»¬ŽpbOP0€ÿ÷ÿþ€ÿþÿüÿØŽ™’–öÿþÿþÿöÿ˜ åÿýÿþ’ÿþÿ¶l¤x×ÿÙw¢+*ˆÖ÷÷öõøïÿ®Bhdg`RÂÿøð€í òíðñíññïïîîïîíîèÙÊ»¬ŽpbOP0€ÿ÷ÿþ‚ÿüÿ»”ž™˜îÿýÿýÿöÿ˜ åÿýÿþ’ÿþÿ·m¤x×ÿÙr£-+ˆÖ÷÷ö5õô÷ñý©\kdfk[€}Êÿ÷üïûôîöîîñðñòïííîîíîèÙÊ»¬ŽpbOP/ÿ÷ÿþƒÿú§Ÿ¡Ÿžóÿþÿþÿöÿ˜ åÿýÿþ’ÿþÿ¸Žm¤y×ÿØl¤.+‡Ö÷÷ö5õõöóöˆTidgeSAL­v¯ü»ÝùØòôììêéîððïîíîèÙÊ»¬ŽpbOP/ÿ÷ÿþÿüÿä ¨¥¤¦ú€ÿ þÿöÿ˜ åÿýÿþ’ÿþÿ¹m¤y×ÿÕd¦/+‡Ö÷÷ö5õõöôôaMldfgffkaI:kÅ@bø…œîð÷ùüðééëïííèÙÊ»¬ŽpbNP/ÿ÷ÿþÿüÿÌ¥­«¤¼ÿýÿÿþÿöÿ˜ åÿýÿþ’ÿþÿ»Œm¤z×ÿÔ]¨1+‡Ö÷÷‚ö4ôøðÿ¹6[fgededgkhlb_DqdBdÖ•¢àý÷óììîèÙÊ»¬ŽpbNP/~ÿ÷ÿþ‚ÿþ¹¯°³¦Õÿüÿÿþÿöÿ˜ åÿýÿþ’ÿþÿ¼Œm¤z×ÿÔW©2+‡Õ÷÷ö€õ2ôö빑]chifgedeegg_ZbQTeQG6ÊuûëïèØÊ»¬ŽpbNP/~ÿ÷ÿþ€ÿýÿî±¹´¸­ëÿýÿÿþÿöÿ˜ åÿýÿþ’ÿþÿ½‹m¤z×ÿÒP«2+‡Õ÷÷ö€õ2ôöñÿÕ@Wa_fdggeeffgjhdhhjkh[F<~ÖäñçÙÊ»¬ŽpbNP.~ÿ÷ÿþ€ÿüÿÛµ¿€ºûÿ þÿöÿ˜ åÿýÿþ’ÿþÿ¿Šm¤z×ÿõw 9(‡Õ÷÷öõ1ôõïí·;hƒ`jcehjddebdgfeecfbah™LjëèÙÊ»¬ŽpbNQ.~ÿ÷ÿþ€ÿ þÿʾ»Íÿý€ÿ þÿöÿ˜ åÿýÿþ’ÿþÿÀ‰m¤z×ÿþ€ž<'‡Õ÷÷öõôôöõÿ¸ãœ1FikRSkihlhddefg#caXHÕîÖÌ»¬ŽpbNQ.ÿ÷ÿþÿÿþÿõÂÉÅɼáÿü€ÿ þÿöÿ˜ åÿýÿþ’ÿþÿÁ‰m¤z×ÿþzž?%‡Õ÷÷ö‚õ=ôóòñûûÙ„©¯EõþÿÒÀËÊÐÆòÿþ¦ÿþÿýÿÚ„k‰ªj”÷ÿÃX¨QØö÷ö€õ8ôöðö§Shl]QjdBI“Ô\sK¶`Cœ­º­µµ·¹û«ÿýÿá„k†«k–þÿùq©X*Ò€÷€ö‚õôôóýûóûõóÿá©ÞüÝÎúìòôêîññïí€îíîèÙÊ»¬ŽpaQI?@œ——°¬´¬Çÿüªÿýÿãƒl†«l–þÿùg¡^ Ìùö÷€ö‚õ€ôïðóïñðíõÿôíöùíòïïòï„îíîèÙÊ»¬ŽpaRI?@”œ¤ª®¥Üÿüªÿýÿål„¬m–ýÿöb¬yÇûõøõöö‚õ€ôöõóôôòôñìðóïîòïðñîî€ïîíîèÙÊ»¬ŽpaRI??Œ’’¨¦¦ñÿþªÿýÿæk†«j—þÿúOfe·þôøõöö‚õ€ôóò€óòñóõòðòòð‚ïîíîèÙÊ»¬ŽpaRI@=‹‘“…žž¶ÿý«ÿýÿçl€­s”ûÿÜ)%§ÿóøõöö‚õôó€ò‚ñð‚ïîíîèÙÊ»¬ŽpaRI@±|˜ù­ÿýÿé€k[D&KúÿÏ#5A‹ÿñùõöö‚õôóòñð‚ïîíîèÙÊ»¬ŽpaRHC5-uµ†n­ÿúÿþªÿüÿéƒa6LI/ùÿÌ#@Orÿñùõöö‚õôóòñð‚ïîíîèÙÊ»¬ŽpaRI?>7_Pyƒôÿþ«ÿüÿé…bP[IHùÿÊ$FY(cÿðùõöö‚õôóòñð‚ïîíîèÙÊ»¬ŽpaRI@<&>Uxc°ÿúÿþ©ÿüÿéˆaTcVIùÿË&L];Nÿòøõöö‚õôóòñð‚ïîíîèÙÊ»¬ŽpaRIA93NXkkmkæÿüªÿüÿéŠ``m\RùÿÈ"R\R5ò÷ö‚õôóòñð‚ïîíîèÙÊ»¬ŽpaRHA=)Epfch[•ÿúªÿüÿé_gtbVûÿÙ.QXc-Ýýô÷õö‚õôóòñð‚ïîíîèÙÊ»¬Žq`QK?2#F`Sc]aXÐÿúÿþ§ÿüÿéŽ_nwfVÿþýLEXk*½ÿðùõö‚õôóòñð‚ïîíîèÙÊ»«œmbS@., ]ç¡OY\Oyýü©ÿüÿè]mw_ZÿÿúDFHf)›ÿíøõö‚õôóòñð‚ïîíîèÙÊ»­ŸŒ~t[;.&,žÿýÉQOYD´ÿùÿþ¦ÿüÿè•Ubnm?ÿùÿU*YcIzÿòû€÷öõõô€óôóòñð‚ïîííèØÌ¼§–‘Q.'(&'&ßÿöÿÖPKMTìÿþ§ÿüÿé–Z^n:Rÿýÿä: ;1Uûë€òðòôõõ÷ööõôóóòñòñòñð‚ïîî€íïêÙŹ¶ªy<$)*!%;üýÿùÿÝKG<‡ÿøÿþžÿþ€ÿ"üÿè>>Jañÿÿýÿêšt‡máÿüÿþþûøõóíðîïðñó€ôõôòðïïƒð ïìêãØÖÀ‹:/- ÿùÿÿûÿÞJ=<Èÿúÿþ“ÿþþÿþþ‡ÿ ýýøüíqäðýü€ÿûûÿüÿ賯ÅÖÖÕæìöøÿþþýùöôïîîëîîíïð‚ï(íìëìèéêìïõõÙ«»ÇËÏÍÌÏTOL¿ÿúþöýùÿáO.[ôüþÿþ—ÿ þþýûûùúúûÿþ€ÿøjÓÿúûüÿ üö÷öüöÑÚäß°Ô¨¯À ÍÂÔáêñûüúÿüùùöõóóòõ÷õúø÷ôèØ¸ª¹íÿþÿüQHAïõÿþþÿúÿÞC…ÿöÿþŠÿþ€ý ûúûûúøúüúþ„ÿ þÿóîÕÛ´~ÿõÿþ†ÿýþƒÿCþþ÷Úì¿ÆÆŸË¦ÀŹ֨Ùãèééêêéæß×ܯŏ±¼¬Ûøÿþù÷ùûöÿÅ1N9nÒ¶¼ÅÞðýýÿßG!žÿöûÿüü€ýüƒýŠÿüúóäèÏÑÅ«ÎÉÀÔèîÿþÿþƒÿ€þÿþøúûûøûý„ÿ:þÿëîá½ÓÙ¢­¼ÀÅÉÊü¦ŸßÈÝïðÿþÿþøýÿÿþÿ÷ÿy,I5YÿüôÕ˻½ÆÞ°“ÿüÿþˆÿüõôñçÜãßÝÃÇÔ«ºÓ®¨ÞÃãíìÿþÿûþ“ÿþúüøùûýþ’ÿþþúúþÿÿþýÿöÿ¯);;4Sýûÿ&üóêÑÙcO›ÓÅÄÏÍÆÄÀ¼¶ª£—¤×ÊDZ¥àÞÜËå÷íúÿþ„ÿüûøúûýŒÿþ€ÿþþ‡ÿüüû÷úûúûü‚ýüûùûùûüýÿþÿþÿûÿá1.462J÷ÿüúùüýÿ ö2 €öÿõðúøö‚÷û‰ÿþýüúûøûüüžÿ€þ–ÿþ€ÿ"þÿûÿà5%-+,7$ÌÿúÿþÿþýûüöÿÀ;¹üÿüþƒÿ þüüûúøûûúùûþüþ‡ÿþþ¢ÿ‚þ„ÿþ†ÿýÿë.+(.)4ÿ÷ÿýþ€ÿþÿöÿw3´þüúÿýþ‹ÿþÂÿûÿ[ +0uÿùÿþÿþÿÿúõ@Zïÿúÿþ‚ÿ€þÿÿþþÈÿþÿ÷ÿ£%9ßÖ2"Rÿúÿþ‚ÿþÿúÿÕ ¼ÿ÷ÿþÕÿûü4 5ïûÿ¼ 0óÿþ„ÿþÿôÿ¥ý¬'¼ÿùÿþÑÿþÿ÷ÿŒäÿúøÿH äÿü‡ÿûù5÷ÿÿí2»ÿùÿþÑÿýÿå ÿôÿøÿO"æÿý…ÿþÿùÿÈæÿöþî-àÿýÿþÎÿþÿøÿUMÿøÿÿøÿMåÿü†ÿþÿùÿY«ÿüûÿÇ5ùüÏÿþÿúÿÊ Þÿüÿÿýü3åÿü†ÿþÿûÿÐkÿôÿôÿŒ£ÿøÿþÌÿþÿøÿSmÿ÷ÿÿüÿ× 8ÿûŠÿ ûÿ. ëþþÿüî&üýÍÿþÿûÿÅ ßÿüÿÿ÷ÿ…ÿöÿþ†ÿþÿùÿ®ÿöÿöÿ‹¤ÿùÿþÊÿþÿ÷ÿWoÿ÷ÿýÿúÿA Òÿûÿþ‰ÿ ûý3Ùÿýþÿè5ÿûËÿþÿýÿäÝÿüÿÿüÿÙ<ÿûÿþˆÿþÿúÿ´Iÿ÷ÿùÿG×ÿûÿþÈÿþÿ÷ÿtoÿöÿýÿ÷ÿŠÿöÿþ‰ÿþÿøÿk‰ÿùôÿŒœÿ÷ÿþÉÿþÿòßÿýÿþÿýý- ÕÿûÿþŒÿûø6…ÿôÿœ‰ÿøÿþÇÿþÿ÷ÿ–iÿ÷ÿýÿúÿÂ=ÿûÿýþã*[Èå7KÿùÿþÇÿþÿöÿb¶ÿùÿýÿ÷ÿ]¡ÿøÿþŒÿþÿüÿä+ jÿ÷ÿþÉÿþÿ1%ôÿþÿìæÿýÿþÿúÿä=¤ÿ÷ÿþÊÿú#-þýÿþÿ÷ÿ’eÿøÿþÿ þÿûýùœT-„ÿüÍÿú#+þý€ÿ üý2½ÿùÿþÿþÿüúÿþúÿýËÿþÿüÿE/þýÿÿ÷ÿ¤Cÿúÿþ’ÿþÿþ÷øù÷þËÿþÿöÿd Ôÿúÿùÿ0¡ÿ÷ÿþ“ÿþþÿÿþÍÿþÿ÷ÿ’Äÿýùÿ #öþ˜ÿþÌÿþÿ÷ÿ¯†ÿóÿâÿøÿþéÿþÿüÿÜaÿñÿ7Eÿøÿþîÿö(<ÿÿb ÑÿûÿþëÿþÿøÿVƃœÿùÿþìÿþÿ÷ÿ¢ _ÿ÷ÿþñÿ ø! Pÿõÿþïÿ þÿûÿ¸Qéýýÿþÿÿÿÿÿÿºÿÿÿÿÿùÿ ú8J¼›¹èàûÿþÿû€úùƒö÷ƒùøý”ÿþöõöýœÿúƒùöõúúûûýÿþÊ # &3­ÿ4&<Q5r–¬ãÙÝÙöÿþÿ„þÿùõö÷ƒøööüþ€ÿþ÷Žöü‰þ‹ÿýÿíÔÛÔáŒ5P`hfj<Ä79,"3ALPQ>'643@€‚€ƒzšÑÈÌËÉÌÆìÿý’ÿõ˜w¡îÿþ˜ÿ3þÿÕÇËÈÊÇÍ“x}}v4547650/+--)$## ” œ…<$+KVz…𣩍¤{›¡‡7H1BWifa\ZWQPJHHA==<8541.,,*)'$" €  "#&$0 F '*3Pcj€’—¡¬²µ½´·®²¯³‘|«®œ‹1H8?f„Š„zytomgfe`^]YURSOLJHGFEDB><9€87653/+)('&''&&'(('$6Ê¥‰psVp`\gcVNOLFB:93.&+#CþÿýóÖ˯¡š”—œ›œ¢¦§©©«­¯°¯e®­®±´¸¹¸µ³±²´¶¸¹»¿¿ÄÅÃÈÃÍÑäÔ³“‚ÆÁ©F3ÌîåÜÑÌÁ»±­§  •“”•’‹‹‰‰ˆ‡…„‚~{zyywwvvwwvutrmf^YUNMID86%CûùþÿWúïàÒ¶ª§¨¡¡¦«®®­­®²·¼¿ÀÁÁÂÃÁÀÀÁÂÅÇÆÅÄÆÊÇÍÏ×àæóûþÿä°–}ºº¥§1‰ÈìíïïñííêããßÖÓÐËÆÁÀ¾»¹¸¶€´€³´³³´³²±±°°­¡”‹‰}vk\L85#Gÿýÿþûüüþ‚ÿýôìêãÝÙ€Ö××ÕÒσÌÎÐÒÔÓÑÒ×Ýáæìõ÷ý‚ÿüÿä®—x«­ŒÄ 1‹Êîëêéêìëë‚íëê€ë$ììëëêéèçæääåääååäååäæáп®ž{jYJ86#Kÿü‚ÿþý€üýŠÿýû€úùúúùü†ÿþþýýþÿýÿè©£M|¨‰Žé  2ŠÉíìëëêéèèçæåæå€äãââãââãã€âá€ßÞÞÙȹ¨™‰zjZK86"Pÿüÿÿþÿþ„ÿþýý€ü‚ýþŠÿþýýþýüýþþƒÿýÿꦧ@œwæ 2ŠÉíëêééêé€èç€æ€åä„ã€âáâÜ˼«œ‹zjZK87 Wÿû„ÿþþÿˆþÿýÿì§£H+#Âé 4‰Êíëê€éè€çæ€åä€ãâ€áàáÛÊ»ª›ŠzjZK87^ÿûÿþ¯ÿýÿEÜé 3†Êíëê€éè€çæ€åä€ãâ€áàáÛÊ»ª›ŠzjZK78fÿúÿþ¯ÿýÿð¥£F Ùé 1ƒËìì€ëê€éè€çæ€åä€ãâ€áàáÛÊ»ª›ŠzjZK78lÿùÿþ¯ÿýÿò¤£EÜé /€Ììì€ëê€éè€çæ€åä€ãâ€áàáÛÊ»ª›ŠzjZK79sÿøÿþ¯ÿýÿô¤£FÛé ,~Íìì€ëê€éè€çæ€åäããäãã‚â€áàáÛÊ»ª›ŠzjZK79xÿøÿþ…ÿšþŠÿýÿô¥¢FÜë *~Íìì€ëê€éè€ç€æååæåäææäããåãáââ€áàáÛÊ»ª›ŠzjZK79zÿ÷ÿþ¯ÿþÿõ¦¡FÜÿB)~Íìì€ëê€éè€çåæåççäæåÞàäããßàäãáàáÛÊ»ª›ŠzjZK7:|ÿ÷ÿþ…ÿû÷ø‚ù„ø‚ùƒøý‰ÿþÿõ§ GÜÿH'~Ììì€ëê€éèççèéçèäáçãäïìäããëèÜáã€áàáÛÊ»ª›ŠzjZK7:{ÿ÷ÿþ¢ÿþ‰ÿþÿö©žHÜÿG &Ììì€ëê€é€èçèçåáåáêîÛæß„µãßá·½ôãà€áàáÛÊ»ª›ŠzjZK7:{ÿ÷ÿþƒÿ ýÿа¸¶³²¸¼¾¾¼´ƒµ ½¾¾½¹³±µ¶·²ëÿý‡ÿþÿ÷ªHÜÿH %Ììì€ëê€éè+æçêóæö°nTŽŸ=Beka=¨¦¨ñÜãáàáÛÊ»ª›ŠzjZK7:{ÿ÷ÿþÿþÿûÿ;‘“ÿ÷ÿþ…ÿþÿ÷«œIÜÿH%Ììì€ëê€é/èèéèêçÙ“i²fBPIco`\\G[nBWÒâàâààÛÊ»ª›ŠzjZK7:{ÿ÷ÿþ‚ÿ$ýÿè KkvM ]‹€W' ]ÿøÿþ…ÿþÿø¬šJÜÿH&Ììì€ëê€é€è,äàÍYBOPrlFhY?PYdff_]EÄêÝãàáÛÊ»ª›ŠzjZK7:{ÿ÷ÿþ€ÿ$þÿùÿ»2£ìÿÿûÿ¬„ÿøÿþùÁ_(þý‡ÿþÿø­šJÜÿJ&Ììì€ëêééèìãô¾H¦oVY; gXè±\„‡Vgm]˜ÖÚã€àÛÊ»ª›ŠzjZK7:{ÿ÷ÿþ€ÿþÿöÿ„ ™ýþÿúöùõÿRÝÿóùø€ÿ êkÛÿûÿþ„ÿþÿø®šKÜÿH&Ììì€ëê2éèéåëÚ\IWi¯W“í¼ÎíèóßîÇDIYdjV¾éÞâàÛÊ»ª›ŠzjZK7:{ÿ÷ÿþ€ÿ'þÿúÿL$Ïÿùøýÿþÿ÷ÿ} åÿýÿþÿùùûÿ«¥ÿøÿþ„ÿþÿ÷¯™KÜÿ|&Ììì€ëê2ééìëçÆSaDkíáìæíèãâÞäááÊhclC^ààâààÛÊ»ª›ŠzjZK7:{ÿ÷ÿþƒÿùÞðö€ÿþýÿõÿŒ åÿüÿþ€ÿ þôÿ¹wÿ÷ÿþ„ÿþÿø°™LÜÿ–'Ììì€ë€êRéêæè—TjmAa×ååäåáãæåæäåâëàÜtB€ÞàâààÛÊ»ª›ŠzjZK7:zÿ÷ÿþÿÿþÿúÿËÏôšÿûÿþÿþÿöÿ— æÿýÿþÿ€þÿðÿs#ÿø†ÿþÿù°™LÜÿ“(Ììì€ë€ê éëåÿ}9_e¬Ûîæçç€æ/åäãåããàâßw9ŠëÜãàáÛÊ»ª›ŠzjZK7:zÿ÷ÿþÿÿþÿ÷ÿ®zÿ˃ÿý€ÿ þÿöÿ— åÿýÿþ€ÿþÿþÿïcïÿý…ÿþÿû°™LÜÿ–(Ììì€ë€êéê㯫qeVËðáèåææääåäãäåÞç}yëÜãààÛÊ»ª›ŠzjZK7:zÿ÷ÿþ‚ÿþÿøÿ²m÷ÿþÿÿþÿöÿ˜ åÿýÿþ‚ÿþÿü‹ÿü±™LÜÿ–(Ììì€ëêêëéëÝK]l`E«ðäèåææ€åäãâãâäãáâàáàáÛÊ»ª›ŠzjZK7:zÿ÷ÿþƒÿýùÿ“nÝÿüÿþþÿöÿ˜ åÿýÿþ„ÿýùŠÿý±™KÜÿ– )Ììì€ë€êëæípEjZBÕíäèåææ€åäãƒáàáÛÊ»ª›ŠzjZK7:zÿ÷ÿþƒÿýÿæƒvÆÿûÿþþÿöÿ˜ åÿýÿþ“ÿþ±™KÜÿ– *~Ììì€ë êêéìåó‡MoZ”åæçæ€åä€ãââãâ€áàáÛÊ»ª›ŠzjZK7:zÿ÷ÿþÿþÿúÿǯÿûÿþþÿöÿ˜ åÿýÿþ…ÿþ‰ÿþÿ±˜KÜÿ– +~Ììì€ë€ê ëèæ‡^k`­çåçæ€åä€ãâ€áàáÛÊ»ª›ŠzjZK7:yÿ÷ÿþƒÿüÿ§††£ÿýÿÿþÿöÿ˜ åÿýÿþ’ÿþÿ±™KÜÿž ,~Ììì€ëêéíäö¦Ahcg;¢öâë€åäå‚ä€ãâ€áàáÛÊ»ª›ŠzjZK7:yÿ÷ÿþ‚ÿþÿó“‘™ü€ÿ þÿöÿ˜ åÿýÿþ’ÿþÿ²—KÛÿÜ,~Íìì€ëêéíäö¡?ichRWÛæâ€èæåçåãåä€ãâ€áàáÛÊ»ª›ŠzjZK7:yÿ÷ÿþ€ÿþÿüÿØŽ™’–öÿþÿþÿöÿ˜ åÿýÿþ’ÿýÿ³—KÛÿÜ-}Íìì€ëêéìåõ§Chdg`Qºòìä€á æáãåâåäâãááââ€áàáÛÊ»ª›ŠzjZK7:yÿ÷ÿþ‚ÿüÿ»”ž™˜îÿýÿýÿöÿ˜ åÿýÿþ’ÿýÿ´–LÛÿÝ-}Íìì€ëêêéìåð¤]kdekZ}zÀûëïãîçâéâáäâáàááàáÛÊ»ª›ŠzjZK7:yÿ÷ÿþƒÿú§Ÿ¡Ÿžóÿþÿþÿöÿ˜ åÿýÿþ’ÿýÿµ•MÛÿÝ.}Íìì€ë€ê3ëçê…UiegfTBL¦ˆs¨ï³ÑìÎæçßáÞÝâäãâáààÛÊ»ª›ŠzjZK6:yÿ÷ÿþÿüÿä ¨¥¤¦ú€ÿ þÿöÿ˜ åÿýÿþ’ÿýÿ·”NÛÿÝ/|Íìì€ë€ê3ëèé_NldfgfekaI{ÊØäÚË»ª›ŠzjZK6:yÿ÷ÿþ€ÿüÿÛµ¿€ºûÿ þÿöÿ˜ åÿýÿþ’ÿýÿ¼QÛýÿ,-|Íìì€ëê2ééêãá¯RrREMYafihffedefibR´ÝÉ»ªšŠzjZK6:{ÿ÷ÿþÿÿýÿåÄÏÊÎÂóÿþ€ÿ þÿöÿ˜ åÿýÿþ’ÿýÿÁTÛúÿ7){Íìì€ëê€é;èèêääîíü·„Øè©yZ˜‹9BRibhiifeefYoʺ«›ŠzjZK6:}ÿ÷ÿþÿÿýÿ×ÍÓÑÏÎÿþÿ þÿöÿ˜ åÿýÿþ’ÿýÿÂŽTÛúÿ6 &{Íìì€ëê€éèèçéèåãáèñêäìîÛïØª¥ÏMDX`€fg^>¡Ã§œŠzjZK6:~ÿ÷ÿþÿþÿúÐ×ÖÚÏÜÿýÿ þÿöÿ˜ åÿýÿþ’ÿýÿÄŽUÛúÿ8 "zÍìì€ëê€é€è8çæèééäáäæâáåàåððëåÁxWvdhfeiI眊zjZK6:ÿ÷ÿþÿýÿíÑßÙàÑëÿýÿ þÿöÿ˜ åÿýÿþ’ÿýÿÅŽUÚúÿ6 wÍìì€ëê€éèççæåæèçåæçãæäßÞààíäÙÅQd€feÀ¿©›ŠzjZK6:€ÿ÷ÿþÿýÿáÚäàäÕ÷ÿþÿ þÿöÿ˜ åÿýÿþ’ÿüÿÆVÛúÿD tÎëì€ëê€éè€çææ€å.äååãäæåäãàÜëÃK\hdkWƒ¼©›ŠzjZK6:ÿ÷ÿþÿÿþÚäæèåÜÿþ‚ÿ þÿöÿ˜ åÿýÿþ’ÿüÿÇW ¥öÿƒrÎëì€ëê€éè€çæ€å€ä)ãââãäåÝír>kbiTOµ«š‹zjZK6:ÿ÷ÿþþÿôÛìéðäåÿý‚ÿ þÿöÿ˜ åÿýÿþ’ÿüÿÈŒX ”öÿ‚oÎëì€ëê€éè€çæ€åä€ã%áââãÌzbgfXF”²—ŒzjZK6:ÿ÷ÿÿýÿéâñíöãïÿý‚ÿ þÿöÿ˜ åÿýÿþÿþÿüÿÉ‹X •öÿƒnÎëì€ëê€éèççè€çæ€åä€ã%ââãßç˜\j^G^±«šŠzjZK6:€ÿ÷ÿþþÿáêòóúãùÿþ‚ÿ þÿöÿ— åÿýÿþÿþÿûÿÊ‹X ”öÿƒnÎëì€ëêéêêè€çæ€åä€ã$âäÞê¼CafbB…È¥‰zjZK6:€ÿ÷ÿýÿùÝðñùùåÿþƒÿ þÿöÿ˜ åÿýÿþÿþÿûÿËŠZ ”öÿ‚nÎëì€ëê€éçãâçæ€åä€ãâäÝí®@€eC朊zjZK6:uÿöÿüÿîÞñðüðìÿýƒÿ þÿöÿ• ãÿýÿþÿþÿûÿ̉Z ”öÿ… oÎëì€ëêééèìô÷é€çæ€åä€ã$âãàé«Lgakª±Á¨œŠzjZK6:vÿ÷ÿüÿáâîîøåöÿþƒÿ þÿöÿˆ ÙÿûÿþÿþÿûÿÍŠ[ ”öÿ‚ oÎëì€ë€ê éëæñÆ|±åççæç€æ€åä€ã!âäÞäØmfXLÉѸ¬šŠzjZK78kÿùÿÿüØåéìïá†ÿ ýÿ÷ÿzÍÿúÿþÿþÿûÿÍŠ[ ”÷ÿ§ nÎëì€ë€ê éëåðy3¢òäéèæ€åä&ããâäÝñºXcb=tÚȼªšŠzjZJ85$DÿþþÿðÓåãëäåÿý„ÿ þÿöÿ]´ÿøÿþÿþÿûÿÏŠ[ ”úÿÍkÏëì€ë€êëçíÖOjœºëãåèå忀å*äãääââãàíªFL\|lŽêŽª›ŠzjZJ94%>ýÿýÿáÒáÞèÙïÿýƒÿþõïÿäYÿðó÷ÿþÿüÿÒŠ[ ”ùÿÊfÏëì€ëêéêçÉpC< ÷€çèç€åäåãååãçÚÛ¡Rm[fÓÖÛÊ»ª›ŠzjZJ94&=ü€ÿÓÓÚÛàÕùÿþ…ÿ üÿU ¦ÿúÿþÿþÿüÿÔ‰[ ”úÿÍ^Íëì€ë€ê;ëçíÖfWqD–°±öÞääååäæáåáàÛè@‰sS@pÞâÜ˺ª›ŠzjZJ94&=üþÿõÇÓÔÙÖØÿþ„ÿý“^9h¾ÿûÿþŒÿþÿüÿÖ‰[ ”ùÿÍ XÌìì€ë‚ê9æî„Ad^†QyíäòèãææáëãéðèðnD\imxããÞÜÊ»ª›ŠzjZJ94&=üüÿåÂÑÍÖÌäÿý„ÿûƒg|…ƒ‚‚w`²ÿúÿþŒÿþÿýÿ؇[ ”úÿÎ TËìì€ë€ê;éëåóÃDef]YA¯Ü°ÜæâÛí¿àКz©e_G‰ëßäÝâÛË»ª›ŠzjZJ94'8öýÿÒÀËÊÐÆòÿþ¦ÿþÿýÿÙ‡[ ”ùÿÍPÌìì€ëê8èëåê¡Thk]RjcCIËZpK°_>NVW@[½åÝâáàÛÊ»ª›ŠzjZJ:1,(ÎÿøÂÀÅÇÇÊþ‡ÿôñô…öóðøÿþŽÿþÿýÿÚˆ\ ”úÿÕLÌìì€ëê9èëåõ­G_]mfifeS]nTI\YUX@ŽÄœâèàãáàáÛÊ»ª›ŠzjZJ:3)2›ü쳿½Å¼×ÿü©ÿýÿÛˆ\ ”ÿýÿ%DÌìì€ëê9èëåò´8WvICQZRg^b`[O—kN{ÉïëãÞâàáààÛÊ»ª›ŠzjZJ94&9™ÛÝ«½·À¶êÿý†ÿ‹þ’ÿýÿ݈] ”ÿýÿ%<Èíëê€éçë½Ããnq®^3mÁVBéØÕðæÞÞâã€áàáÛÊ»ª›ŠzjZJ93(6­º­µµ·¹û«ÿýÿ߇] ”ÿýÿ%1Ãîëìëëêéçñïçîéèó×¢ÓïÓÄíàåæÝâääâáàáÛÊ»ª›ŠzjZJ93)3——°¬´¬Çÿüªÿýÿâ†^ ”ÿýÿ%&¿ðêìëëê€éèèäåæãååáéòèáéëàæãâåãâáââ€áàáÛÊ»ª›ŠzjZJ93)3•›¤ª®¥Üÿüªÿýÿä„_ “ÿýÿ%¼ñêìëëê€éèèêéçèèçèåàäçãáå€äâãâ€áàáÛÊ»ª›ŠzjZJ:3)1Ž’’¨¦¦ñÿþªÿýÿå„_ ”ÿýÿ&¯óéíêëê€éèè‚ç æåçèåäææãää€ãâ€áàáÛÊ»ª›ŠzjZJ:3*.Œ‘”…žž¶ÿý«ÿýÿæƒ_ ‘ûÿÜ 5(  õèíêëê€éè€ç€æåä€ãâ€áàáÛÊ»ª›ŠzjZJ:2*+Ze€‚—Ôÿü«ÿýÿçƒ_ ”ùÿÍ%75–øçíêëê€éè€çæ€åä€ãâ€áàáÛÊ»ª›ŠzjZJ;1.!<±|˜ù­ÿýÿçƒ^-#IúÿÏ#8B„ùæîêëê€éè€çæ€åä€ãâ€áàáÛÊ»ª›ŠzjZJ;1.!)uµ†n­ÿúÿþªÿüÿé„`>Jañÿÿýÿêšt‡l×ÿôõòôðíéçãäâãååæèèçéç€æå€äããä€ãä€âßÝÖÊÆ®|3/- ÿùÿÿûÿÞJ=<Èÿúÿþ“ÿþþÿþþ‡ÿ ýýøüíqäðýü€ÿûûÿüÿè­§¼ÍËÊÚáéìøñõòîêèäãâßâááãâ€ã*ââáàÞßÜÝÝßâèæÊœxºÉËÏÍÍÏTOL¿ÿúþöýùÿáO.[ôüþÿþ—ÿ þþýûûùúúûÿþ€ÿøjÓÿúûüÿüö÷öüöÑÛä߯ҥª»šÄ¸ÊÖÝäîïïöïííêè€çææçèêèîëéæÚË­¤¸íÿþÿüQHAîõÿþþÿúÿÞC…ÿöÿþŠÿþ€ý ûúûûúøúüúþ„ÿ þÿóîÕÛ´~ÿõÿþ†ÿýþƒÿCþÿøÚì¿ÄĜƠ¸¼¯ÊÍÎÖÚÜÝÝÜÛÙÒÊл»¯«·ªÛøÿþùøùûöÿÅ1N9nÒ¶¼ÅÞðýýÿßG!žÿöûÿüü€ýüƒýŠÿüúóäèÏÑÅ«ÎÉÀÔèîÿþÿþƒÿ€þÿþøúûûùúý„ÿ:þÿìïá½ÒØ ©¸¼ÁÅÆ¿¸£žÞÈÝðñÿþÿýøýÿÿþÿ÷ÿy,I5YÿüôÕ˻½ÆÞ°“ÿüÿþˆÿüõôñçÜãßÝÃÇÔ«ºÓ®¨ÞÃãíìÿþÿûþ“ÿþúüøùûüþ“ÿýúúþÿÿþýÿöÿ¯);;4Sýûÿ&üóêÑÙcO›ÓÅÄÏÍÆÄÀ¼¶ª£—¤×ÊDZ¥àÞÜËå÷íúÿþ„ÿüûøúûýŒÿþ€ÿþþ‡ÿüüû÷úûúû„üúùûùûüýÿþÿþÿûÿá1.462J÷ÿüúùüýÿ ö2 €öÿõðúøö‚÷û‰ÿþýüúûøûüüžÿ€þ–ÿþ€ÿ"þÿûÿà5%-+,7$ÌÿúÿþÿþýûüöÿÀ;¹üÿüþƒÿ þüüûúøûûúùûþüþ‡ÿþþ¢ÿƒþ‚ÿ‚þ†ÿýÿë.+(.)4ÿ÷ÿýþ€ÿþÿöÿw3´þüúÿýþ‹ÿþÂÿûÿ[ +0uÿùÿþÿþÿÿúõ@Zïÿúÿþ‚ÿ€þÿÿþþÈÿþÿ÷ÿ£%9ßÖ2"Rÿúÿþ‚ÿþÿúÿÕ ¼ÿ÷ÿþÕÿûü4 5ïûÿ¼ 0óÿþ„ÿþÿôÿ¥ý¬'¼ÿùÿþÑÿþÿ÷ÿŒäÿúøÿH äÿü‡ÿûù5÷ÿÿí2»ÿùÿþÑÿýÿå ÿôÿøÿO"æÿý…ÿþÿùÿÈæÿöþî-àÿýÿþÎÿþÿøÿUMÿøÿÿøÿMåÿü†ÿþÿùÿY«ÿüûÿÇ5ùüÏÿþÿúÿÊ Þÿüÿÿýü3åÿü†ÿþÿûÿÐkÿôÿôÿŒ£ÿøÿþÌÿþÿøÿSmÿ÷ÿÿüÿ× 8ÿûŠÿ ûÿ. ëþþÿüî&üýÍÿþÿûÿÅ ßÿüÿÿ÷ÿ…ÿöÿþ†ÿþÿùÿ®ÿöÿöÿ‹¤ÿùÿþÊÿþÿ÷ÿWoÿ÷ÿýÿúÿA Òÿûÿþ‰ÿ ûý3Ùÿýþÿè5ÿûËÿþÿýÿäÝÿüÿÿüÿÙ<ÿûÿþˆÿþÿúÿ´Iÿ÷ÿùÿG×ÿûÿþÈÿþÿ÷ÿtoÿöÿýÿ÷ÿŠÿöÿþ‰ÿþÿøÿk‰ÿùôÿŒœÿ÷ÿþÉÿþÿòßÿýÿþÿýý- ÕÿûÿþŒÿûø6…ÿôÿœ‰ÿøÿþÇÿþÿ÷ÿ–iÿ÷ÿýÿúÿÂ=ÿûÿýþã*[Èå7KÿùÿþÇÿþÿöÿb¶ÿùÿýÿ÷ÿ]¡ÿøÿþŒÿþÿüÿä+ jÿ÷ÿþÉÿþÿ1%ôÿþÿìæÿýÿþÿúÿä=¤ÿ÷ÿþÊÿú#-þýÿþÿ÷ÿ’eÿøÿþÿ þÿûýùœT-„ÿüÍÿú#+þý€ÿ üý2½ÿùÿþÿþÿüúÿþúÿýËÿþÿüÿE/þýÿÿ÷ÿ¤Cÿúÿþ’ÿþÿþ÷øù÷þËÿþÿöÿd Ôÿúÿùÿ0¡ÿ÷ÿþ“ÿþþÿÿþÍÿþÿ÷ÿ’Äÿýùÿ #öþ˜ÿþÌÿþÿ÷ÿ¯†ÿóÿâÿøÿþéÿþÿüÿÜaÿñÿ7Eÿøÿþîÿö(<ÿÿb ÑÿûÿþëÿþÿøÿVƃœÿùÿþìÿþÿ÷ÿ¢ _ÿ÷ÿþñÿ ø! Pÿõÿþïÿ þÿûÿ¸Qéýýÿþÿÿÿÿÿÿºÿÿÿÿÿùÿ ü2 Fºœ¼êäüÿþÿû€úùƒö÷ƒùøý”ÿþöõöýœÿúƒùöõúúûûý€ÿþÿË›#®ÿ0 4m—¬äÙÝÙöÿþÿ„þÿùõö÷ƒøööüþ€ÿþ÷Žöü‰þ‹ÿýÿíÔÛÔጠ*;DCF&Å&1893"'643@€‚€ƒzšÑÈÌËÉÌÆìÿý’ÿõ™w¡îÿþ˜ÿ3þÿÕÇËÈÊÇÍ“x}}v454==;;96520€1 0.-)'%$#"##"€!t (̤‰psVp`\gcVNOLFB::620+% .ÿýþóÖ˯¡š”—œ›œ¢¦§©©«­¯°¯N®­®±´¸¹¸µ³±²´¶¸¹»¿¿ÄÅÃÈÃÍÑäÔ²“ĽŸ‹A(u³Ôʸ²¨¢™”‡‡„}{|{ytrqoonljhfda^]\\[Z€YWWVSOHA;5.(  0þø‚ÿúïàÒ¶ª§¨¡¡¦«®®­­®²·¼¿ÀÁÁÂÃÁÀÀÁÂÅÇÆÅÄÆÊÇÍÏ×àæóûþÿä°—{´´š‹§'q¯ÒÑÒÒÑÎÌÈÁÁ¼³°­§¢Ÿš—”’ŽŒ‹Š‰ˆˆ‡†…„ƒ‚~||xmbYTI@5' 5ÿüÿþûüüþ‚ÿýôìêãÝÙ€Ö××ÕÒσÌÎÐÒÔÓÑÒ×Ýáæìõ÷ý‚ÿKüÿ䮘u¡¤Ž…Ä (s±ÒÏÎÌÊÊÉÈÉÈÇÇÆÅÄÂÀ¿½½¼»º¹¸¶´²°®¬«©¨¨§¥¤££¡ œŒ~paRC4& 9ÿú‚ÿþý€üýŠÿýû€úùúúùü†ÿVþþýýþÿýÿè©£Mo˜w†é  (s°ÓÏÐÏÍÌÊÉÆÅÄÂÀ¿½¼¼»¹·¶µ³²±¯®®¬«ª©§¦¥¤¢¡ žœ•‡zl^PB4& ?ÿûÿÿþÿþ„ÿþýý€ü‚ýþŠÿþýýþýüýþþƒÿMýÿꦧAˆgqæ )s°ÒÐÏÎÌËÊÉÇÆÅÄÃÂÀ¿¾¼»º¹¸¶µ´²±°­¬«ª¨§¦¥£¢¢ Ÿž™Š|n_PB4& Fÿúÿþ‚ÿþþÿˆþÿMýÿì§£H&Âé *r±ÒÐÏÎÌËÊÉÇÆÅÃÂÁ¿¾½¼º¹¸·µ´³±°¯­¬«ª¨§¦¥£¢¡ ž˜‰{m_PB4& Mÿùÿþ¯ÿMýÿEÜé *o±ÒÐÏÎÌËÊÉÇÆÅÃÂÁ¿¾½¼º¹¸·µ´³±°¯­¬«ª¨§¦¥£¢¡ ž˜‰{m_PB4& Uÿùÿþ¯ÿMýÿð¥£FÙé )m²ÒÐÏÎÌËÊÉÇÆÅÃÂÁ¿¾½¼º¹¸·µ´³±°¯­¬«ª¨§¦¥£¢¡ ž˜‰{m_PB4&]ÿøÿþ¯ÿMýÿò¤£EÜé 'j³ÑÐÏÎÌËÊÉÇÆÅÃÂÁ¿¾½¼º¹¸·µ´³±°¯­¬«ª¨§¦¥£¢¡ ž˜‰{m_PB4&cÿ÷ÿþ¯ÿMýÿô¤£EÛé %i³ÑÐÏÎÌËÊÉÇÆÅÃÂÁ¿¾½¼º¹¸·µ´³±°®­¬«ª¨§¦¥£¢¡ ž˜‰{m_PB4&hÿöÿþ…ÿšþŠÿMýÿô¥¢FÜë #i³ÑÐÏÎÌËÊÉÇÆÅÃÂÁ¿¾½¼º¹¸·µ´³²±°®­«ª©§¦¥£¢¡ ž˜‰{m_PB4&lÿöÿþ¯ÿMþÿõ¦¡FÜÿB"i³ÑÐÏÎÌËÊÉÇÆÅÃÂÁ¿¾½¼º¹¸·¶µ²²°««¬«©¦¥§¥£¢¡ ž˜‰{m_PB4&mÿöÿþ…ÿû÷ø‚ù„ø‚ùƒøý‰ÿMþÿõ§ GÜÿH !i³ÑÐÏÎÌËÊÉÇÆÅÃÂÁ¿¾½¼»»¸¸´±´±°¶³¬ªª­«£¤¤¢¡Ÿž˜‰{m_PB4&mÿöÿþ¢ÿþ‰ÿMþÿö©žHÜÿG j³ÑÐÏÎÌËÊÉÇÆÅÃÂÁ¿¾½¼¹µ·´¹º­±¬p¬©¨°¥£¢  ž˜‰{m_PB4&mÿöÿþƒÿ ýÿа¸¶³²¸¼¾¾¼´ƒµ ½¾¾½¹³±µ¶·²ëÿý‡ÿMþÿ÷ªHÜÿH j³ÑÐÏÎÌËÊÉÇÆÅÃÂÁ¿¾¼¼½Â¸ÁcQz‡CD]dXB…ƒ‚­Ÿ¢Ÿž˜‰{m_PB4&mÿöÿþÿþÿûÿ;‘“ÿ÷ÿþ…ÿMþÿ÷«œIÜÿH j³ÑÐÏÎÌËÊÉÇÆÅÃÂÁÀ¾¿¼±~`—bGOMek^\]HZhFS𣠠ž˜‰{m_PB4&mÿöÿþ‚ÿ$ýÿè KkvM ]‹€W' ]ÿøÿþ…ÿMþÿø¬šJÜÿH j³ÑÐÏÎÌËÊÉÇÆÅÃÂÁ¿»¸¨TEQSngGaXDR[fegaZG’§ž¡ž˜‰{m_PB4&mÿöÿþ€ÿ$þÿùÿ»2£ìÿÿûÿ¬„ÿøÿþùÁ_(þý‡ÿMþÿø­šJÜÿJ!j³ÑÐÏÎÌËÊÉÇÆÅÃŽɠIjXW?…_T´nUpxWeka}ž¢Ÿž˜‰{m_PB4&mÿöÿþ€ÿþÿöÿ„ ™ýþÿúöùõÿRÝÿóùø€ÿ êkÛÿûÿþ„ÿMþÿø®šKÜÿH!j³ÑÐÏÎÌËÊÉÇÆÄÃÀõXJXf”S}»˜£·²¹«²™FK[feRŽ¥žŸ˜‰{m_PB4&mÿöÿþ€ÿ'þÿúÿL$Ïÿùøýÿþÿ÷ÿ} åÿýÿþÿùùûÿ«¥ÿøÿþ„ÿMþÿ÷¯™KÜÿ|!j³ÑÐÏÎÌËÊÈÇÆÅÆÄÀ§S_Ga¾µ»¶¹µ°¯«­ªª›\]jHV¢  ž˜‰{m_PB4&mÿöÿþƒÿùÞðö€ÿþýÿõÿŒ åÿüÿþ€ÿ þôÿ¹wÿ÷ÿþ„ÿ6þÿø°™LÜÿ–!j³ÑÐÏÎÌËÊÉÇÇÂÂ…QckF[°¹·µµ±±²±°­­«¯¦¤hFk€ 2ž˜‰{m_PB4&mÿöÿþÿÿþÿúÿËÏôšÿûÿþÿþÿöÿ— æÿýÿþÿ€þÿðÿs#ÿø†ÿZþÿù°™LÜÿ“"j³ÑÐÏÎÌËÊÈÇÈÂÕq>_e•³¿¹¸·¶µ³±°¯®¬«¨¨¥eAq¨ž¡ž˜‰{m_PB4&mÿöÿþÿÿþÿ÷ÿ®zÿ˃ÿý€ÿ þÿöÿ— åÿýÿþ€ÿþÿþÿïcïÿý…ÿMþÿû°™LÜÿ–"j³ÑÐÏÎÌËÊÉÇÇÀ—”leV©Â·»··µ´²±°¯­¬«ª©¥©if¨ž¡ž˜‰{m_PB4&mÿöÿþ‚ÿþÿøÿ²m÷ÿþÿÿþÿöÿ˜ åÿýÿþ‚ÿþÿü‹ÿKü±™LÜÿ–"j³ÑÐÏÎÌËÊÉÇǼK\l_F’¸º¸·µ´³±°¯­¬«©©§§¥¢£  ž˜‰{m_PB4&mÿöÿþƒÿýùÿ“nÝÿüÿþþÿöÿ˜ åÿýÿþ„ÿýùŠÿKý±™KÜÿ–#j³ÑÐÏÎÌËÊÈÈÄÈgGjZD°À¸º¸·µ´³±°¯­¬«ª¨§¦¤£¢¡Ÿž˜‰{m_PB4&mÿöÿþƒÿýÿæƒvÆÿûÿþþÿöÿ˜ åÿýÿþ“ÿþ±™KÜÿ– $j³ÑÐÏÎÌËÊÈÉÃÌ{Pm\‚€»(¹¸·µ´³±°¯­¬«ª¨§¦¥£¢¡ ž˜‰{m_PB4&mÿöÿþÿþÿúÿǯÿûÿþþÿöÿ˜ åÿýÿþ…ÿþ‰ÿLþÿ±˜KÜÿ– &j³ÑÐÏÎÌËÊÉÈÅÂ}ajb•¼»º¸¸·µ´³±°¯­¬«ª¨§¦¥£¢¡ ž˜‰{m_PB4&mÿöÿþƒÿüÿ§††£ÿýÿÿþÿöÿ˜ åÿýÿþ’ÿLþÿ±™KÜÿž &i³ÑÐÏÎÌËÉÊÃБCfdf@‹Ç·½··µ´³±°¯­¬«ª¨§¦¥£¢¡ ž˜‰{m_PB4&mÿöÿþ‚ÿþÿó“‘™ü€ÿ þÿöÿ˜ åÿýÿþ’ÿLþÿ²—KÛÿÜ&i³ÑÐÏÎÌËÉËÃÐŽBhdhRR³º¶¹·¶µ³³±¯®¬«ª¨§¦¥£¢¡ ž˜‰{m_PB4&nÿöÿþ€ÿþÿüÿØŽ™’–öÿþÿþÿöÿ˜ åÿýÿþ’ÿ)ýÿ³—KÛÿÜ'i³ÑÐÏÎÌËÉÊÃÏ“Fgdg`RœÃ½·³²±³€¯¬­¬ª¨§¦¥£¢¡ ž˜‰{m_PB4&nÿöÿþ‚ÿüÿ»”ž™˜îÿýÿýÿöÿ˜ åÿýÿþ’ÿLýÿ´–LÛÿÝ(h³ÑÐÏÎÌËÊÈÉÃÊ’`ieejYnlŹ»²¸²­±«ª«©¨§¥£¢¡ ž˜‰{m_PB4&nÿöÿþƒÿú§Ÿ¡Ÿžóÿþÿþÿöÿ˜ åÿýÿþ’ÿLýÿµ•MÛÿÝ)h³ÑÐÏÎÌËÊÈÈÅÅxWheffTDNug鑤´Ÿ­­¨§¤£¤¤£¢ ž˜‰{m_PB4&nÿöÿþÿüÿä ¨¥¤¦ú€ÿ þÿöÿ˜ åÿýÿþ’ÿ0ýÿ·”NÛÿÝ)h³ÑÐÏÎÌËÊÉÈÅÄZNkdfgfeiaKB`™DZ³n{ª©€­¦¡ŸŸ ž˜‰{m_PB4&nÿöÿþÿüÿÌ¥­«¤¼ÿýÿÿþÿöÿ˜ åÿýÿþ’ÿ7ýÿ¸“NÛÿÝ )h³ÑÐÏÎÌËÊÈÉÂΚ=\ggeefegigic\Ff^E[žvh|œ«§¤€ž˜‰{m_PB4&nÿöÿþ‚ÿþ¹¯°³¦Õÿüÿÿþÿöÿ˜ åÿýÿþ’ÿ$ýÿ¹’OÛÿÝ *h³ÑÐÏÎÌËÊÈÇÅÆ½˜]chgfge#fg^]dPVfTJ@s‘ao¦œž˜‰{m_PB4&oÿöÿþ€ÿýÿî±¹´¸­ëÿýÿÿþÿöÿ˜ åÿýÿþ’ÿLýÿ»’PÛÿÝ )g³ÑÐÏÎÌËÊÉÇÅÆÁϬCUbbgeggeeffghgdgghhf\HEf“™Ÿ—‰{m_PB4&pÿöÿþ€ÿüÿÛµ¿€ºûÿ þÿöÿ˜ åÿýÿþ’ÿ(ýÿ¼QÛýÿ,'g³ÑÐÏÎÌËÊÉÇÆÅľ¼–@bw_hdehi€e ceffeedga^hzK[œ—‰{m_PB4&qÿöÿþ€ÿ þÿʾ»Íÿý€ÿ þÿöÿ˜ åÿýÿþ’ÿ-ýÿ¾RÛúÿ7&g³ÑÐÏÎÌËÊÉÇÆÄÃÄÁÉ–³;GdhRUjghjg€e€f%ggddXH‘›‡|m_PB4&rÿöÿþÿÿþÿõÂÉÅɼáÿü€ÿ þÿöÿ˜ åÿýÿþ’ÿ0ýÿ¿RÛûÿ6&g³ÑÐÏÎÌËÊÉÇÆÅÃÂÀ½Äëq‰BOiTGO]cdgg€f%defibSŠ˜ˆ{m_PB4&tÿöÿþÿÿýÿåÄÏÊÎÂóÿþ€ÿ þÿöÿ˜ åÿýÿþ’ÿXýÿÁTÛúÿ7$g³ÑÐÏÎÌËÊÉÇÆÅÃÂÁÁ»»Á¿É—r¬¶‹iT€x?FShafihffefY]‰{m_PB4&vÿöÿþÿÿýÿ×ÍÓÑÏÎÿþÿ þÿöÿ˜ åÿýÿþ’ÿ5ýÿÂŽTÛúÿ6 !g³ÑÐÏÎÌËÊÉÇÆÅÃÂÁ¿¿¾º·µº¾¸³·¸ªµ¥ˆvƒŸMGYb€fg]Euk`PB4&wÿöÿþÿþÿúÐ×ÖÚÏÜÿýÿ þÿöÿ˜ åÿýÿþ’ÿXýÿÄŽUÛúÿ8 f³ÑÐÏÎÌËÊÉÇÆÅÃÂÁÀ¾½¼¼»·³´´°¯±¬®³³®©“fRmfgfegMdk`PB4&yÿöÿþÿýÿíÑßÙàÑëÿýÿ þÿöÿ˜ åÿýÿþ’ÿ#ýÿÅŽUÚúÿ6 d³ÑÐÏÎÌËÊÉÇÆÅÃÂÁ¿¾½¼º€¸¶´´³°°­©¨¨§®§Ÿ”Sc€fc…}l_PB4&zÿöÿýÿýÿáÚäàäÕ÷ÿþÿ þÿöÿ˜ åÿýÿþ’ÿWüÿÆVÛúÿD b´ÑÐÏÎÌËÊÉÇÆÅÃÂÁ¿¾½¼º¹¸¶µ´³±°¯®­­ª©¥£©‘J[hdiYf|l_PB4&{ÿöÿþÿÿþÚäæèåÜÿþ‚ÿ þÿöÿ˜ åÿýÿþ’ÿ0üÿÇW ¥öÿƒ`´ÑÐÏÎÌËÊÉÇÆÅÃÂÁ¿¾½¼º¹¸·µ´³±°¯®¬ªª€¨#¡ªbEidhULym_QB4&{ÿöÿþþÿôÛìéðäåÿý‚ÿ þÿöÿ˜ åÿýÿþ’ÿWüÿÈŒX ”öÿ‚^´ÑÐÏÎÌËÊÉÇÆÅÃÂÁ¿¾½¼º¹¸·µ´³±°¯­¬«ª¨§¦¤¤•ldfgVIjp]QB4&|ÿöÿÿýÿéâñíöãïÿý‚ÿ þÿöÿ˜ åÿýÿþÿYþÿüÿÉ‹X •öÿƒ]´ÑÐÏÎÌËÊÉÇÆÅÃÂÀ¿¾½¼º¹¸·µ´³±°¯­¬«ª¨§¦¥¢¥|`i^IUwm_PB4&{ÿöÿþþÿáêòóúãùÿþ‚ÿ þÿöÿ— åÿýÿþÿþÿûÿÊ‹X ”öÿƒ]´ÑÐÏÎÌËÊÉÇÆÅ€Ã7Á¾½¼º¹¸·µ´³±°¯­¬«ª¨§§¢¨F`g`Ggk`PB4&{ÿöÿýÿùÝðñùùåÿþƒÿ þÿöÿ˜ åÿýÿþÿXþÿûÿËŠZ ”öÿ‚ ]´ÑÐÏÎÌËÊÉÇÆÅÄÁ½»¾½¼º¹¸·µ´³±°¯­¬«ª¨§§¢ª…EbgcHlk`PB4&pÿöÿüÿîÞñðüðìÿýƒÿ þÿöÿ• ãÿýÿþÿXþÿûÿ̉Z ”öÿ… ^´ÑÐÏÎÌËÊÉÇÆÅÃÅÊÊ¿½¼º¹¸·µ´³±°¯­¬«ª¨§§£¨‚Mecf€|~l_PB4&lÿöÿüÿáâîîøåöÿþƒÿ þÿöÿˆ ÙÿûÿþÿUþÿûÿÍŠ[ ”öÿ‚ ^´ÑÐÏÎÌËÊÉÇÇÂɧp—¼½¼º¹¸·µ´³±°¯­¬«ª¨§¦¢¤žjfVMŒzn^PB4&]ÿøÿÿüØåéìïá†ÿ ýÿ÷ÿzÍÿúÿþÿWþÿûÿÍŠ[ ”÷ÿ§]´ÑÐÏÎÌËÊÈÇÇÂÉn:ŒÆ»½»¹¸·µ´³±°¯­¬«ª¨¨¢­TccEb—‡|m_PB4& 3ÿýÿÿðÓåãëäåÿý„ÿ þÿöÿ]´ÿøÿþÿWþÿûÿÏŠ[ ”úÿÍ[´ÑÐÏÎÌËÊÈÈÄȵNf‰Á¹¹º·¶µ´³±°¯­¬«©©¦­„HL_rap †|m_PB4& )ÿþþÿáÒáÞèÙïÿýƒÿþõïÿäYÿðó÷ÿWþÿüÿÒŠ[ ”ùÿÊW´ÑÐÏÎÌËÊÈÇÆÅÁ«mF@ŠÇº¹¸¸¶´³²°°®­¬ª«¢¢Sj[[——˜‰{m_PB4& 'þþÿÿÓÓÚÛàÕùÿþ…ÿ üÿU ¦ÿúÿþÿVþÿüÿÔ‰[ ”úÿÍQ³ÑÐÏÎÌËÊÈÈÅȵ_WnG‚””ijµ´´²±±­®ª©¥¬tDqnVD`ž˜‰{m_PB4& &þýÿõÇÓÔÙÖØÿþ„ÿý“^9h¾ÿûÿþŒÿVþÿüÿÖ‰[ ”ùÿÍ L²ÒÐÏÎÌËÊÈÇÇÂÇvCc`|Pl½·¾¶³³²®³­°³­°`H^had¡ œ˜‰{m_PB4& 'þûÿåÂÑÍÖÌäÿý„ÿûƒg|…ƒ‚‚w`²ÿúÿþŒÿVþÿýÿ؇[ ”úÿÎ H±ÒÐÏÎÌËÊÉÇÇÁ˦Gdf`XC“±’¯µ±«¶—«Ÿ}h‰b\Lr© ¡ž—‰{m_PB4& !øüÿÒÀËÊÐÆòÿþ¦ÿTþÿýÿÙ‡[ ”ùÿÍE²ÒÐÏÎÌËÊÉÇÆÆÁÃVgj]Si`DJx¢UcKŽXCN\YDUޤž ž˜‰{m_PB4& ÐÿùÂÀÅÇÇÊþ‡ÿôñô…öóðøÿþŽÿUþÿýÿÚˆ\ ”úÿÕA²ÒÐÏÎÌËÊÉÇÅÆÀË•H^_lehfcU]lUK[_VUFx–|¥§¢¢ ž˜‰{m_PB4& žûí³¿½Å¼×ÿü©ÿSýÿÛˆ\ ”ÿýÿ& ;²ÒÐÏÎÌËÊÉÇÅÆÀÉš>ToMES\Se_d_YQƒaMi˜®«¥¡¢  ž˜‰{m_PB4& #œÚÝ«½·À¶êÿý†ÿ‹þ’ÿQýÿ݈] ”ÿýÿ%4¯ÓÏÏÎÌËÊÉÇÆÅÄÁࣻqdg“X:cRDm°¤¢²ª¥£¥¤¢¡Ÿž˜‰{m_PB4&  ¬»­µµ·¹û«ÿRýÿ߇] ”ÿýÿ%+«ÔÏÐÍÌËÊÉÇÆÅÃÂÀÆÃ½Á¼ºÁ¬ˆ¨¹¥›µ«­­¦§¨§¥£¢¡ ž˜‰{m_PB4&  –—°¬´¬ÇÿüªÿRýÿâ†^ ”ÿýÿ&!§ÕÎÐÍÌËÊÉÇÆÅÃÂÁ½¼½¹¹·µ¸½¶°´´¬¯¬ª«©§¦¥£¢¡ ž˜‰{m_PB4& ˜š‘¤ª®¥ÜÿüªÿRýÿä„_ “ÿýÿ$¤ÖÎÐÍÌËÊÉÇÆÅÃÂÁÁ¿½½»º¹¶²³´°®°­­«©¨§¦¥£¢¡ ž˜‰{m_PB4& ‘’§¦¦ñÿþªÿQýÿå„_ ”ÿýÿ&™ØÍÐÍÌËÊÉÇÆÅÃÂÁ¿¾½¼º¹¸··´³²±¯®¬«ª¨§¦¥£¢¡ ž˜‰{m_PB4& ”…žž¶ÿý«ÿQýÿæƒ_ üÿÝ ŒÚÌÑÍÌËÊÉÇÆÅÃÂÁ¿¾½¼º¹¸·µ´³±°¯­¬«ª¨§¦¥£¢¡ ž˜‰{m_PB4& Ze€‚—Ôÿü«ÿOýÿçƒ_ ”ùÿ͆ÛÌÑÍÌËÊÉÇÆÅÃÂÁ¿¾½¼º¹¸·µ´³±°¯­¬«ª¨§¦¥£¢¡ ž˜‰{m_PB4& :±|˜ù­ÿRýÿèƒ^;úÿÏwÜËÑÍÌËÊÉÇÆÅÃÂÁ¿¾½¼º¹¸·µ´³±°¯­¬«ª¨§¦¥£¢¡ ž˜‰{m_PB4& "v´†n­ÿúÿþªÿQüÿé„_$ùÿÍ cÛÌÑÍÌËÊÉÇÆÅÃÂÁ¿¾½¼º¹¸·µ´³±°¯­¬«ª¨§¦¥£¢¡ ž˜‰{m_PB4& :]Qyƒôÿþ«ÿSüÿé…`)1!ùÿÌ-VÝËÑÍÌËÊÉÇÆÅÃÂÁ¿¾½¼º¹¸·µ´³±°¯­¬«ª¨§¦¥£¢¡ ž˜‰{m_PB4&  6ÿþþ-$=„àÇÐÌËÊÉÇÆÅÃÂÁ¿¾½¼º¹¸·µ´³±°¯­¬«ª¨§¦¥£¢¡ ž˜‰{m`RB4( žÿýÉQOYD´ÿùÿþ¦ÿUüÿè•THQF"ÿøÿP-9,jÞÌÒÍÌÌÊÈÆÄÃÂÀ¿¾½¼º¹¸·µ´³±°¯­¬«ª¨§¦¥£¢¡ ž˜‰}m[LC3!ßÿöÿÖPKMTìÿþ§ÿWüÿé–X>HJÿþÿæ-=ÖÅËÉÈÅÆÅÅÄÅÃÃÁ¿¾¼º¹¸¶µ´³±°¯­¬«ª¨§¦¥£¢¡Ÿžž™ˆvngY;;üýÿùÿÝKG<‡ÿøÿþžÿþ€ÿüÿèž=)7\òÿÿýÿì˜k|b¼ãÕÕÓÔÏËÇž¿ƒ»4º¹¹¶µ´²±°®­¬«©¨§¥¥£¡Ÿœš”‹†kG /- ÿùÿÿûÿÞJ=<Èÿúÿþ“ÿþþÿþþ‡ÿ ýýøüíræóýû€ÿ(üûÿüÿ蟔¥²°«¸½ÃÅÏÉÍÉÅÁ¿¹·µ±²±¯®®­«ª©¨¦¤¢¢Ÿ ¢¥ „`X·ÍËÏÎÍÏTNL¿ÿúþöýùÿáO.[ôüþÿþ—ÿ þþýûûùúúûÿþ€ÿøjÓÿúúüÿýö÷õûøÓÛãÝ«Êœš¨ƒ¦—¢¬±¶€¿ľ»»¸µ´²°¯¯­®¯ª®¨¥Ÿ“‰w†³ðÿþÿüQIAîõÿþþÿúÿÞC…ÿöÿþŠÿþ€ý ûúûûúøúüúþ„ÿ þÿóîÕÛ´~ÿöÿþ†ÿýþƒÿCþÿúÝí¿À½³‰˜š‡žœ¡£¤¢¢¡Ÿœ•”‚Šƒ‰ŸÜùÿýùøùûöÿÅ1N9nÒ¶¼ÅÞðýýÿßG!žÿöûÿüü€ýüƒýŠÿüúóäèÏÑÅ«ÎÉÀÔèîÿþÿþƒÿ‚þùúûûøúüþþ‚ÿ:þÿïñã½ÎÒ™©«®±±«§–•ÖÄßôóÿþÿü÷ýÿÿþÿ÷ÿy,I5YÿüôÕ˻½ÆÞ°“ÿüÿþˆÿüõôñçÜãßÝÃÇÔ«ºÓ®¨ÞÃãíìÿþÿûþ“ÿþúüøøúûüþ‘ÿýûùúþÿÿþýÿöÿ¯);;4Sýûÿ&üóêÑÙcO›ÓÅÄÏÍÆÄÀ¼¶ª£—¤×ÊDZ¥àÞÜËå÷íúÿþ„ÿüûøúûýŒÿþ€ÿþþ‡ÿüýû÷úúøúú‚ûúùøúøûýýÿþÿþÿûÿá1.462J÷ÿüúùüýÿ ö2 €öÿõðúøö‚÷û‰ÿþýüúûøûüüžÿþ“ÿ€þ€ÿ"þÿûÿà5%-+,7$ÌÿúÿþÿþýûüöÿÀ;¹üÿüþƒÿ þüüûúøûûúùûþüþ‡ÿþþ¢ÿþ†ÿýÿë.+(.)4ÿ÷ÿýþ€ÿþÿöÿw3´þüúÿýþ‹ÿþÂÿûÿ[ +0uÿùÿþÿþÿÿúõ@Zïÿúÿþ‚ÿ€þÿÿþþÈÿþÿ÷ÿ£%9ßÖ2"Rÿúÿþ‚ÿþÿúÿÕ ¼ÿ÷ÿþÕÿûü4 5ïûÿ¼ 0óÿþ„ÿþÿôÿ¥ý¬'¼ÿùÿþÑÿþÿ÷ÿŒäÿúøÿH äÿü‡ÿûù5÷ÿÿí2»ÿùÿþÑÿýÿå ÿôÿøÿO"æÿý…ÿþÿùÿÈæÿöþî-àÿýÿþÎÿþÿøÿUMÿøÿÿøÿMåÿü†ÿþÿùÿY«ÿüûÿÇ5ùüÏÿþÿúÿÊ Þÿüÿÿýü3åÿü†ÿþÿûÿÐkÿôÿôÿŒ£ÿøÿþÌÿþÿøÿSmÿ÷ÿÿüÿ× 8ÿûŠÿ ûÿ. ëþþÿüî&üýÍÿþÿûÿÅ ßÿüÿÿ÷ÿ…ÿöÿþ†ÿþÿùÿ®ÿöÿöÿ‹¤ÿùÿþÊÿþÿ÷ÿWoÿ÷ÿýÿúÿA Òÿûÿþ‰ÿ ûý3Ùÿýþÿè5ÿûËÿþÿýÿäÝÿüÿÿüÿÙ<ÿûÿþˆÿþÿúÿ´Iÿ÷ÿùÿG×ÿûÿþÈÿþÿ÷ÿtoÿöÿýÿ÷ÿŠÿöÿþ‰ÿþÿøÿk‰ÿùôÿŒœÿ÷ÿþÉÿþÿòßÿýÿþÿýý- ÕÿûÿþŒÿûø6…ÿôÿœ‰ÿøÿþÇÿþÿ÷ÿ–iÿ÷ÿýÿúÿÂ=ÿûÿýþã*[Èå7KÿùÿþÇÿþÿöÿb¶ÿùÿýÿ÷ÿ]¡ÿøÿþŒÿþÿüÿä+ jÿ÷ÿþÉÿþÿ1%ôÿþÿìæÿýÿþÿúÿä=¤ÿ÷ÿþÊÿú#-þýÿþÿ÷ÿ’eÿøÿþÿ þÿûýùœT-„ÿüÍÿú#+þý€ÿ üý2½ÿùÿþÿþÿüúÿþúÿýËÿþÿüÿE/þýÿÿ÷ÿ¤Cÿúÿþ’ÿþÿþ÷øù÷þËÿþÿöÿd Ôÿúÿùÿ0¡ÿ÷ÿþ“ÿþþÿÿþÍÿþÿ÷ÿ’Äÿýùÿ #öþ˜ÿþÌÿþÿ÷ÿ¯†ÿóÿâÿøÿþéÿþÿüÿÜaÿñÿ7Eÿøÿþîÿö(<ÿÿb ÑÿûÿþëÿþÿøÿVƃœÿùÿþìÿþÿ÷ÿ¢ _ÿ÷ÿþñÿ ø! Pÿõÿþïÿ þÿûÿ¸Qéýýÿþÿÿÿÿÿÿºÿt8mk@vþÒŒWC)  Rd’Àñ±{ÿüÿÿÿÿûôéÙz4'    =ëÿþÿÿþÿäüüþýú÷ùþÿÿÿÿÿýøëÛÌÆ¼¨’ym_RE;30,% 0Z0",03@Uky“ª¾ÇÏßðúÿþ÷øúúÿóÿ…ðÿþÿÿÿÿÿþýúøûþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüôëâÜÚ×ÏÆ¹« —’ƒsaQGB84:>BKp—Ëýþÿ·Œ…~rmmmmmpv}ƒˆŒ’’–šž¤ª±¸¿ÅÊÐ×ÙÞçñúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüýÿÿÿþÿøÿ¨åÿþÿþþþÿÿÿÿÿÿÿÿýûúùøø÷öö÷øøúûûûüþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüøüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýûûúø÷öö÷÷ùùúüþÿÿÿþþþýÿ÷ÿ¨Ïÿýÿÿÿÿÿÿÿþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþýüûûûúùøø÷öööö÷øùùùûüûúùù÷öúÿÿÿøööö÷÷÷÷÷÷÷÷÷öööööööö÷÷÷øøøùùúúûûûýþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿøÿª²ÿûÿþÿÿÿÿÿÿÿÿÿÿÿþþþþþþþþþþþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþþþþþþþþþÿÿÿÿÿÿÿþÿ÷ÿªœÿúÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþþþþþþþþþþþþþþþþÿÿþþþþþþþÿÿÿþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿøÿ«Uÿûÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿøÿ­òÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿøÿ«òÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿøÿ±ðÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿ÷ÿëÿýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõ# ãÿüÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿýÿêÚÿûÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿíÏÿúÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿíÄÿúÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿî¼ÿùÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿï¶ÿøÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿï³ÿøÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿð¯ÿøÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿñ¦ÿ÷ÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿò›ÿ÷ÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿòŽÿ÷ÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿóÿ÷ÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿôvÿ÷ÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿôlÿ÷ÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõfÿöÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿödÿöÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿö]ÿ÷ÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷Tÿøÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷Iÿùÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿø>ÿûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿù3ÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿù*þýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿú"üþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúüþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþû òÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþû êÿýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþü! áÿüÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþü!×ÿûÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþý!Ïÿúÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþý!Èÿùÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþý!Åÿùÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþý!Âÿùÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþý!¹ÿøÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþý!®ÿøÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþý" ÿ÷ÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþý"‘ÿ÷ÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþý"…ÿ÷ÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþ" |ÿöÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþý" yÿöÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþ# sÿöÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþ$iÿ÷ÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþ$\ÿøÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþ%Mÿùÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþ&Bÿûÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþ&7ÿûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýþ'0ÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýþ(-ÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýþ)'þþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿ*÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿ*îÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿ+ äÿüÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿ,Ýÿüÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿ-×ÿûÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿ.Õÿúÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿ/Ðÿúÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿ/Æÿùÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿ0¹ÿøÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿ2­ÿøÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿ2ÿ÷ÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿ7Õÿûÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿ3/ÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿúÿŸ(þþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿü!øÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúîÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ ãÿüÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÛÿûÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÕÿúÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþþÒÿúÿýþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿú¼ÿùÿþÿþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþý÷ûòqÿóúõøÿÿÿÿÿÿþþþþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþÿÿÿÿÿÿÿÿþýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþûÿùÿ€¢ÿüÿþùùüýþÿÿÿÿÿÿÿÿÿþþþþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþþþÿÿÿøúüüýþÿÿÿÿÿÿþþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþþþþÿÿÿþùâÅ\9py©ÿýÿÿÿüø÷÷÷øüýÿÿÿÿÿÿÿÿÿÿþþþþþþþþþþþÿÿÿÿÿÿÿý÷üÿÿÿÿÿüùììÿþÿÿÿÿÿÿþþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþþþþþþÿÿÿÿÿÿÿÿþÿôÿ† «Íáîôþÿÿÿÿÿÿÿüù÷÷÷úûýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûù÷÷úÿþþ«9./-#*ÿöÿúöøüÿÿÿÿÿÿÿþÿÿÿþþþþþþþþþþþþþþþþþþþÿÿÿÿÿÿÿÿÿÿÿÿÿþûú÷÷÷ùÿûÿÌ  0P_Š ¼Ýìþÿÿÿÿÿÿÿþüúø÷÷÷÷÷÷÷øúüþÿÿÿÿÿæ”.¹ÿùúüÿÿÿýøö÷úýÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþüùù÷ö÷øúüÿÿÿÿÿÿÿýåÞ94Fi‹¢ËÙìýÿÿÿÿÿÿÿÿÿÿÿÿÿøÜÆžB êüÿ'{¨Þýÿÿÿÿÿþúþþÿ÷ôùøøøø÷÷÷÷öööö÷÷øùüþÿÿÿÿÿÿÿÿÿþÿôÚÈžˆbC- (0?ZfmquuoeR?4! iÿïÿ… )T{¦ËëþÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýøôäÊ¿¦‚u_;/ !æÿöÿŸsÿùû£“²¯ª©ª¦£—ˆ‚shaYF0&    Ìÿþúÿ¶ ¡ÿúÌ. ÀÿõÿüÿçÞÿûÿŸ   ¨ÿñÿüýüÿ;JÿóúÿïzÿðÿÒßÿóÿn‘ÿ÷üýþûs)øöÿ†¬ÿÿ ÒÿúÿäÀÿÅ ©ÿÿž çÿ¾Gÿøþ8ÀãHþÿÁ¦ÿÔ •ÿÌ¢ïÊÿó/‡ÿÖ øÙ ®×iÿÿ ‡ÿÜŠÿ$ç” ÔÿÐ §ÿÈÿn @ÿ5kÿÿbÚÿ— ¤Ö ÀÀ  ÒÿÌBÿÿH+ÿK?ÿ2kÿÿ_…ÿÙ ŸÔá   ÒÿÌ Ñÿ›òƒ —óhÿÿbOÿÿTVÿAVÿ3ÄÿÏ £ÿÙÿ? Lÿ[6ùÿkèÿ²ÿc ˜ÿtwÿåeÿú:®ÿØÐñÿn ¢ÿ¶¸ÿ‚þþÿü.¶ÿ–>øÿq-ˆ®Q³ÿ’œÿä•ÿ°(ëÿ•rÿÐÿö6Jÿð#;øÿ¨)öÿEÇÿò- ×ÿq‹ÿÿ‹¯ÿœYþþЃÿá Eüõú6?ÿûÚò÷ÿg¸ÿûûÿƒÙýøtic08˜Ð jP ‡ ftypjp2 jp2 Ojp2hihdrcolr"cdefjp2cÿOÿQ2ÿR ÿ\ PXX`XX`XX`XXXPPXÿdKakadu-v5.2.1ÿ —ãÿ“ÏÁ=:Wiþ…²T'&›˜›¡-cz„±Ã «”– N˜ÎÓ<âê0Èê¿®IÀÔ_OütÙëpPÕåÚ»ÙdÝÛöÄI°ÇÚJø±#Þ,B¢c åÁ ´ÏÆ—¦ÑŒIÄÙŸ<Ä®ôK•TÇÚRx­s!rÇÞ’ºHã“@£±œk»öu­þ•YóLÔÿ&ŽîlÀ CÏÁzÃlmá:M@æö"g2ÃxFî+°RWmÔ¼žQÙ'úBr©›M_¿ÑOŠß'XúSºõö ô[\ÉÇàƒ‚ ~h "éŸâ/öV"ïì=.H:Zk·ËüW’+?öCÎ3"’o)¼Î‹Jâ˜2Ì1KÁõÃí#õ µ$öL w·Œ*È¥Íþ¨«xÞ:ìÞŸ .þóóõ’KE,™ôˆÆ@Š[Ø6t@>K­% T¨á>§öŒ:ÈúéVWR‹65¶µ?M`\$h² ÉšæåT– ºÚÕ©pHçÄ[&ÇàY\?€+K¯³Ò•—Dl|\âyvLðYª¨ ×uÈb+Æ÷àÝÍilLM€sÞ=C/l;5›%»<©PËÆø¦l„GŸË.WÝXz;÷÷?GJQøi7D…\‘€ydöÆüRvƒÛÃÇ& ¤ÎT£HêÎ}ÆÀõ–l‘÷ÂdÕüÞ®{Ñ Ð}OzwBì92Ô 6™Þ&T$(.YÙºE?$7? Ðô'j„G…øäŸ;“Œˆ+ý7„†³™±­2PËGHFãNJˆå6A w‰ƒñ ›È 6%"¦ €…ðÑ[Ö‡+KÓè(ÇàǵnµHb¿ö`Œ ¸ì$b5¸ÿ%ç{êÚzi a®.æ§&Ë&–õ¾ˆióSLáÇìõ½Ä}Ñ]dÙ¤¥óLÖ’K™¸GÑ/¶Ýƒ #ƒŸ7¶šÂ™*oª§nÿ!iN>E1Ï(‡DUÒaÙ‚¡M’þ|Ò¥{£¬Š¬¼W]'³1dß…Qò%™«“µ5¼°­«4SÂmHÜœñ˜ ±Š&‰Ñ´ú¨];nü0gí‡ }ÚìœJÇ3p]àò,/’Þøß’˜ù½ø~ðÊfN)ô!»vì†gqûµòæ¿æôΗùñÅ‹'ÒV¥>Ä6„aÍÛ­Ø,甬a_°Åf;¦ƒ™‚ž°PЙgÏͶ?ˆüÙ@¤w(³«pkôµ ”m+«;#iÔæÎ}*0—ê¨ä3ü¶žlÚÕým´B%AS?p›ÇxúÖ~ Næ¢Wî;¼«’ñ!Zü8%Xo/s}—îºp¹£i›+¬ú±oŸÒE7ˆÙ†ó€Ž@­š 3 ¬›vYy‚‰}cF­¢y2ÝEºÇÿXitáo4ÛøžAlR\ù”ë_)Ÿr{søx¡ÿJûšš‘qI–¨&ÍîöL <2{¡u]x¤ä¡•h^l;Ü’'ŒÚ,_V"šÞí“`ãÛÞgoª±Þç™ë¤Õþ®ŽH '„û#ˆ«9Žç$"¨OФ¥•YèÑ“³eñpÕœ'þYÁòŠóבÆà¸èU%²ñ;G" ôçÑÉSÕe‘øú£qL.j+Ú? ¦JÏn*Åý „áûŽ-éÏϦ©ùôEŸB€àSYptQ-ÿ/«gµ¥W¢áÆz‰cÌJôA—z~õӪѲÛÁF{ÿ65¿§ö9‰fŠ «úÏ Zróž<úŒ @Po±zeŸ4Ý“eÇërµ¤Z&Ú—¦í¸ûôu”b` Ì_išˆ;£¢5÷çÃ$¢…;mI¸ÑQÐiÈ·xñ˜¿p8o"OQï;TÏD ¡…‘”V·ùòì  åÖ«5„ø>])Îjý\tòûòÄ+"²Èƒo!®A½Øÿ\àÊaìgi*u Ñ9åYüï byŠ PýìôiiPZüÐ?Àtû Èb£ÅÍ”–¨QûÅç ?¾Ž¹$¥ï –¿¤ {™%˜y`h²<FàÀ¯‘Q ,Z9d™G é%à ÎØ¡#Y®L£3¬¸®@y—Ïy.Ì£T˜‰ÇK{Ï$÷5Ç?u÷Kun÷k­«=¼·ìš/š¸hG„|_Ý«®5Ó’Ì]ÂT5ô§ ¼œW*~Ÿ õ–ì­a©X>¹®7X}M¦†K‡ó}ƒ$®•€Dt“æ}&lðˆŠÝ<‚7ðàþ‘\㊡&WèyZŒf&züéùQK ¿­¥Y×þÛ¿¶9 0°xñs‘!{Z¡(’¤šÿY˜Îë;;DŒî®’3o-g3Û}KEÊC™‡ö>¸é5ùHé ýí>áòŠnP å3Oì…èNsݳ¦Aßu€Ôžš®‰SÖ¤üOqytÒ€—g›lPøZà•Á’/©‚:µqs˜¹\ŽMu óaMc¼ Õá7d›S@JF~âeÃeî¢kS*êæàu@ø«Y%Í\Ö3ÞÆ~ƒªF<Ü#D4/ô8¬ä”át˜CžÿF 8ÛØñ^¸ÏÓˆï麥â/,/¢ Ýøýz]'anÒ‹i?¡ŽWá°ø_×%å l07g2:3gim0¯»ƒ"’2Oý,;0`í‹£ÆÃ®´[v!V)`;xžnˆbóΩËYñ\U¥QÉT©Œç©l®¸÷ì f ˆgæ­Í.Ŭ•{? × œU\-Îà 8«Izv=W*6ªÐÚµ•hj⸑ ³Í7]mŸ}™6¯Þ°.µ[Î(æîš"Ïë½Ç99ê«)üŠ*¡E{ €kc¦‹†³eP¡ ƒˆ"þ/_Ûyâc7[J§Ôüï™Ù6¬·bÝsÓ©8ZS0­ïgA\éØÑj(ä>ëQïÅ^ÞÀ‚YfõãÑR‚åc†dRüá ̵w¢p°JÈì&\Š:v›4G’ÜQýº­KéÁ†3N}4¬±ª'øº!¶Ôù\ÈPŸðª•köë¼›7$¯á¦ïC• êµyh7NÏ~º¨*ž_ñ•Ü1¹nzn9õâàÑT§£›ø›‚!RQPÞÙ½nénMJêSÄîŸËÉûŠ×-ˆ`]Iê:ùœJÓ› žÕ™*_úC"›¡f’He»CùrÙÑðÍEtVwè=nÇLíW]{Ùz˜b ¦ÞT¤×1¿· ´åî‡ÂPŠCÒi2¸§ôË˽óÌYÜà-·\Ù ’>'”çCTõS²çFî™T¢Fåi'Z½óSogò|…ŠðJÕwp“€ò(Ðûë0RÛê´|Y®°ß‚z\§¯4Ñzßgu‰Ä°ìéûç¡öã³_у}¤à9-T`"J+ÛS™©t(ÛeÀ™ëuÿKID$Ϩ³º7ÚëTΨIÁõ\°}V\ªÀn€d­=¨”¥à"3ís WÈã%Ô ÒÆú>ï§°¬éÞ´SÅ"ŠÊŽÎ|ÓåšÛô0žTŒW휺„r4GÎJM¢!¾9œ}åÍ¢!Ñþ cézÓ!Ö+¯m2Â9ʵŽ¿ù|â •Q_á8ü©º§[ò;äeöÕbYgV?ŒŽK€*O#û>‡ËB&b°–ÎÝ(ÝGç †ú:dÁªêB]R'a¤<›kUúbÚq‡fù‘:õ‹Þɰ(`aJ÷“ÓG ¡yãu{RXÎö¡m a:FÖl϶7ÑÁ1‡íþ‡:5¥Õ{Æ]Î"f•iú†`„örR(]RÍVõ¹©6;`u£§S<ѼL <%†O-ʘuäE7©ULÍPVëTÃEžiß4l»ú‰Úg—uñ?øz蛯‡ÈŒxô¹QïÒuW="CbÇïÙ•_ÔAQ²Ã‡h=ù‹¿&¿kBº4I䀨8À¤)m>axêÑŽ‚1_`¨‡æ(¼ò‹äñU#KÝÐ)%‹XãÚÝ=:&$ëªG½·Uíâ½lmæ†Ø¡ð “ë>”ŽWºŒ»Ô‰`ëïÔšT³ëë¾ Jõk«ü2µ´À›ªøW  «!žI™­ûÚ…=¶(»$˜³³Ù“­ÃÔΜÙo©‘­ÇáxGálÁöØ€)¾‘¤Ä%Èœ§ñ€Ë{ûüJ}ãû®[!.[X?Ѻ½œ¿‘L f±l{Ûvµ=Û9=C¬v·ÁŒVxz:/µrÔ/ÏšÄ`z¶H°4^?î_ ®â–"&÷Ýüá4^™ã¸q|àýP5[øË™ëL:” °æÝU2aŽ: ƒ’b–Ҷȼ£,O`ÇØØÂŒk±å™§¨zö/ô¾Àú‰­ãyÅö,¯Óí‰ zÍ= X09‘RMy¸ûlùé…Ç:u ®Ú‡èYÓ·&ÜÂ"3h\x¦\Pu’ï&«QÝVGj ]W‡0@›’vªïiY€¨þ1ñd¡•â³ü†zdæ)òy"B¶ñ1Áˆ[çˆæb•Ác ¦„ã0TîÀ\= %øÒ´LU‚F´}þYä‘,›@ÎSÑ×JZÈ<ܬ¢©«w²¾¢F½`Ýsj1º‡y©>ÿ % Òpæš‚>š4t7¯Z®c~ΞQ†µâ(?"•„VƒiöŽ3ÚñŸ­BÑz|`B‹bÍ=þ1GÙhVÕz›M«ÌÛŽ'dV\Jöl!žE5/àžï¬o8¶+fh%˜ëÅS£üt ¸JKb*φ²vD¦VÉ:—t‰&ÄÁ?Eq,æ_9Úb«êÌFÓssú4gñQÕÁNÜF….93€‹i|obÔ4ÃÌn. û«ÖEñhA\RÕzê“>.…kÌzL©=Ö`/­¾¯þ,Ôñ[,wŸ£Ë5 ôžJ×$I¨„å×~Ü#Ïs–Œ: Ñg`yCƒ´è"5›cÌ7yÞæíè,šC†®*½ÍYÎQÈLnt[?š;ÏÏDg硱ùÞ€àTîºè¤eÁß=gúyëV‘X·¦½fX£ ɬàa=:bY¦p’mg6WG|:³&¢»^p½A³ä!<ˆƒcü¯Âþè+cìéÕ/€•7+¨üzܪËbRÂ*D2ªFdæãä]AuFª©g-rrRŒZËÆ?Úw¾c^dy=¹=!½ÓƒûëÛc]XçÝjè‘ñ呤.©-迨%T˜5Sr€Éï‡êÆ×@|á$£GOöÂôì *B$Hiû=f}d”Õ(ï’³­Øq4™e§Èåz?רlñï«™`q¦Jêë”ÊéÞtù±%PIÑ< ÔJ#Iß$uÝ£C„£¼ L=n¸âdk<ºÿ[Œ ä5V¼R®õ$f-N]´ÜÓæ@_r9Yð\}Ñ£ìjËôÅ7BGëàüp:Šþ"xBÔ«Ú§Š'aÂÉSî€Rï€VpÕšìMÌÅpøEñæ±îØ<–fûx9 ²”Öù ùv·\ÉßfÒŸÛ_rÏ^]Y<ÇÇÙ-Z4vä[;>lçç3a*,˜Ð£„xþrk¹MfXB¼RÊiß&ªiõoÁ™ðR3£„d–…H-üõË4O]ášÍfmŠK²"nª#Žë#é…ù$ 8"äë‰=ÊŸ–ÊóY8–úÎ¥ùï[¬rìïd\¨ã ÜÕÒ*/5äíbrUH¾ä"Op¨àÔÌBø¦W6ÞE]R‰:iÏðž^?(ýZåtÙ2õ¥óÉ( X 8¨’=¿;¶ÕQ4äUî Dn°_{õlâ—üCò·D¦T—S>cùb~èÓ5æåT›é YG¡|A»H`º°æ”,ë nªÑ|´xñÇ[—~Õ »dÐó—-*EÀмººˆå“ž‡}×!D èîëýÆ–ä“@6•q0Ö ¤8„éD^ò¿w6A@NôbŒÎXûl1]“/È„)èt;¿6…òà­ò6zv:×8wâ8‡Èªß´-x« %vˆÇáìÛ‡«„~¤€­Ó©“$È@"åýÀTô’mÇæ@*Dz(£i óÈ(UWÑÐqßùŽõ-'l;4×Pz¤é© êRÍ|}¹ùÎ]oü™Á(›>ylV\~n¶+ûÕž•%o©¼Këõ EÓÃW‡ÖçÄí;Ògû"CЮÛèÆëöýfîk4úê• ·½Ìò»´ü?AŒœßZü†~²}ER  hɦ²—ˆ¹Ú¾cX>cR“¢ï›$=㇦ܲwáÚ·³Â¤«2c¨}jè•b’£¢b€v2ɘ{ò·—+ã=ºL×øùަ_ƒÑìšy²YbOæŠh‡ÿʯ¢ñ8-ƒ›™dGˆe¼¡B¨¦ÿ(ê ¨†g;®.‡±7Í\.‰Tx¦‘0qÊ|'v;¼‡|UužG•lÄþø­ƒT!<>ˆa!jäJ`ãdv)¬€Ì1}ÎFÎŽ䆘ܓ'¨lpt´€kF[GޫД™ú:6ÕqU„βmÀ.Ÿ4øüòDßT)dë›RÜÊ¡ÿ 7Ðq¶=:µ#èn"ÿ;âê_± /ºýŽåE|ØÐÏ·;¶T>îVˆ¤L뎻=P&ˆÝúq•£‹pf̃í½_µ/ì—ŠA—Rjvv¡Ð_;ŽT‰l3‘¥»"ÄáçõâûhãÑfE'àwœ5™¾Ñ¸3¼&S.Ub¥TVª6æì“°· Èý0¢éÞA"ÏlGÆûrNvz0¹VXºH¥:"[¢lBŒA2ÂÍ\vÁ-QÜ­-÷ü”jȽı ‘aF”f¨è«ý0ùµ[ B8t$`Óµ<áä4##Ūl¨ˆÕ±íP-”¦©õ*)Õ½$Hvš Áq­$&Y­3ØÇâ§ ÷Oï‚„8?a²Æ³$‘⥚s¤ª<̘丬ð'òÑI7À ŽêçMT ªX.67í…ŸA4™ 0A§a‰[Ó¤ŠÏf`KtÙ™™T/ÌBydu!úÞ(>…IžJÎ~\~ÓM"VܸîÀ6»¹ê-®%•"kd‰8¿·­kdC©v؜ʣüƒ»¼åa3ý9N1²yÃCÁÜ>2?ô’Í+*ÚÑ©%Ò&„/v aƒ_òš–Oqf£ºŠ"bqÚÒ?WW ºLäÕ L_À˜ñ],ñ¬©¸ù«yçj\û™9âžœžvA ^]²¸¤õµÈ[ÆfJT±C?$ŽÎÁE—­a=튊'vPš›ÓÛh1U^fSðUßKóiB‚UâãJmØžWž“6¿ˆ6Ö©¶†hƒQÄ!B\ÝѨ(Ñ›]V®1X4Ñ?ö›ÓÜÇyº#5hIqa: m/7¤‰GÝÊÀl”xØ)s V‚üKE/3zÅèñWN£ïžBÝO=,‚T¶X~˜¦¸ígœl3 ÕÀ`ëãÞ Vô»[By˜>ÁEœÚ,u×,/K¡•‡!äèSõKCä…w€´Ù®ôØGNƒx^SÒ¢ôáH@Æ!ÛÛB!Än"Ѳ㱴ٔ.qz>‹N®ƒ™V@x]xèc§)"—éÕÈq–;óçYämá?†i$z¥Ð¡2µö3´z–ÅgæsðǃC#ÇZJq±B¾µAìyùXÄ‚ê½"‡ÿCê#¯è :Æ¿¼ý˶í1s€C}nÞ€‰edÜÛðC …rt4î'ôÍŒ°ûé‰ÊÀ{g ¹¬öl²4…Ý­Ì$ m0„Eqé-æûŸôqžû=®KÝ_h„ÚÚ‰h¶ù«Äò{ËQ¨U)“FHFº—ƒÔèc\‘_¥"æ5hW8„ ©¶º„njã ôWŸM–¦¯y÷e‘ÿ~wž$ÕÊeí4¥‚23À¬'Qô©üœ¼³kR@:­/Y ý)ËÑ‘Q~ª2YÔÅØ6ÂýÖðs¦å6 Ë;e³y/ZAŽW¸Ò¯€•?¥œÏW¯C϶‡Ð²txí1# ªE€fÍÌPË)~YÿLˆžèÂ÷ðÙ­ßÄßMB€¨™ÛÁ9¦ßК,Áö³Pnš~€ü”"ü¯6mgèûWwËZ‡¥¿ËóÆF…ÛÍ1}¨hm_ú.mÂ÷øZ!#<Ó{¨¨5z¸a-‘uÖ^¥ÑÏ‘ç[4Ó4\nRô„œô¼¶a<ÜÜ›d»ÄN© ù Že[ØÈ­|‚ò‚P%¼áQbÿZ¼•bê:fñ¶›xç|©ƒÓa†Q”0xVºj…]gtÚRq‚;`?‚äÙçGq a¥ë»kû7¾4ø#®¸PÛôÇœþ[çÃ5Ï7A*ÇC]±N3Z‡ïÄÒ‘§{žØJ£—ÿ€&I!te tˆB?7Á(û ;° _’tÁÐÛœ‹O„ßtmLq,gߤ3L¨¥Õ^ s äSö9¬ ÛSžvúS_Å2‰bkvi1¹[3\õ"Œ;˜ŒÉ[F»² ÀfÝ´í)@²ª¸`^/ï5ݦb«yÑßÉŸ‰B3ŒÍq~IFEŒBßWK IàV S~bQ«B¡%II ÎX~d¶qå¼s¼ôá9~3ìê…y* gì}i»¥RïZ"­57×kÕI³¸”5ñZб-}¢-³ÉÊ6צZ… Ò‚à(‡lU—,'±]$°ÿp>ÕÃð\^k!nMP»¤A ‡8vMçË#j xà÷õ(I0'z¤\j… n€5n]uAä”KÂÄQ «;On™3mÅ8žŽµCÙtÎäfü õÕ¶½ytž˜x›óxŸmõÓæhe°#ºµ‡r2ÕKùË•·1-•8Çå˧o¶[-ø+èýÿ\à#“Î3Є•ñ^ŒÂQõЬôùíÅQxª´yTœ;ŒfÞ´™ôF»Ùxûú R_6§[§§er uè}°rc¿µ“9‡#êMV„Î^ŒÓOíæ’Ïµ\çH@xü}®Ÿ+˜6$ã¹D«)fõÔØW&UËC•õê DåÞ±¿XBŠÀ ò /¾!Y¸ÑŽrX³JW]/XþÏâšX#P6+ž®Eô§qUÏ?ËÞ)ÜdFB;ÃEÊO0Õt HŽmû­WtH$? “­£têTÍâ;^=`´Ó~³£.‰p½˜s¥bï ZRý´x\W³¼“/øsŠ*ÀQýí ÏøÏq]÷k¶ðŸ_â FxþeWÊ´u€ÅÐ Xˆßª0_´{Ûðy€<¶Òš}¼ÄÕ oò­»MÖè¸53Œžî-hßXÛÛÉaª¤Ò¸Œ÷Ò€ÓˆNñ:Y é™·-Lç^ ãÅ`õ ç ý TJàÃkŽ[bO­![J|¯I®š.Z4W§»¬H $h×ÔÏæ i?­Û7+DCÔÏñ\VµR´kKï6FšÅ›mæ ‹BÕ÷ýØÁ%^‰‚ð<ÆòïºÊw5÷Ç0{ýMþ`Œ™Œºï}©5ÖÆÕgÍ–pƒOâhh ]ÆIå©CøÍ€{\T× øï„Z ZIÕU~WaFørB³¶ƒpô®‘¿CHU•¬[‰–,}»F çsb¡£Mœ"Îp€nÅcÌ‚Ùu>8˜!‚cx&­8'a*_o“\'Öw‡,m û»J¶R53jö´l¨*P¢w„ù-!µØ#´K4êÌç|+Ð9ŽÀ²v(@çÛƒA¬Ä A³|ÛehÉC€³-n‰9T_oÙX mQ*²œnÉÙÏ£x×WuÆP}±Á½L±Ì¦Üa1ÏŬ&ì%@€ð¥jÃ8 Z¹I„RPlQÎÖ:Í¡Ü,ÈöÓÃ[¡†2ðzÃi¶)€‡mÞ[+ï4¤rx,uÌØžJœ°$lÅžŒLžv ¼DÏßK´]Cxn4ðï%—ß %ŽOq•@ÂÕº;³0qK#ñ”Q× UäMƒºŸ¢©ÿ¯ììVì|Û¨z)¹}Æàø¸+¥ÀÇî&¢–ƒŒ£E È-¤Š†Wm¦§œú5«îÁÁö¸©…ÿJ?¿é„é‡adf __…'®Fè_5å5sjûâ&Râ±k†pçnië"Ö!û|?ÚÀ©å2ð³›<ŠhQ†ææ¦ªa‰äÅÕ:¼i8*7vâcí­–¤*Ûrê_N”¼ÚÀè~>£¥" U|[ÂúNêÑ> φð"+¤±3Í‹Æþ+hœ@²¥‡;>H)&š‹-é¶ÍáU&Ë{ÛÊœA®Eu+5Žü ÁõtIÕ»}[ž® ”xû¡ï‚^b{ Fûx…½Ñ?ú?¼•ª ð:BÞ£·T¨.ëeÅž3hÐPóleAÜÙðÇF•òÀ»}ëàS!åSQzÅJÞ67G¸–”àLDÍ›ƒã¬> ^غmw~yÍoδ W:·C9˜A®`lS°J£u.©åßÎA;+¿IÑ µЬš‰È 4¯¢µSr§E¬¯Ñ ºiÖ*ÖsiwÛF£tMtT!4×ÁRŸî{¾×Vëž/#x ƒLŸ‚¹˜ÅûR/}ôVPFøÓPÑò\1@Î2ø_Ä©KJ+"Ž¡Ä_ãÍ065QWÛ&Gƒ;£$æHãìË€^Ûüãù„‡ÏZö°óqü¶ƒBÕ,§/ø Å—tóÑtVV+PÇ»(;•gWܤ\ÿ‹ç'í¬ÀúÇl v–‘ª–8#Çpy×ͶŸÚ×#ƒÿk^)¼²n†n¹çÁÙ.£ÕZƒy,xßu b6i² QˆE‰ÐÌL~ÙnÃsÚË'—ÒkKX§Lµi«ÁºD¦·7Lz £ W'ƒ8_LjI°á‚‹äê©aŽ8í^éIÆôÂ.96¼ncMZðtn’À-pÚnK&Z ÃN¤ö;~ˆ]'+—Ýj¼Si$VÑž#œö O#6SÔs‘Žg¸³ˆ¢•S=˜³GïO±Àt/ߺÉ/´êVp?ÑéÆAÚ—æK‚xÕýü7þÊù½èv¶·àx· ™süT¢äk3YaNTˆáw ½>5¶¬¤÷àÝRð’^t ø „àž×qíXjyòüQ’W` žè÷MJsýåæûÐ=šet®ˆg?]ðm<ãA{Öª+ÊAWšúMJæ5#Ž/ [* ªÄ¨f/úK9e¯5*¥ÆE\ôÎì¼[RgÎ4Œ¨¾bv£€{t'ĆõßµNâŽËæ¤ÐîS€©©ý2ŠXù|Ó” ”Ë"ŽÈz³?*wIÛËéΉ¥¯Êk Ö7b} Õól©Õò—†ëe øþ“÷Ø,¢Ù6JªÅ .ô 'ÂC—vNjEõ¼r Ï»wÙ>â#á‘ÅS6‹Š@™!«¤ ¶üí+®‘Rÿ„æ|¸n0/±ìBn6½ôíB¹ -£’<ééÃ+àSI¸•!–ùîú}ªì²Øº I+˜BÉb„›E wâ#¹—¼Èþÿ,õ\Ž ‰Q´Šð&=?ðN'oÞá)%ÞSGÕI7hÌNH/PŸï«Æ™´ØjUµ¹ý=틇Î÷Fîñ&ÓéäÁW h4æ"Öº™š­ÇOÍîÅ[ £ê焟z¤+lçó† [$ _™¦?0'ɳæ³µD‹ºMB{:‰ðI–¶@l¥Ë)µd¿ ׸4”bŠe,`º?¨ñ«Û[ÚŠ QÞøExÿL©BÏ–þv¦ê¥ jÎÊÿ2 ŒçŽksú3¢mè¤ð‹A¿¾s8ÃóÆø@„þ¸õÍdÑÒÐt-±ß}'´W“,§“MP,m¯PÞ(Gè˜ ˆ³?˜1Š^áóú*§ª+Xw ¥ó(<ë@^»B\à¥(Ôûp@S -Aá1ÆÂÉÚùðÆ­þéÜç.äæ2Þ¹Q«õXø‰.åfXËB>årŸ¨z»°[XHïkî]Õ4&­é_$º,–´Î¤_™Ð8ÔšòŸ6>É®Á>BHK"ç¢æmµ¹R‹inO€kâ„àœÁ$–\:ìâòÁšéÇáÓL~ÃíÞÀÝu1â€îäÄiw¥¿%Ãø~åÎÂ_­þ¾Óà!Hϲв4m˜ïÊ©úF·àu­0—}¿# ¸øæÍ“Z|ã²3…)•EZ#0Ùö‹ƒRmË_ëlí>ëóe‡AzÅ=–rÿE><õá×4Fì’pö_í¦nŠS©îÏ•YýêmNÝ%¾ÁÆx?™É{ÄGwïÀS>Nµ ŽXPPžEy—ÜHÂÌþ^? @w†6aßÜÒÎ{¾Û醆ù™­ÅÇC‹W(–Ñít€,4lÐ;ûÓÎÀ{a]é‡D…n;UÄÆjƯ`*™…¥_“cZù•xëZ¶Ói*f4v%ÆOè~“”…:DT|RÊ,óÿ9É܇¹:i§vÍ{v`¶®P.ž>­·òµ’}l̃í…K8˜T^` jûƻˆ¬J ³â‹‡rÞpµ¾ÿMìçà$€?™’ úÜsóq|šÇ5r‚}$.uk…:i¤(ýµyR0¥·l7 H Fn ù"íw¬ÊDþLó¹i×@_ۼ̤‚2@5jFÜÇœÃEuBЇÜ@^˜àÒ§‰¨FÈ_Wè±&Ú¸!ñ›ÓùäƒáU(œ½ã¥÷ás®šPÊWîþù‚]yž•ÖhYóŠ·–Ê4 žÛHŠDÛ®×/›6Å;b ;ÐRžšÄ7W¨1Uì㈢gxñç-æ{Ó¥:¡¤«,°TÕºŽö˜Åh^ïð³g躦‚.ªÒJZLWxèGΚ·’¢÷ÉýþÌ6°¯2ð*ÓÍǽ뛆4ø´‚ÏŸ'€ò}YàÏfÚJ g%Öé|Ž5ÿ}®‹]ØSòÒÆ6ÕªgkymçÕë§¼rEÑ·ù­›Ðåöb:„1K5V÷wgõ^·®êzQgŒ[ ¼ã™õë¯ÆŽ>-åpfáïîçï°m3°ò½DDŒ­¾„ÆayJØSµ¡1êJzG7±Ö^a¡§-ýœc9U ~Z5°šë¯¬ î÷òé¥ÄU¬dø™rÒï5‘Qò;oç¥ú™¡oU}XÀ—ɘúbŽ•°–Á™5ëu õ©Y5À§ëÃ5,Ì(º‘w†¨ýbM,ONò¬Â²"Éð¦å‰Rsç…™ú*•PábgGႾÒz©\¯R©ñÚvžÀ#ü¬°S½xöx&»'Ê»"«ybIRøîZ-šå=Ú8~ÌlŸà9<|‡½Žÿ(­Ñ\táhH¹Œñž¿•UËï›f5[ ³æ‚º¦Ë‡”p£^š÷¹VZû[G×}ÿ ßÊ5¤Ó/HBõ€a¡up+¼”ÕçÝe•tKð‹ìÎɆ´‚ÃPõ ÂH¾oe‹7Zú^x ÂR^º £žc}æ‘i‡›l4{†ò´Î~=Ûié¸× 4#qcð ¦bpž P‰}b‡ãjÄ~ÈŒÆ J˜žÓáש~G¦ÉX® ;#aõ•íå }ÏÞ0(k¢W-G²'ØÓuTÑ õaX‹,ìX"kȳçä·ßvŠØyp°( H·ü%.ªÎ±·01éfYáQ,%‡t`Áó1`]Ô„=da2\·¦f£é\ ƒwêY{ܬ³¥Dç ³±ï:q|íƒó9Î`è Q¼¼Aš¥?z KÒp} ‡yºsí¾4Ž=#_÷G‹–ë?'“ÂïU<,¸=^ý¤×ΪÊûý¨¡<*1ñ¡ä^Û9 ]„(y<Ýæ½Ž½x!ã·£.õÿ$ºv(‚%ÿbVΙϤ@­Ï¡ypN`#¤¥´µøÊÏiH«hˆxèkšeéå¼ËPŽ|ùdf<㮥(kv3²`Oh#mÊkòÈ%v™nÏÐÏ’ðÍh53É%³?„ÈÒ7?y +iD­˳â:‡¿ŒÖ(Ao¨\\1N@ÖÛúÙ ¡+ŠÇ5 ÏqÍPu€‘—‰ 'oX,\¬´&•ïÏ[ÏHFÇ]ú{F{d„àn1Á‰—)ÏϦü:QáPÝw¯~„⛌Ê^ñVb–Zaø¸ÑJž'!o²·‹ó» ]A¢å¸ÁÎ$¾£kw¾ZÊÜAI­Ù*ˆÕÓÛÆ›]¯€S/À›$GÆN}@SÔ ÃÎ4jçéÂéˆMÒß=ÖÚCòƒX®‡*p}–84Ô½ã6’¾PÊÊÎ:çùfN1¥¸­è¦’¢)å8#·»IûRéÒߴᎊÛaTR²ë7 “F†TÌ4EMi^¾²n„R¨¢ÇW‰~D‹8 Ê¢Hž÷§Ý,Тn‰øA¬lº|Rl#ü‘Öa!Už*)ûÎ.À÷ØðEÎuCe¢95YòÖk.B N_ÌU…!#þqDÆrúùÓ±ÃGw‰Ð)HÆФbqíž.#x2wXdz/Å!+:⦠–yAR¾«÷µcSP5|ã´Qò©Ü³›"J¿~±Q-ª&¿¡ý§ð·™sGì˜Oæb9ò‘Aj¤~'aó I@~êÊCóF¬ˆ#Lš*Î¥"ßÂÅS3Ð^Ò ¾0wJ±žví®±WüJßÞ¨F8ô?X¥`Ìð½|¶¼³ 4Ö67æV¾—t™ ÝFÚZ?nÏÙ!imS-Dêh,¡ÿzÁµ»{¸9§€A×–=ù¾bãCÞÃÈÞ F§£W å¡vô‘±:ñûë%Ƕ¼W)=E¼”Yó<þhîPV˜ü¾„[Ò?ò kŠÃI B„|ª5ZcDÂ]oòë£"Ð"á?0Lë›ýËòšäÌäp¶œè?(‹xµ¡KV“B>I<ÕKˆ j |¼ç'k,ÅæŒ}ªPXìÄt2´OnFÖ{”rIÌg˜/AºÐ’΃P–ˆí­!iÝæ™µÇ2žYäRù*ÍïðöUt‹ZÄ_aôÝtRÜ|½Ù1—ÈÒV(%g0Y 0RæµÍlræÕŽÈ$D¿Œ¶ÔI®YGÑ[ÓÜ9£=ôþä,A“¸¨S6îíØW¿¯b¨àè•thlRÖ#œê°µPR\Ž¢îú Jµ™Á }Ã,ñ;2C…ZP¢ß§åJm³S‚A×"qѬðoåµÒ"‘4U5k7~´køC›1ëˆ5ü€·§Ï¿Ú1ÎÕ¤ D—÷¥Ðwc ÄÚ¦Yi3€ßD7¬¬%·`ºK<{!‘¢?\õ¶£^Àuã Ö.…DŽ Eÿ*?_EŒ|á¤þ''ŸOM’‡šµý ›Y«eåö5 iïG9³Ü-a¸‡­ƒ‡¶euƒ±yah¨~oÈí¨UÒb;œÈ›—±r™ed[å+[]Ùß ÎÙÆ¸¸¤Î¶‡¹Žk*2&ÊÚ–õÌs¬) Ô72&˜$™Lg–#Q}”æCÓ¬Þªf`<ÿ„,RTYšôÝk Oåyƒ);úÃÝÄ×9soÂÃå§cœ²B_òβû™œ'ZÑ|]Õè £ ušÝE6oýúD›HËX‰—O3ª dFĤ‡F@Ö6-Ü©ñåEg‘%÷Œ|€ü,$.=“ÂÐND¦™ ½-õ™œ&(ç~“(ä¶úýGˆž¹[0ÜÙ©BÜ¢ük*= óÁ:ÀÓ\™höéêwß™]iª@ãœxC$î(‹¼° ‚úíÀãÙ•¼õ\âƒ+VØÎniügÚêýa…1Ë! ™ßD7g¢€{~8“¥õårɤl#2환 àÞ(„¿o)Cº·P…‹ouX–FÆèxÕ úð¾í;|o³¤jqûß™¦˜ö¢0$[³‰7xw³Ë3©·¥Å9fÙònÎ4¯a[¡É•q†ðŠÇ…š]F‚J8y³6¤3`!òj]›Cnûî ÐÇm†åR—mºóRiô–±~üÑÒäž¾‘À0‰[|j·ÌˆL"ú%ÉÊ’\Àq½a¡ó)P"ëfóæ6õšDWS+y‚ÅWøX–?È9öa{<Ñ5äÍh¸E_Ý5Ådy£ ¿‘çZ¦ÆuØaù?ÇÇH²Ñâ\ô5¢úY0b",†Eõ‹[Õ=ç{‚{²v©pí"6ÜäQA¢QÿHÜ®êðE¤AÖ aYGà5ß ì‘²I.çé!v}eÂÓ«5…í“6©|¬:>Ôþi§ ‰lÞØaþéûv-¬hÙ uçQµÚºí0ºJx(A|khÁ:í²ƒnÖ"´æášüesûvr@l:ßâÆ$ë>ÂÙ ”ûÁ*çðõ^þ•—Ûߟðío/·Ò²û{š¾ÞÓÿC¯?o}¿·®ßÛÚíëݺJ¿Y¶ýM‰ä€îz{°Ñ5F܃!w&¶EÙMžáÐÍ_Øg¼µû‚È”´JÅ‘ÎTzßl¥hñyèΥ̦¡)f–³Ž’JÅ rK³8ËðqF—'tŒtu 2^7z™+ÀlV:Ò^™÷é/®o-—æZèÌhTèŒDøN{R#Ìqxb`]¡©Y|sÆ:# EÓÏŠ ÆìróäãhLMž~¼ÒŸ€u(ä÷‰`R0É8ÆÇK·óÉþØ|ûœ²û‘CÕþ¤MôÚÄŒ;XÛBðE™[ý5?¶&XÍ, "ô €#ÄN)É=Ö%`³†\·îÒ,Ó9ªopa0 þ6éìqpeéW÷U®OÖ7£ > CúØ×U›-kA?Á¥Õä–ð½°rmtM*`ä7(¡¹ì„a‹À/H«Š÷v#9:íÝK´º Fñ[ø„ þk}Õ4oóÝgôåJà dj+ÞøŽ “ºµgÂjGìöÎT6|„C¿s2W©. ðíÛz>TpÀ£ØÜâ$^ˆo G{8›™¡‹™Cµ›ùàíÍ”aRʈm©ƒ䀧Å 7Y[DY¿öà¢iaÖi¤@éªRc úÑôÚLhŸÓÂÓa°m΃K £Ìà?AÙÝHŒ~бŠõ˜¥"šÉ”i>éèHi [¿m¹¯„»'\wj’ÆP„ñp_±°UúmÍ&AdþIJu Í­é§¸ÞK ç\HŸ¨sE‹Iñ B¤^:¤Òäþ/;ŠðЩ­¾”^Åg$ Ü¡)Ôäù²ºqݼ• =¡›{¦Ú¦:€¤V ¢~²²XJ8G̼ÜÖÜÕŒ!dS(îÿuÊÈÁ­¢Ü^{î|묥ø¶ì*qü—s'*}žè ¢F½p! õN)OoŽ^ ¡ÔòбvÅû/ÑA7‘®ßV]j À6 ±œ«ÇòœB™£ŠÜ¶n¹©r û‘1Zçß}"&[ù> ;!é‹>ø´h÷ÝÚS„HH~¶~euÙ%`Å"uUuƒ¸õªÏ•&Ë7îi´[òmn a©òZeš².ÛyžÈÑTÓø[†?o±BŸG­…A1¢ä»£œµDCðƒ`ý?‹ü s97Íp«w›…hZ¼¦~ˆ³6à)€ôÒçÐâÿ"Åœ”¶ö}±~Y4ÞêºPõŸ—¸/°ïBž–…»±’O‰&Œ{ÿhUr—–àŽ9§€û`&½­´;7©œÝ‡ú›%ç¨gIE…àÂS:§NÈ\9©uLJ#¼A¹ì[*·Ñü„aR+Ià•ñÀ!‚TÇÝÎ1ì‘‚t‡)³t!Q€NßÙ«w×úÇØv0w6 aõÄewüÖ=!”÷öÇB\æJqŒ!ø«ÔÃ) Wë¾Z Ï'ˆL³9†¥þQleŠíÙaàeÀ£ÿu'ú¾hâ|Û*zY¨ü¥’&r·ÿ RC â“ßêéìå-ÿ2…a‚m;ü6m퉈K\'Ÿ¾M$t&¿({Üy_År'’vm(9 ¥ °ðdç–†^'/ˆ"ü‹ÿCϹAl²XÊ!ê'¡œnŸÐ횣›´Ìå’ vÁ6hÓ*é++ÁQÓ)î8ñÙMæ8­þæ‚êš+û!Ò‰ÛuMŽÞ ÍB-‹›ñžØk“N“ÓŒ"vÎQé±—DÅRitGB ‹Ì.Þ œµ}›ÏúP$S†su zÛ™(:†wÝ…99m? éÃï]ȧ洟éM*Ï\gÂwEÒ»M4ï^G&ÌX´[g/> ÉÑCfâ]àemLæƒáóúÃu&+23^¨ê"áÕ•Úú@G ¢oG$ HøÊ§I4Í LL-Ë·¢G/lzôI£ö'骼/¥}X\¦ûEËDC†Ágùq%&”úŒ¶ŽªòXÔ{™•±¿Q)ßÙ-~ØÔYªÄ’j†•Ï»oNÔBY‡¬úÃC¾¼vFÎ_„üwEφœAié. _蜡ÐÔùF耋:ñ(+çú']½äpçZV¤‚É -Ÿƒ¦ÙReÛúšú7 …Ë1­·ªa³S˜süX `w.™üßéré.ŠÙÚßé¡ê€ƒ " :1$¦¥}ÿkBìÐG$žÓv Áó²4•üÞ=Å{jÃJZqŠçÂÉÒJÂxYn7?ªÅÒ/H ‰„ci§B|Õé£ú•'’8~Üçeça!¬*°ð¸Oub2eøq§¢®g8RA•yè'}L¦p.•[£°:âMÂ%¼ÑH¶Î1Á+‹f}kuMŸ2èe±(CŠþùæY¤À=¨æhás käMuA‹ö¬„ଞñ¢&S—^$R9÷9:|,þä T–ú\G’nkw£LÚJA›ìc™âZ81‡·£’‹+«Ý±ì¦¡ìIèVs³kÈ®¶]"ZYz\ÃJ&Áßjˆ>„’K=&Û€Œg47`Á÷cƼÝ{kÚ@À»#G9ÒÔRÊ2˪'W­ŸÀ剸¿û!“÷˜ :­â“ìÐa‰Àð­Žûe¯Ò?P¨xëòH  ‡‹ÔÖ »lCVü5Zæ1îâeSp¨±?Þ€g¡ôίî PÏ%˜Õ•Ç4K”à™<3Í´HŒ>Ùî¹Êʶq]"Thè~Þ­ßývŠŸªÛéWÙ念X ´…NòÑÄ?.5ûgzPCºTk?? §2÷ñ¨hÒÄ §úò×dÆ?VwÒο¤b™“h~òviè`¶²dãfõrM¨ï–¥(Ü-`’F-¿úNz;:KN!‚¾ÔæÔ V}]ÙÃVŒ/.ÞCyÀ—òÞPX%(åG³Y%Lr%ôcd¹e9zû8¯s€œ#WÕջĮæ=8Û–O( O0 |Qò©ñeú)Ö¤ó# ËÝÀýq…‰täš“¯0Ý—Ea…øÊ×›iJQErU !6ÙSÊŽsŸœ¤e(/0˜,UqãO,à·VØŠRZ·õÃPõëCh–ÀwÂè'ê8¿¤¢ÍuªðrŽ=ÕÞ¤/O‹Aæ‰ø ºpÉÆu ¥³!P ¯óF¸Òf{Õ“dì›ößëR›þuJ[ºaL¡> íäþÿ×ÊÑlŸXˈžmÞx0/c‹Ñ€4àÏ|7~¤HúÝ…ñ’sغÝw+¥¦ÄaøT&ðCGºŸ µ§©ðF‘o&qõÓ??ÇáT%ý³!ã{Š Óå6Â0çž‘6¥ï›RcñÀ±¨>Ž‹éú,·»Ü“ìnñ1dži«4$ŽB±-WOB"·ßö *·(Ê»¥Žš¡KLáÎ0øu7ÛÕrÇTOq}#ÚêfÔOvÚÁ‚½E¾!â ì\‡É“tWtp4Ðçç¹Wˆˆøå‰v8ÐÉ$Ôš(ó£ÕÒ!‘Mð¾Æq÷Õ’K"M½û:s`åîø¯Aíê{)²G‰}=|1 ¬½mp¦ÂŒÓLâx¹t»æî懞·¬ ÚÇ5KÍúÆZšr’ÛmtÒ塞ëzaB™QÏçaÔàÚ8߯JÇãä¹ M?àVÆùV{yñ„`6¬"$¥D=ËÃï? !H=öm(½M ‚æòØ_Z-¾ü<˜[r³Ïôr宵W¨ô\tŸbmìëûäPÅA*>ÚM·µ2Ÿ-cÛÓ³èJ"‘o‹<¯»ƒsÑŠ"uæJ2V—ûº?!èæG¬~u„òÄ©ÚUŽþŒh’B³Z5AåM¤b4*|KùrŠÒi»5„>ëtè-Ó¬)@^ΛòQ¡¦M* ðÚ˜’ì7êܶCÁÚcA±bå-ÒÈ£Ä'#á°2Ûü48eN(r=iawPŸ´£ûrl–¨)иP à½;S>CY‰º$r£˜r똖P­»N/$û(‹ögnsêc~¹y¼¬‚Æå% ])¼‰“ÆÍ£ÙšíDrRÉ\\‚#ÅýšáŸ\ÅáÇtA½/ƒ¸Œ-yż8 ã‰ìXRŒsÕ€îlÿl‹ˆ=v–ÐOß3n²/¬¥¯Ç ¼¯Ô“)Ôïs ãp\Æ)Û G-Í|:kt6íL÷É¡'€â¡rÜ&G/9ö ÒóO=ñÃcA$¹Eñ¢Ú«Âñë%“킺R=ëý„kÿb§Ð†Ÿ£©ÚLÓÂí¶ vIŸ—¸á4ÕôA*BšVæ,oÓߨOæñTžhKÐD(R± DêzUËĹcß³¥Žƒ¤ðL¢•þ2òÊóû*¤v—•òˆ‘S…õÞZ‡ë–@à+]âT€ E¥åd/Tjñ¼Ìœ¸÷Èõ ;ÈÌ«H>̓ݨ;ö¶„«=Ó…Ÿ'HyÕH°]w¸7»ðpjvÛ}=y`§1x«ˆöW''fŒhrã‹'E T\9º;‡%=dÄ]ÎàÝpÊüïF5˜£  NÎê4áy ¸>ý†V®/4à áb]ÇY Û£³G'Ýr©¨¾0èG'_ÚáÎóÄ×\±Aø±ï‚·RÏ·.@—Š8þéÜðÄJ §ªŒp¸øPžKaÛm¡ŠŽC?å`ŸäXîÖÌ‚‰ÞxÃi4½t¢_Yׂ¾ÿi©‹¶Dú‘e)±ãº„I°‘Ž[E&›õ7­'p.Òmûâ'zÅ+3âÃ1šñEšÊòD¹¦9BN5@ýþüÈ&hù/Â9|Ú×½†HQuz6¡¹Ér€¥«&¤ ‡†zQ{²˜æHÏ‹ð(0Îñªr,\Q l¥÷úü©ÀÐ4ORãë‡à2Ê­©.h¡Ï€a5:0É8Ê¿t`Ç/†ØLø5ö?=ÍHáF‚³dJë®,÷®d`Ëzû»þþ¡¿T¸kY)UQQBœÓ¡d–¿·uµ³Œ·/ý°Ã/ŸÑ»jo‚Oê†u?vÕÆÁûmÚ³}#†í­T>-áîwAN#Ù;ÿ ©ÿ ½ÄG.6öÈ•G®áqŸ+œ«|D*½,®›UJáÑ1ÒL¿fȤ’—¹ð„¥ÌU{ ̧±«í~Ø)ƒVС"ߤî›+-ä€Ïü&œ ²Џ‰G'YDˆ^—MA‡XWmY×€þÙj¾7zžuDwƒê´¯[Éy/•÷O ~ŽtÂ÷@2Œ.®·Y¾ÏYdQ2ઊD"e°ä)âÕ¾÷XÖìÔ¹³o£áöµŠëÁ鵕ͻçh\Ø 4~‚BýüÒB”ŽU U€%㻵-?ü2Û`Æ_TóþÅRˆàÎMm¾5ËóÖÔ%ÿvà€K »Q©‰ÛR>¥º7'Ü †\„1eÕqj£’»Æ•?”À.•m X w:Èã\âÅ\º{°B€E©\HÓ^sëyV!¿½ä_Õ¢ÁL„älm ’ZÓ5!—ú‡¯«ç?v»óݧ‘x”æAF­/Pθ·úÃbÉþU§»z£æðòš*•ÀC²P[?¹£©éÛãj´wj ’J÷“ßKæ2ÿfÄà£Iïô~EýGƒ&œTÌEÿ--ZOçŽi ¢¬ Pj‘çQ…-Ž8!G|i@Õlë>C€Ü McÉ2¨ydYša›zDbÏŒr(ºßŒ—¸…ÃÞJšc8îÒ0U•q؃r< €¥†Í*øÑÀ~ÀçE 0§¾\šgTËâñ‡fú4ñõ‰4Ȭa‡¹‰©lÅèêgWþÇgEuxúmÇAŒY­ç›µÁÃU,b£(Ä·bòƒ'O?|q@—"SéSÝ0=ß<Ò@/Oÿ<Ôµb_éänƒ رþ¿yfcYïª8Ç£ Ev[oð®Ù¼‹âuî/J@!P“¥ç¾ûçÚȃë\É‹}ÐÃevÐòY\/L*þä²ûœà+ÈÏìúr¦Å]=™š;ùvÔ4¢U‡8Ý(³aÊ)dþ)´_{°×ÒÚI¹´íW> 3‡üÈsØõþkJƒø˜Z¸6]xI‚Á%„·ÊÆWåU7ó\xñðAm‚Ú³o1¢)ãWðÊû82Ä$i¬çݺç ˆþ÷°¶å‚m¶÷ã^KZNS¬×®r·ô| …@Lé±ÑPÐÚ?dÕœµ\3«òE†v%Cô?…v„Â"Ô¹ll­gŽàŽçÇ2tëÜ]®^]C³MRzðÄrpº%†ïpýƒ{ŽgG¡Oƒ…?^b; /ƒÌ¸éIz¤7ç?‡ LhÙ—DRolphù÷¯}[@-‘wM…?(› Ñx6 ý›ªþS uÔ«B HÊ-³GL©Êܤ:™Ém …ýêùu­îÆ‘C=ò›Ûšç¬9iöHùëûªìïܶû žjÛ·Råý) ‹â¼¶‡(Ò!gÓbÁUÔ‰ÃDåœ~x9$EŠÈpnwêN}‘AÚ¹ö·‹Æ f†çB6WnjºšÇ@©,Š03þ ï2â ¿× Î-½ý¹ì„“áªh•ó®Ó nÈHPº×¡2‘1efVÎÀÈ€E˜~Aî¨P#èûW/ƯfxfàÛDhÉ!ƒŒM%lÆ*J€]YðÊÎïã%›e‡Ð"Í5=A•÷ÇÃÔfÕÈ-brCò 6ßt۪ܫŽÝ1OA ÞìkÂÏŒúÑ¡›vuÙ šý=¬MFŸM5à 5âFpu°ô€Ÿ¥Z¡M/ʲ(²+üϲi2 ç̰²Òùâ«kKö2Ö€Yí4ÙÛÊî9¡¢ŸÞ)Äñ!îlë·@1"—׋žöûÑxB˜ºf¨¯¸eR(UÒ‘˜ô“”âžì¸Àvƒ6`³= ü¤Iæå9˜ŸžŽöz|Ö™¦?é*«¼e/ºøë°õ“l &oÆ h©ÖŸÓ뺓ªÿ Šx±¤Ûã2Õ>Ç>¸â`(܃¸P ȃ>RÆJÞØ ¬¬±&lBÞ\ üh¸20#í4Íåj%e[òOX0·Œ‘j%Ò’¼D6q„O´.[be®7Yó`¦¦ë9E¿ Àþ¬©èÿþ0ª˜“µÈ&By&ÙW<¤ _-ô†yÎñ}Ç߈=¼ÞpklÞÀö2SõC‹Ã Hè,Ë©øv=¬ïFåŸBãL•ž“"¿½Z1ÀÞ¦)õâÐ,°1Ðx*U8føð£÷e¢|oëãŸäv›€ô»wÚ<íˆB¸º.јáq8Ô ûиá¦ÕðÛ$Äv«.¯^lVqÐTdéQ–n¡ŒxT ^‰"â¤É¼UÄð8ÒÀÜ+“°Ò_ÏÓÃÊüTNÛôËûµº$×­c6ÉÝÉ|i¦ï¨³©& ¦ÿxûßí!$881^)­uKÝ*&ˆšct ÷¶ˆùé¹ïbf½¡Æ…T£yº+3óP`Ü*«¡‹~cŽš›èƒOÓ•;\Øí†>­H½Ý=g¨ò“ƒƒ•M_=í¢ÚbáùÿlþÞåRcE&©š+ª¡×qg7–`w”²µžÜP¹²(¨6^—ôÁ£ŽxŠŽ ÁºÚÅñw¤ëîÂV1h‰=VîØPˆÃ¶Í®ws[h·X_èyl.Ãîž“æÆù”¤'å`R¢Ì£(k‰XæÜY$€ev¹xiö™`¥0à{¯ðÞévH/ä:¤ÿnƒ[Œm3\ú^àð 7žPÕ> y(*,»êW b/>õxêL};—LŠü=†òû!éîaqf.çìÈa€Ã†#¯F;’=SSœ°×뵚8A4Û³Fê=~EÅX˜w 0‹ÖT{‰v[q:¾¼Ïä•ñ–mØêgp»÷ªŸ†Av혵h? ܈öÁ›£½AW££mëB´K^ƒÏÝoÚ6‰áL?6Öœhh3Ü;UÖî”SV5èÏ­z–"l/>4 44×°ÄsÿU1öXuŒ€Ë;šK¼¥¨õ_Œ`Àj›·Æ¦ÜXØßãÇΞŠ2õöNëº/ÌΠãºpO•­ ›™J•TÚêìµÃ¹t,ûXÃ<°Ï‰¬ùTðÑ-èSÞ`ú¢p}3TÔ–ÀȽ}»YWÛ§ùÅâ*דàèŸ AhêË%EI ^?Ç/¢‘Ìã\Çœ>Ký ƒ8–1<äà`Çò0T°7µ‘Ó©ûC ÔŦC“„DÔŠçi¿½ùT‚m±¤bëv“á‘È©ZÎuM—Š‘ÃßR"±ûBcÝG椩#V…j ˜ñÖÙD_Ø”RØ0]Mì¼i>° 1$ó–xIÌ}¬´¡•&&O¯Í¡x.™ƒA…l&w‡y+íuSíA€ Ê…è5ÿÓ¶ìö½SW|uúfèüVý¬¶ùÙ¡î$åÚÐåÙ­T=Ž{ƒ ~ÆäV¢.ú>]O#cV)-vË/9X¨®3,Â]>¿½ê› _>{¦’a2á¶om4|:IHç&™¯(Pã†Ίøs·ôõ·¡­q|ÎçKNQ“¨Üëý£@W™8ë«}ž­0 eþ³?ǵ¨öŠ´Ø­¹h¯ŒÒ@Ū¸%=–”ŠŠhB‘ľdé~ W$ÿl•Pœ„Ÿ? g…PZ”çKúÕžŒ"ûM Ã%¾¥w3kùIÝu\*!¥v›†4XæÔXU¯)3%T·lxc8ðZ#‘ülÁr=c”´ðSÍYÆDpcþ·•þçwÐû¥mV?Òwr–ø›EèRûƒøŠkÞÇx»v·„·s yO¾zlƒ$à'Zƒ—ª]j·7›ý—‘²iÂ7´ð"’Ÿé0÷ wbq*ÑX»)@†¼c×PwÝ︚ k*ß7%±ÔL1¡â¤Ÿ›—ò÷®ïê½Eûl®,e¤Ò1ºÅªð)ˆOk<ßf@ÛJt¥\Ø÷lCÌ$E[¨ñ‰ÐDQ—ãËp4SµŠânøD²æ¥ŸR=×¾wñÐø†^kE…> î·t€ßY¤b÷ô„g iŒ²-ùÈg ´ò8Qô º~,Ë–Ê_‡]ÉU{Xó`— g—”L׵įmÎw3ä1¾¶òφP]×¾\-ƒ§þ\üè"/hð\èIâôÑnúÅ×ÏdSVÕ«©VQ~îõW/cÀv+(Î|àÎÙª1ýqz/‰¥7–˜ ŒÍalÜä åOЧ ÊÈÙõë\7Îÿo4õ œæ™‰1[ºYª¥@xh?ÎÛ<ŸTstç`á°hÆm_¬"!`>%4¸d¥(Ìóúîaõ™8{¸²¨|¿¶-ÈÓã }{•¯(§Â•fYb†*—Ó ƒêËúÂuÝôÎúÌ’ ]´gB§Uïª ÜÁÿoÌ Áä[Oû`H´û¨}<þVÏqøÊlù™^`v½H÷Bîèü¼Ä$Ï8ˆæ€[t5à|/ ¿¶G!7.5ƒ(éL äGþA,ÈÔ@•VïX:Í ñÅ ÁB†²¯½Â_û_HÀÁ[¹À.ÕÓ3&÷²—32z8Ú÷mÞs~Íéû]^%Ï( Ó §¡âöåò›ŸÚý¢ @sf—e«ÓýÀ|js°LÄ…«üÛºù i¡¡½ü&|-Þ‰ã.r¬šé¡ìW BJ§©8Õ¾ØS'Òr©Ë!ÿc|²*LÜŠ‹ˆÍËf]ºù¥™ß}_ô‡‚Á‡t dÅîJ“õ[²r4ùÆ4¨ÞÉÍ| ÉÚ¶ ªêûLG›»Õ^ÅÓ!UÏ©ƒ\²ûl;¾e^Ó®Æ7ásS ØÐ¬Ä=!NAÌ6c*Vö¾o7Ñrüž“²»n3™¡ wÒ 3¸8k=vbøØ¨/!­“èפk"“ËQKNl…ޱÝô‚"¡ðGÉ­|FÜ™ÜUü¼>¢ÈÔä!Q½sܨ²»Åk ;ºÝÞÝzúæÛÏp§ -Ÿ14šào“–ëí¾½%¤/?JArÞ3lHo¯ÔñäãðLšürLoŠå²kÜ6Â-X¢SÀOz RúïSÌ€£ÕôøÉ€­b#e®@c„ÐD¸øf&Ø2WA¢ÄHhܧÒ< RèHëIô¯…ŸÅë¶±ÏT¢‹½ÍG¡×Kê-;:ºƒ®4À—S‡CwMºG1mwGûÐŽüÛKh „,Eù¡-¦+æ¦à·LSªíÒ$W®I¡Á>+$†ÌëaY¦_kNZS¦èâ‹ï<;…ör]gÃÓ‘Ä‘²FVë @hÀ‰FÒ ¹> ±Q»/é=µä¢Tgoé°èŒb}pìŽÓ²;}C¼$_B„nÔR‚oðŒ×QyØ$jÙ+Ÿ”¾87 ’ÃdrJQ{ÜÄêHÜ„pž‚ÔJÐyÖ&Hþd![Š€“Ÿ)îÏi¡å…óï 3ëKê‚Ý \GÚ…7$‡< ÀY¾Žg&| ^éNÿa¸™Ù¹‚-o…WÓÄ¡e2ÈìØçKBå;7ù!A{žÆ(å·FT4¥¡N.38>³K!®:ˆz–¯)³º‡C#Uq%ÿ OÙAÒÆQ»Ç•ø ×ì#²Lõ¢Ï?g6G¹Is“þü[£þ¹„ÚÀý4p‚zP0æ(jzC´Ï;xi‹ðYø8ö>äôÀ’Îj¸R²Ñuý<îýjú/òþ³ueTòE¶€ªÈìT¯Ú‚aým§Cãþó> ŠÁ”råMWËÓÚÒ!oÌä¨eòn®’öiND2­r  sœ°Ó–ä=@òÍšo v“™J?ÎA `¼Û8c¦aï`uÚ¨ÆÄݯZá”(î]Ë.ëýõXÈËû¡0ÐÙÇŒÎÄVF®J|׬"| ôL…¶Õfl´F¶ø™…¸ ¯<…|¹¿Ú„³z#W»RSz‹é݃A\ûUÞimoŠ=“ÚÜ ¶Õ(œj§rËt#7®hë[˜[2„—Ží&‹8ÿ-shÀ²:&¯…“YòdòËÓI˜”ïû-±áÑ==”âÞÛŸþ9ÔÌæ>¬ÏO=lðBb.¯Ž‘¡íˆjB¡Q™HeÌ«3ËòL#&J@Ç`Ü„Äûq€,ª¹x•ô¿²³Óî£ísÃy05ϺgeæœÙ7³èBfMÔG‘Œ~á mv7B`Ð yÐp-¾ëAy |ÅBÙî¿ë‹ÁÐvD„Lœiö@Y¼Ñ+TeöpxÀ𵔩‰!ªŒÂQŒAº*^ÂgoÑ_öLòeçnóP2ØixÿVþÖY©"‰øä Ê_£ºØjôN €]›|¤K¬P^ȸ®ƒà¢w…]³©;¶ hÂüÆ$XdrîFÙÓ¦‰ó}¡à¦Ã—Ê‘A85Ý‘Ã~컟ðáú!T†ˆCp¬o±¸‰dÍ^*¸Ÿ°OªŽ Fbi_ÝFftúƒJ1B} µrÏíLÆú¿ÓR²ØNÍÆu> HÄW1kç €Í~ô%ÇÞHë–s%7Œê42Ʊ,VõùÛE8X"ôR•:& ˜ üÔÃàø…°Ù{ÓGÒÔi¨‘ã™êOø­ã¸Í%àÙ»O‘{S–ƒ…ÇWû$HÏä a‘Žr4/òÌ4Cç{(]?³º2#2æâ¦\5ÍéD09ºC‘=À•è‰ñX,Ò1“wÓÑÒÃt™øÏ–‡Y¬ÙEhëU]KÁ\÷ |Ak ’È›•”~ÚÁŠ9¬",œ%,ÎÅËÇôŸ©”æîý³N ØM3lé¼r´[af¹qÑ@èEÜ&„jO¸x°™JVU"sÞ½Ò†vÃoJÇ5é‘n²<«ÍfÏX'ßRaˆŸY\>Ý®¨ÚjXÑZ²„‚-él–ÝL¿f›ð<¤{ óXF­(“©ùH_/äâÈÚÓw÷K—¸P³:Œ5°ª/o8&¾ Žu Qá ”TÓÀú žZî øe±q*…MšÕßëJÞðU³~ïi¸{Êy H›2¥jxnÂ'†ÒÌ^€ÕÊSœæLzЖrÔ׊°ÉçüW±Þâ{s PŸ¿e c~SûGL¹úoª×\ܼ9|íyQi ÉX, =è0B&~ œÝ\¢mÎö}¹°‡£dg–L•Â';~åªÄAγæ^}4d hÀë¼€¥xb©@Zÿ?3ãù-Ÿ‡‹¢µ}íèßõ;„ð\AÉ,2U>­PnÆg%~Õ±Š%ñ3oE—qˆI•P¸²sÔpäL.VÆÑŸn;D¤æJüxó©îCÅ3\[zÖ:)þS8ësìç²d¦”·¤-Ò“!+7þÿxnà8aÑßÒçfVâx[ \¿§(Øn˜"ú ;IC¡ëg~(îc$‚»®vЯ/9€H ›ð¾XõTGßç[½AT+Ïï6ÃÉi¹èÂCEþG;ËÆúÞQæÖó­Ì•„†iøŒ£FzÁ"û¢:<ïI¿?ãUè{²ßf[×Ws0^ý×`©Õ7p°ÞŽ@Ö›™¹¹‹¸°­QSãÇTƒÙù}ôŒ2w¯x6Qãõ·š¦DRL{üñŒ¾Œ#_*»t0ÿ h­}aSW៧®WYtfí/:ÿF@+U,È[M ÂzæN€SÍv‹uî–y+Õ¨Ê+ùòas6E—4üA£i=|&Ùp«f!²#:>‹aׯ…’9eL'û(éïiìpž#p¿­K˜ZY¥Æ¥òjü?¾@q­ PJ$2ÃÅ÷T3š7 eb#Âf к ÙFEw|öŒ£ê}_E(ZÑK¥Ž™ŠïhÒ&ë>Ï¡èz•›}Q*&=ÊAT×z'rÔ^ëÑëL³euøSþ>r ¸VÁ%æŸÇ÷á`?Ü'Š4ˆÃ‡ÊƒèƒmÉ,Âé<žhò5-€;òùJ¶SÓÑì©…~¤§À4¤(‚ÈdÒR-¿¨Šsó”µI}O{‘L,5—ÿ…ÚÛÂJ ùe^&þR)6A¤I†Lke8t ŒÖ(uÒõ²´|ÞeYÖßÄjD¬1²À ’°à,¢’§£ÏœÆiÓDû sª5‚Øc¥L¯¥ö눞»òßüÚ(g¯$*Ò[l40M}çèjó =#†ùŠãÏj²²KûÙy]£Ê†:iò³r±6X-/ò{TÜ>h²Œ7VТ@‹±LÆ|eù™³Ó]Œ"C3Á)¿Ñ°›ûš {š‰[µNâ˜.+;·viÚ9£9ˆ±Ñž\k¬ø±¢óÈÈÙw{ñYË_—ox6›d G­q×ÅC^jɳZQú ûBI5Ë•ˆK&‰Õ;Õuú9ɱ,všùù3wV†0S½÷àòJŰцI‡k_a|Bj ’ Û)ËrÛßAÛÍ 3¦xPòör•Â2à(_¹¾ö³Šð BÂó›‘§¸)¶Ÿ«‰ó¾ ó"c.¤§^ p§!$Éü·‘-sZg²p}R 5/-RùKH¿ýaò—š§žÎ›žRœ,/ëÇ_NgÔž y!oüö_ô]k=åÇN˜¦ã- $X‚áóuŽ(ÐXzhÒZ—L‘\°|û‘‘G‘µiFì½®¸ä}³[:EŽLcu3<¥vÅm_«Ž‹4ž3¤Ÿ21«çfÜg´ $ÜvPüoŸÓ›p¶º%íN¯›VXKgž!;ðz¸Ë*8eY/®¶\*p®êa1bŲ1KŠ@áG0ð¾bšJXÁè䢓&”ž `ˆãxG¤ÐJŠÉ1!{-ÏÊÞ+ƒ? ø ¼~Ÿ`‹,oª,D9nbü¦}áêó9æA¸2±5„üÀœ,œX #´;*X¥¨öˆv-ù^É‹ŒÆDÊÝ_ÜÞµ²²»Õj1Ƽ7ºÖóÕ6┡q÷_«“²Ïª`†ˆ˜lpd[šöLjþî­ù¯£õëL*ßÄb òršÚ_¦ÉËe'%&?UTÃçœ6ÌÂü<ã•÷\‡ž¾öymâs†˜pà9‹Ý¤*ÀA^j{zœœæÙÆ0_u k ÿ]v¼Ü1ï:ðÙyǮÑDm -ðèÈ©¶ ˆƒcù ûP⊈A&(5£;0û‰vÆkkV#! ê ÷ŒLѦÓWfTaÆ€tE¯wv…=«û…I$#S¦<³Nì«ih°wJ û–°Y&€é|¡“g´û z5ßÞBi-@*É…†« Êy´¢¹.åÖÑù“Ũ„óöËšÃe"V`S§ùBUÑèg]Wðâ[-a×#ÄŠà†fOÿP©­¾™†ŸÊÛpÈO•ß¾Á¢ ZhTÉDÝ…Vm¶°2l€PÐ×Ôàб´™énœ]°= Ê•£—ºVòÜÃ=]ÕâDXò'f“A°Ö?¹ê"ìy½ëD>'_{R Bö½¨É†oU‡srvš˜Š‡V©/YÌFôèuÉû_Ó\ªºÚµòˆ]»Ï&ã¸>²´%¼¸3 SËðRõætGìÿdŸâL´ày}ÛårÑG#Ë(vìIÏ÷jòˆ½sìea¹ºh(M>b„8ºIîHøžê“؃S"/È€€<}ÕÐ)–JÑÃ3êÌaÃÝ"MÆ#bO¸« ô«#__ “'U+Œª¥úA?S~ÌG}R,ÂSÖ-{1c·”WÜÿúßõê–Sad­¶=¸¶ øÐÿb."3c0Ï)ˆ‚VyÐ…éWe[¦r_´)Ø(°Õh\tàK$¶‘¢¯NŒ.’­ÐgÅ̺2PÞÛïõ{¸)ò½uŸP¹+Ü6 ÷áªð'É%8VÔ 6Q¥îX=˜Õ»ps+¼ØtëÁΊ©Wã”Uä‘?¿bh'&ø¯´ÜFÿSf*V"N¹O€ x§ FóÝÏIq°a·®<؅©1–î¡Á“%ãù-r’šhw{¬ïÄñežWyx¯>qM4ÙÒ ‡LS„©– fGh üª\,PÕ°zÙ?YMÒËEë˜g÷±V™ ~ê¼ ZUיּÕüUc»øutwÑææ=sRÊþCŹãréäÉ6l@q6¡•€÷óŸE ØBú&Z°œ£„/ì–¨K»5µò»ðÉu)‘»dÔ蘡€RNnIœî\êÌç›kSÖÿGÏÙ,0ŸE‚õA—ÂÎ ~ˆP0$_8p0\[³7MÕ† Ò¦-VŠt¾´@¨#Bnú^|Šº¸~‚´ÀË–x;óTþí Ù=´P©ç+H_ƒlˆ!Tt¶s­AhLÝ“à FÈUt‹–>ÖZÒUk)[Ó‰{ð@»:û\g¥ègÜ桬ØÕ5ôûNŽç{TŲ^éjÆEȦ×ÉR2¯#)¶gz]“*÷ùöÚ X; ÏqZs6SK{#ùn—{³R6¸µÀüf > ®¤„•5¹”©‚ÙH‰Lä.˜°;Lñ>ÀèÞà Ô¬61ù*„R_´Õ0évð'¤eý¯X •E¡‹F[»Km>~¬£ãµÞ¾š´v­/"czG‰é¨HfäøM_ÅÃ(>…º#ÍeZ )7’ÿk@ÍÃÈʰŒœaÌŸøå$JÈ^<[¿‚DVtÆ–Ôߤ¾CVMÊùElÈø‰Üý:pþÜh £cefb÷×Ü‹/'¨”¡f¢eß<Îîê—‘›ùöŽ÷¼ÓË‚aÎ+OÔHÞüE@¼ö3˜iGÞY òÆ~¢ >çá±õî/ÀBõ(_Uîuæï­ s«mg‘½'ŽQL':Ò„N§0Š6GýÍ\´²ð`(PÀ#o*ÇrÁr¼Â(röí®e@Kͤ¹Ê¢Ë´K–÷)+,fÅÛm–ò`RBã,ýë‰sGãœh¦#mÕ"ÞçŒ$lذ”ÔõÍáPÙÊùZ¹`ó*‰=53NÅñ¥a$Éû"›´ò17*[<·– N7ÙÍ»uÆ$´góƒ± ™ãâ—›—hVUö‡È:Š~²hä?ÂÙÇêØa;Ù÷a”qQnÙß?Àÿ:RÑǰ›38‘ð !«$Ü+àÌŠ‹À-±æLË@J„ —Ë”zŸ ÚõG¦uù2â$þôž`”æyä¨,|1K\Ö0Ô\¬툸!‰÷öb­y%Q+ÿ/šÙ¨)üçâ5G¦-ÔÁ®bvØišY0g…|Aúvl›‡Gɵ ‹@úöOBµOAæ6–ñ›ø1ü!=_Ÿy4§®Þ¢ö¦¯m"Y4Bˆˆ6Ò=ùo¯o&ÊÄìÀT¶µ æ¹Jξ'QãW@QïÈ$¨´ríVß~†Ó·íh€am§þ­(ãkûÿfú§®ZîuaâLøç@ƒs*å"Äzwñ ¶l޵ÍÍB{O;Ÿ³ß–G¥ÂÄÖ-1 ¾ùOŒþ½È_o—J5ã~²ý¡…ÿòÒvK¿ÄùÏï5„ÕøÄK¶¦Mߡ߫îþÛrF5 „£ó8 X¸­7{B¼²'N úŸóÛʹ“oó"ý|ˆÔo>7T°Çf0åYX„"ØîBL?ÅÇÈùÑò„šV€JI—‡™Ð6̖Ô º÷J)3ÿ*ðövÛ¦øŒœuÛ‡ç5’2O.ž­‡zE‡46IdE?KD$µXkzáË­…ÕaÕµn[ R]·ezˆ|#ÑhaL“왚9¯ËP‹²Œ•ªÚγQýbÞ.RÀ"½M¡ÿj˜ ÑÃŒÄë¤Чc4ù—°Hñ{»œD'füšŒ!ÇÅÙýOC–†j,`ÁFÑ -E®›î0Iå4`k›—ìWd!­í.êÎVu‰lNÑ„|&ss~£¾I7;YnœóÓ彿Î>1—TqØx×S 6Ý2”ìRsÇs¿#’PKéu·}›1(hšE Øøãƒ„Ymw8ÔîÑJ»ß^Ù9 ¬x˶18÷ã€mw_Ÿ´ y­¯3¾ÖÛ·VuO¦G»çÒ¤ù|ÄÀ‘ú€ÀÂÚ“«;Ûâ=ñrB€Y¦l^òQÅW¤K=B“lÀku}šiòÑ \£ÇÈj<¬³•)Z¸IéŸyÓLË Í Û‹\#.1ôEdå¶­ƒÙï×g91„h.}¡Á-ç]’ nªÉ› QÆ’šG¥ºò®dõ X6«KË®4†©ã½¹~Š=œ#3(—Ë` ”ÇÛBìÕ{DÝ„¸G¦ƒ«tz·ŠgÌåd”¯­Hè  zÅC&ñ²ÔR”†¨TlXÒ88[y,dÍœ¤u6òø64‘¦Èi ,(ЙɸÍ À¤YzÌ&æ±d=ÒDƒ¦h,‹Ÿt ÉŒTð(U "g$]aVÑ\Ï|3Çÿ9u4iŕ޺K™8b¯šŸ¡Šå‹òêOs³ @/hÔ™,êB€ðNô#!P&ìàºÉ;º„Š`•¸/5R²ƒ$t F¡†K瓜83x ”Ö“ò>5¨²’çëÊäêðæ|Ön¬:8 ëËíõ»bê–ôf,úÛ8sh„V—Sqº¯ho‡âU0@ßDKti›¶i]¾á ÉYX :ž²¤˜%ðÜ-¶¥°™ -’qaÆâ#Ÿti¡ê"¢øo®òñî/c–H…„´)ó.üŠ´Ô²|î!7ÀÿbÎ77˜¤ eÝ{Çhk>®²àI~$i?«JÒ9’Ô¿'Þß@ÜbÝ£êEŠ»tõŠžJ )0i½ÚÛco2ã¨)Z”Lð'[rŠ*ÆJÝ_š¼†õ´3N¾?á‘Ïãu©ÆPåxÞ!Ñ[ó‡Í¶ÚŽK«“ÕNU‰`þ`“ ˜5 n1í||¶Ù-2{{qh¼8¿ìr>ѹûãöö©ûu3öê½õ*ãöõûuRú´ú‹ð_?§Õ{ß>ø¾v ݹ§è¶.Ú2:8gJ«jËVÝ߇+Ò\~ù 2-qé†À4Þ;›öá2ñxu;Ëÿ ijM AîÉ²ë”ØäÍ‚žñ¢¡†…ÚIzóölä厔Dþq³râ0p%î¬C&<›ñ/úpËK6HµH¬t V­…bDTEJ8Ki†)ù…˜ã%›Ê +8óÀZ¢µ×â¸ùmñ—¶òÆp¯ýRàøÛ%‚ ÆÅ¼¡{Ýåêý—i}ƒ þcòPêÓŒÏ5PþßÁ˜w)Gˆ[ø™”~¦º†@×y…̦J/ü§/ßLÁÞ1•Áó)“SÎ ­hµù¡œ\ìÄ‘æáíqT7˜ú»Jèä2ö„Ö–H? XÔÜa½HíQ‚¨€9+‹1.­%1¯vS46Ä æû»x)ª EUDÂW£óã^°µI’¦SP³×Ñëuõ •›É†cl…)“6ŠsÞ?k:Ší¢D,ezˆm·Õ‚F¼EáÀº}PÆúQ}ã ?'<ÚRxÕòò‹KŠªÅ…«—e§ÙQñn˜ N™¹a&žKÜ$Í…mÇ`MN$z$¯5íT)0&0bs4KôìOÐËBµ&‹!ñë¼…¨åZZÂ/ég÷Ù*Í‚gL'ê0Ž¿´»ŠÕŽâûä²3éø–y²I7Š JÛ‘Há„ë—[©ÍáqJ÷xÒÉú=gÝŸ#ͬF¯øDäZ L]x¾Øì$ÛÍ#•- SÍ´åþúpùˆÆš“4¸Ø dÿ%Š!ê‘ï±G ÃŒì=«Ì(`Ù#sœ½#õ›`´Òß( t#qÝ;=¿Éa-,Iƒ“Ñ…O„ŠòPJô[ÇöÓÞj–2c$ KÚG}`Fõq€&.Óyî’÷(`®F•“>f¹W¡à‰Y)'¤üû°%:Â6 ”Íéןþ}žr©X›VÒ÷3FŸÞ ›\ØïÞäîqzÇed6™=m§<ÂÈÏÍÈ\ÎûFò‰Ô/í¼w ”Ãh±G‡Ãža]¯#à‹žV}.†´é¯ÿEÁzø*þfrt™¯YoÀü[Ô¹”²sIUW»÷=ó žÆq1EÓÅPGWïš—ãQä%yúáÈq¡rþõå_å¹°kÃgÅ´©bØ £ŠHv&¥¸þ"W¾ ß|ÓŸ ¿!æAø?fÀŠmïiΨÂío+îÜ’ÚV²ñ>a,U”!”$×½{ú¤Ó'à‘Tr&Z!—Ÿ`©ò6?éjwt±t׀IJ6™w¦6î*¹0 .ÒÀ—@`ú(ª½8¾·…P¡ªl)ÑÔ1’5ïúmÖzoïwÀøÖZ4m}ˆÿ µƒîµÂÑ”÷…ë±ÉÕ¿îÙîwúàï*ã„s8i#VÉuGÏ&|3¹Þ <ħïh#¸ã]×êú•5¸«t’øž,®ÿ;ÑŽ}qƒ†—Ü6) Ø`ƒ/Á†îp|߇Qm“t&°Ó9 ÞÑ×Ì.vÑð#u. ¿þ°V/ÊÕ¨¯°ëæD±HƒZ?n®¯] ‚³%™‘‰åz­ÔÞ,Ö̑Ͻ}K¥I lKë(yV ÕWÈj 0M£®TùÕj´R=&'â³0™nU‹WF3kdò`Î&庼ÜXç±Ø¶}ü£²d Ý)Ä4f»Ú¿Ê¯ùÁW¡n%ošwôVÝ‚t 0þÉMB´!BðÔÖEÚíž:= öcfât˜Þ@?~ˆÉÄ{6ç¢y™ÿE•ç˜PT<Õ;oÀøå|‚Öf>E“œ˜§&T»‚D79…@­&1¾3ißd¬¶¥¼Ú‘\'6çB‘POÙpúº![‰/Y¯³®HÍóY>ñÛ%¦^{¢Kb‚±9ŽÛC ƬæÚD¦5§»[½Æ Õ3ê…î]¡t9àì6i$S|ÅM‡Í 8^ù½lôì@¶¾N3€Z/£ãþZŽƒ'wëi’ H¬5± ß\LVñ@ë_¬â%1Wm_ Ø8ã‘>ìlˆhG /ÚœßÔ)ˆ©hƒ6ˆVÚ¡4O4,"i*®¤û_âM ­«Ë Œ§"bÓázìÀkŸ ?ª“£9üD„ÊW¸EØGn!àÂG1/]kyÐXí‚ç„N»sWÙ9M‹‚¬_‹+å³Ò×EÖ9Í}c5ªY%Æ6D5ÛÒù¯_ö­:>œë¹ò`R3=[–Àˆ*ÚM…4Ç™Õ Ô+µç5÷ ®<„ÊQôÇ;í.Ó|´±ý¹‚ælç3øLþ¾~‘6Ùª_)œF'ýŒëDÙu·w…Bñí¤+/\¡‡Emáâ‚©Ôºh nóGXÏÆú7Lñ+N^óLõ42#p×:[–=À•”ÝXõ«,Ø ¸Gœm×UBü Fëý(¾€^D¶’:‹‚ns¨mjx ÅÉ 6,R2qnÄ…K¨³ªJF°>CmK=æ%'¥z¯ªOêô«7Žm‹ÒÚùŠÞJüTVanˆŽç§DÜf‰] ³=ËÝV•W\jÏÑmÑv&3Îz(X}¡]HÓ7”]K¶]º)E1GrXÕ‚ËæÒ#¾–xÕŒ¬ÄQb¿ŠPÄæÀFësõDgI¤vöàFr²ñBÈT*Lµ¦jÉ4ë× ¶Ÿ±¤1kÎÌ8Ô>,KÜHå‰[GûIXñ-2­‹„gì¢H©ÙÿILÜ<¹a÷QéXõ·4[)Æ©‡zø+ôͲòK`C¦ð‰›â‘(P5Þlá»FV½!£ ¨-‰<¸K©F‘û|£?2§ôðÖ+Ïa–3B()P[&Ç«™…x»UU2Ñ,ôçÿ(¾B·Iš¬R±<€ZÕ ÏÒáŽõ£p¯²i¿¿Õç H¢ôLi½È4\ä çîªÙÖËÃôS\Å©—1ÚŠ‹Èþœ{%,hàÆzcœ±¢i˜jm¼ˆXÕ‰¾sÇ»ÁÝÍg€NÃÁR¹?eü…ŽÕÛM—( ù-Td†—z¾¨ûÜ~Fþ"]âL÷ª“¡|Ž`zÿa~SCMÔâË;W¶ïìÅÒW«æ9{£MìÀ9ž(¥¨íÒŸäø #–±ÚÅ4•T¬RtD…×A+t´0qëÿKaJŒòg¹™cÖL)К9‰Acȧ:QÛ9Ý ºéMNäúÑŽꉨ}!„ ³¨_¥I~§uË™TméÍTwŒ]•mñéãï`ÆcbAïþÐÔ C’š#ã| Џï£D— ºp5Ýýˆà¶Ñpó˜F/óèFp†ekI—o8Õ~&ñÏ:C1%âê½h×Â(Û„ºqñÞsÄ“CNPŒ5Ë&$=>P(ÈÒc‘GïCáíѰA`[šk1tD[*±äw%^“¯½› ÷~#÷H„ ƒ—MñÔÎoÞ6BÍÞg[î¶B¦²²±k‘$HºB‹&ÁŠBe#V.¦‚dÐÐo~C³H0>ï\MoDíŠÎ©Î0”ÍH !-{éçž³‡ù@¢Ø(!}¡ŸÜ‹ãÖ‡œE^náV¶6üR²Þ€YíÒT“…4ìµY•÷Hy=4̆§j}©›‘±]°;”!‹Çg^)ïÛèÄuS=82äxÞ†² eÿo%ÖÈ€z—.E·’Çd¾M™ jJô Gæ¨)qÀőҖ®mÊÉØYBF›/û#©Ý1¿c·n«ûJaãöO! S±Ñ’·[PJ 4üokVOÄ#ÈÌ»ë† NF(ãTl+"Ã4a#àñ$~Ê«4Á¢4¯€1HùžÏç>ࢄ@pLÑmFÿ Q*0u§¸ç½Ê˜JèË*Ðo^¿‚B Ôàé¦ §GŽò* ØŸg_³i\k³éƒúä[©ŒlŠ}F1ç‡P¸­œxý8z ‘01ZvONoHG„h®>˜©åƒÍu‰Š³‹®ž¸ûMÿ+—7fXýWÉ8BÔšI8ßù{#rèô ˆ_n”{8Ð:æi •šp"#Å1KO÷§V¸"Yz3½iˆO´ú½[ž®ñó?PþâTdØà`» ¹)%jU€ì·‰Ò® +¶ãòl`FÍÇb?~½ö TÔ–H”Æ4ŽC>$Ið•ÄßÛ4¯²â¬K´l?c6QÅ'}AÂ¥Àe/­©ÌÞꥄ¸¿ŸcñÅ/< SÒƒÔºÒ€f^éaÑxí‹€ / ÒaÛÜCFº3cW¡tƒ*o3½)•hOÎS¸ÖKøTþð?àÚMåaòRy­|`.9’°ÀÜ¥i=l‹,â,½å×} ÖaÍcàAlG3¤µ1óðÏœ:ª¿BÏ©GBì’gT1à 8Áî\80ZÜe9“_ÅE¨ƒ?³÷‘ÊÊ­c–,±{_+¸”&ù¯NR™jD‘¸ñ‹Ë<Ê-üííÅŸl*’Î*fÂÌY8§­.·½¢UáÌ$í²àÈQ@,«n=ýs³—xk+ ÖáYŸÁ…~¼@#±F®\Ð^·ÞZÐN?v&ˆ1«Í…­xk!B$mP…>éëð lüÑ ¬~ß§P 4.+®5†’¢—"-1½·¥¥IîHMìÅäaXÞóz˜¹D¦‡²I KÞ/õ6€P§ë˜ØïJ¥ÐW—η¡´!;dÚÎí’äÃÿmüë÷ÑÍdt¹»lü×™qŒß’qr<(™ÁSB42·wå´Ekဣ×`n’Êxâø‚_x¸?Ì¡ž ±˜Ж5æöQ‹c‘×ü´´SúŽä9å18ئQCÝ;ªòéÓ‚eq.b‘œ+øµ ô9—‡æA4’h„ÍÊÍ(ôƘQaο8t AÃã[þ[â,Sê‚:~¤`éÌ–k7ªI%]Û7âQÝPÛìËXoQ=ZùzåößÂ̾ވÿClñûtöÚ· _Ûº8_UCûhŸÛºÿ[±€ÌÏP(Øç…ܨ‡(EŒ‘á\qû€QÄøŠlèvº‡ã£¶VÊå\r.f€N?¨döþkÒ­ƒ¢Nŧü‚qŸ\=ç=Ú MnÐ×Q[>m½Z¾àW´ÆþäõŠX<¢R¾43õªçÁSçu¥{yzç -?·_Ǩ-c¸¥ÅÛvŒìÀ½¶,èÀûtO±”pÔTJ9ø("‡yôoeÓ`¾?MóôhŽÌx¦ð9/;ñ‘ðqûGøxÉÁA"sù 'Æî¤áR÷D1Ûÿ~qÔ\õ3áÇì…p‚&sÆzkÌ|z«a¹˜ÜnïX‹eÓðÌMXèS*`©A]È$1SƒÆ#ö]õŸØñg¿jàÇ‹´:¸¯2Wö (í׆~{ÛáÀám ÅõL-¢§|$÷ðø_«Ñ(Ö“_©^ÉbÓt(ÞÖ´ÞÈ›?Xlö,!ÉVý5¯·¬ï{8º86~âLÑ\¯nÊ1ûÈà œTë³3@Ù@ö¿9Š˜T"û«ã±ÖÙ8§ÓÅ)TÔ¨ &ýᮺÖ]^}£Ú¯_ A˜ÕZÿykà™veäʸ»J_N½óÒ/䃪nû0˜O*×a¥fÛOCD:dTŸ+(üª4Óá`FWq²¹§âáîvÓì¹úê+—7NuŠI}C’—³º}p<œbîWu®7Z¸Ö0/Ͼ—­ª=*äƒf‚0|"K 7´d£Äù¸¬­sËd~PU1TBÀ++ uÐQA.Ê÷K/(|Lfzý=˜È-‚×SEZ 1&`Má©{•o^÷x—æ]¾VïM4YpØ3ÀvN·…wP"ðAN”¿W‘Wx W0A{j‘%*7‹ 3~ {-)»‘Õ›ÿ6SÚ´g•£¡î¬«²|6¥G)Ð ô~ ê]Ú‘[íÀò‰‰7ÐkÉâi¦¢Vé3µÙýž{g•'°–S'iŸ¡ É?=ÏzÜ?}òzZ¶|^¹7+%3JßIÿAÖ#׿lúPâá?R¾ì—ÉM¢$1¿Û5t‚+!²|ð(²(\#BXã gÙ}QEŠ1ið‚^IJÏCuÏm¹8qØŒ_’²…ÞPsS‚P%œ½™N,Œ6vÀ›& Yë³jL†á8µb?5ã†Xä]è“'#ªùd«¸“ŒhEqã':ÈÏjÐ}/ŽÑô ÷èƒ;9šò¹q ¿Èùšß`ï€ø£$¸5î„Tû‘[+Èö×õÃÍâ–(ì‹öx%Bîh8ª!C¶î[S\†ÂÚCÐ}Î2k—û)7ðÙ`AÛÍV`æ‹K¥{‘yþ}ž§Ïô¬i}Ö¤¸Á~˜µšˆ±F“g}^Ã}-Gµª1¦Þ¤Ù‹D­Ar®à )×ÊåàÑëÍšý§ÖjP¨Š£Ÿ¨#B×াLÿ[¥:D4Ê5_2W¸Phð+޶}é Ýb©«E–®S3Oìq;Ì߀¡ó±„Ò;žO‚ šTK´À1ÔS1í"O9j,»J$ÿCë…dîùœ¨W0gõrF^ó/"õ#(”ª´,üs›ÚIŠ|àÏvíëj4M4=ô%¼a0H?Á6S8½µÒvpf &êÝõŠfSøg“¾éÿÛ"3'ak@5ÄmÁ €–-pæôjUOZP  ßèÅqbSQO®Ë&ß1³Äéá›Ñõœ =c¨ŠKáro–n¯›´ch¿9-š5‚u¦xcXòI!¯Oç‰ÁF•žåªìŒëþ¶%{I¦X‚9éεað¾ÖE”{¸”W±ÚŒ¤×(L~D"  þ¸ñ{j8ˆ~Žl-™G…JbÛÜ3 ]³ÿ"ºÇÍÌõ3ý¦ …¦OÇ@Ö¨†ý§Ÿò1?´4?¥´g0¤æoÚÖ?l’%Ù‚û‘ÃB±µµ`É<@¬ÅOT˜$ýÌ+¥%ʵQ‚àœ”l˜uX|©±l“žºÿBÎ+ÌF¿§O‚—M,{N&ŒJ"HGÇ\¾¦UlÃz: à¨÷Ñ/½9È•åz#w )nqŒ°S{H$!PÅ`¦¦L!M™?›éÙ¡÷Å«¶LЗÁÑ3¹ê ÷þ…§];ö¾¶ÃøF•…ÉéZ€¢FƘÓÃÜ?2…ž|a‹çy&âØÛ,%zg[ÅQ4ø1IÕ_²MËô }™A~ÎqÝãõN|2Yq/(ËÏM¾ ÿJK/Ôß+ò$ [Ù¥õÕÙ šÇI˜/ÖdïES˜È3†ËMù §Èñ5î*rÒ¤ŽQ”´Àã¯Ã¢ªƒµaèÛªyV?è\êÁ9–ãa–“FÌÔ~&Æþ èl/Ix¸šëj2rt±—õ›ªûÍ˰®YØGÐÉ€i. ý%@~ÅÔ¥+"æPR:vœ…ãT ¿d@½ù©Ñcñ^‘r¦h¹¨nÉ€?±W„ãZL½N5ä’¸coPÃ[K·PñÞ€ðË·.…Ü|œ}òÇ.7Ü. ¿­·‰D+’•fáö¨wniáq VV1øvƒtðo½¿×ŽsL*:K‰Ak‘žéÇK“ýe>O¦ïB¾HçÉÔKy.ìešrAËL•ÞÈ0Em–YÞ]W=nN–IÚ=ÞµÚÊØPôDMe +VW„û³ •@Yˆh|l=|!¬’m<í?C Zœ²Táà>M¸çf©×9ÄDEh4-ƒú²5 Ðzo›÷ã¢2£æ–cT~]P3ṵ́•+åòâ±Î1 ʤ"~P®•Ö¦§ÔšÏ¤aîüž=Ë ÊμÖàÍYQÊy–Ù•ýý…V=ãÌ¡¹À°¿ßɬ…=°èw’ä):Ä‚¿k%@‚ܤª®ü¤,×µ÷ÌÒb0Dqv*1‹îGË*s¼.}3# ©»Wf[).KU Œ’z’0³œ.‚w‹Bö¿©Õ8CáðO´I(^$~ô;ïÚ—¬Ù gš$¨+8Õ¨¯Šf\ÎVïg¯î¤¨ÿ$ðQ&¯ôãëÓŽüºmÝ pgÓyIíÎ"¸$;ï/•Dn=‘>ßýBÉÄýUÏeRŒAœ®wÎýªš?¸‡³!d6 ›ãšÎ­Üm§º“ ‹uÞÇ•g=^Ž Ì±«”·nq~õ¦å—RT9Æ\Ô*µKØPÚ| ³N®Fï‰ÖN?Mõ'ë%þ¿À Êg÷E| 0ÞЇOU±‡£:S†#ÝùI*4QÌ}5V*òí»6E›‚?— vcsN$ä{2zÌ;xU»ØW0P"Dª2k†¿üWŽ4¤{« ¹zˆVÎqúÝrЈY2")ƩՖŸ )=åõx’ ž3Å[ÍU/Ñÿ}‘úE×ÿ†Øu¤¶áCpå y˜µþ޵¦)e€›3Zå~ìû_ûTà*z¼ M]ô}|Î^mZN@hôèoš_Ù†£Rå3"Ǿ¦x1¯Ý) ü|î…cTq÷y~›üôT#é8 5MÈÔ0~ºÙ1ü€(_E2a݇"âIJ—Sœ@žsâ5RýÇ÷nQ»¸ÿißlKŠL!¢âá’•õF» ‘e.;_À‚&Ý—ÅÁù÷¡ Ô¯)¤5ûÎ)Q;²tï«—_W!$¼¶RLVß ~¯ M—!¹ôDùÆæç‡ˆJί¤ò6Š¥2ï³oñ(ìà±Í$Ì<â7(|ŽH³¾­@(9ßõÊMÊP¬:y û÷$ÖÀ†7‹ÓhX#«A4ͧ´AÞŸ]mÚ¢'¸"üïØä¶k‚¡æŒòí?¹$¾Z¬pCËÈøü lÙ둞b€(IüR~˜ðZ0^…«xá\Èh#¾2OêÒÿ\BòT)4b9ï’÷|õ +Ÿ”•ßÙ‚ýBú<ŒâIæ‡x#3BœÄدCÇ¡÷ϵ·«å~:‹²k8*aÆQŸX»:è Ú–0žêÕ2QxMI` s 6óÒD¾œô9 d– 7è¦MØEýžc8ÄâÂꓳ=ø·÷Q±¤XÍ‹EÛiå³lP”ðâËMQÔ-èÜÀ2ƒ’ޕѬ]0ž¯š´mMSƒXÐ<½°Í³æ^ýᯢJŸkF¹î± _%z7‹'»_?ZÈcŸíM ®¯ïtmü þu:E§í‰hMVô‚Lf¶ê-1‰þZ!=iˆ)“Ò©_ Ï{yHÿÙic09‰ jP ‡ ftypjp2 jp2 Ojp2hihdrcolr"cdefjp2cÿOÿQ2ÿR ÿ\ PXX`XX`XX`XXXPPXÿdKakadu-v5.2.1ÿ œÿ“ÏÂÿ1©@öÈI\“K ïR†à2kªY”'Ò+](?TȩsÞïŠ 7AX -ŠK’Æ÷I¨‹. \’|TA7ÁP˜‘'Œ[âÌAê“y± ±§%ç^!TÙA7y- &‰«ž7@g^A–À«A ê–x[–!æBB…*—¸|am”tºŠÜktL®m< >u<¯%PÏ2CЪX'9½]Ê‘¬@­ëÿ3}þ…’“Wý“§`J7y½á±-$†3É­ç*NÉi1þ¬I¨2¼x"« TNð0[jþJt¹öÕhÉæ7@@×ßÏ*ðeJR0~tF œ(cèk%µ¦†N4]ëažÍÅ'»Í³ÏÂØßÈ(Åò0QÑm^ߘÅÑ–®ë5äÅyhÞ³ÿxäO0 _ÖB?’ø…WÎ.5ú²¤ªk†·rÜÄ9©”„¢Žìx|›¢•·o®(8îÅ!¡aù·ËÝÐɰű_bâ_ðñ­¡Ýòw)8-P¥y´‰Ò»)8¤²6T3 ’œð½Ž€È•§‰T¶œ‰û¬úä3™>ë§×5lLÚÍ–U8Xõn=Ij^Aõ5ÏI¾+–úö‡ËÙõzrñ©<=Ò™©&ù+t€·³ ¶¶Y§¡êdŽ)7bÏÎÅ¯Ω…˜¢qV>®ÿNš|J÷„a9ûòœ‚q”|ÐCà K%=!ÝÈ® S‚1ɼl”OðL>Œ#6%Fm f¢%@° ÔyÍw;e)ûäð„£¥'Iç_IŠ,V}‡vÑåkëG—×'@MQy¸z'jzbÃ#Ûí)'½ãò5ºæðV¡™©OSëxÐç9É;΃  ZÛŠOKs›‹€¹Öž ó&ˆ(|ÝPÔ# ëôq)¤";·ë˃ uÕ‘LKɎݸ <èÿ y8-î…R¶—tÿ.ÜJ)ìˆÀ¢®=¬dqð”#ÙvjÁ†Î)T²E nÛP"–ŠÞ°‘9S_àÙSC‘éÔIbž¯¥Yà çáQ/–©2HWnäÆ^ø»¤\;Ȧ 9ç©íîÌü£ïQë÷CÃOîÞñ6غF»¢Ð`Kj5ù B¾ÿ7ÇÊ·Å:­‚Ï7šRoØÕö% úÐïã§kµÿ(´¡–dub b ‰¢qÏp‘í$P Ú$o{RîO¶6#œ1};·ä ÚPóV*ÞþfEÚ¨LŠÌ ìý½Üä,þjÚñ ²Y¼ŠÜÙßž©zý ÉÀ[ÖW"=¯²á§1Ò{#üü)üËF™¾3djA»•V­ÕÚaÊ¢q¦Cè%w+Eyç-HP~z|‹90ë˜JÏÍò?ØüÙ£”}`!uæ¯X {oÈ Ž”^6}rÕa7»í!^fóšTùnšÖgšã&õæ[k¦6Q>¼îÀ¢ëà}A­Ûßwß4UçfbæH–/æÞ{ͳ…Ŷu2f^šCk ®ú¿›è€½µ[và£à £”hí1˳ž‹ËŠË½0ï 6-Æ>[nn ­rßÓ “ÚPÉpéþüé8ùçûøØ²8Ö‘– dlÕÄBA!õZp3"™5¤yPjú†½ JóÒÊ £'W>9É».—%·Ü׸ãoÊ3øÏ·zˆ‘¤¤¢†¦ÖÅUÛiÊ©¢!Ȧ™Ò·¯ª¶C3hWYœùA?À\й‹”ª® ×øüÁeAðÄ´uÇÝHiñ‰å3‡):%ªþJŠïóW]X÷»ïÉ=ÿ`TzéãÍÕûÓÖ®ñrÏÏ~#ðÛ¨üöìØýÝ_ms§³bª™ãšç>s`HÃ|FI“ÐÐqk‰0粯ð<üXÝ;ÙX …NÐü®Ãvf?¦[qpÓ@1þ^'°ÁRo$Ä"v…ûê…Ijœ¹!U0›»‹ˆ´YfœÄ-`†4¨¨‘蓺#JjïG–=k–Ò«oÌ…ZC‡âÞV6öoCiùܘu‚<Öñ$ƒ¶†“»Å0=y~Éy7{4‹æJƒ­M~}à)ФH׈b@áåëì2NŠTU‘µyø¨þÇÕ*#ßøÄtv71ÉÇÁŒÕ¨Á”ا{gÕ#"‹|[)µé+¿Oá ‰¸“žáYx–ÆÏ‘W=¬¨aâ€Tc:¤¿‹-Æ]¥`èh†Œ_hµö%SóÝüZûׂ€Y‹ ºm.†öߢ7‚%q@ÐúkWÏ õA'ÅI)‡6Jjÿz^ÅÒŠ¶íŸ(Vÿ)ïúß|àà÷0aÑž.ûmšÐNí“a¤Làµ/ÞI<ôm¾VÓÄn3&ûÃXíìM»mDËá~„g‹„ö m̆jC¨;e‡×)Áï´†}P'5‰ -†&}Ihu=¹¤ÑWOµ%ñXnQx)xi‚p Ÿé…?Y—ëÔÕÈ‘NÌåR¯•âSX@ÅÉ;7.ð´ *¦Ñ*(Ý*Ÿ_3Xàl åì…Àtx±S—C—ðÄÌšÖNfÞÖ®!÷|’¦ ))/ÉÚÉ ’é°öZäºh†ùvèŠõ…ñ¢ûÂ=?n#Q1÷häkLœ?RU[)ýp¹iT¢u9›žC—¢ºç”…ZŠÒ~²ÀìÂ-¢QgN1ƒR?b\2å(syÿo8*Cðö?a;ßêöÖ'®Jþ²%B¿¤0Â8q4ÙPùh3´ÓtîÅÙ!Õ/<»!tŸ´IPp¯ó€íÖ:ãlù|§²)‡5ÙN.¨ÃÙ(>tq Nk-¦Lg´njäPý­bÓ.8jüÍýÀYõ@.®§ åwnpÃ9 äÖ{iK~,$¿Ñ+=Ö1 hé‰!%µ‘ôGžÝ+Gþv“7>°ñø=RñHG@²š¹I/‡_¢|°ÒD*^Kßåy´.…â´`Ù€áf†x’ù“6zèžH³¨¾C¥eX³’^7µï—Õ ;teRÇ9ùëûÇ€‚ž5—Ý ­ Ê4»ºÛ¨ë{¨¾–Mò‘j’:YÏZáä›’€ E?êkÀ']]>K°¨æU’sÎ6Lvý, 8R@·­÷l•>¸‡r~BK¨úÿAº ½ªxe 'ý€™íp vÝ0öpþ†|¾ Ü!ãG¬>‡#Mûä½ìh®bB/cGý\ý›¹y ǧñ«½xæÐ¼…;ÂChš¿É$ÃC¼¿9æò£Aÿ/‚Ôeš-=Ó—¨`ÔHÈ×yB¹åJ  |–ôêsÈ?(÷õ42+î†,C›“Ì:î™±$~…ƒn4xz؈®ø¥“†Ð¶ žÁ"V½áë³µ`zó8M´^œs°”Lˆ„>w¦- Ž‘°²}ƒbÔ†üæÆœ Þž©µ‘Á?:ŒUk­ ¥·E òد¼Ãú\¼Ë:b‹"#†Ì·Ùâ]¡îó œP“Y`Ž}Ðlç_¸&®^;œ/ü̲ÀU¦ŸÍ .È4ÐÓÙ—a}%ÍÙâY²¹ä/‹´¥Y[Ô-¶l¢Ž,­24yå[¦Ø¼k"½™~˜vð•%ÀÙ"@ì‚øNgÆ'D'´ŽäãÜ™}ÎÛ C·ü8Ä©òÛ¶²N"=[ºj ìHÊ»\“g‘9éážQl4ÛCË‘ý ý=çAh-Î((…æ…"Ï¥Üd_þ*f-ª®}¤r[Øë—©°"l£ç “_^ÜÐe€?¤«(ÙÎ:cäÏŸºöù£¬f¿#G†§Pœ£ô£¾&(կщ$sœÏ³HKÏðÁCþÅ"L–H@?aç¨Æ'‹ñJÊ ¬VN‰Mâ¯kŠ”}3{§ý-,6éÔ}¼%{¿¶=ñN£¼2ø!fn‰]àü.\‡{´ãá´ô4Bÿ&Üù1è™KŠc³$g[º<†Hžbûæ%ÑŠ„œ@È.íNU³l/-û%ú-Á"Þ'™ MT0žN³.ÿ°!œˆžÁ×{¤ÏÍÍj£´b¥†óFn"öÌßi!Üd4u&}ñÏÎùÂîÎÙÙg#A5w%”Ï$¥!ž{²‘¯¨>:)¥bEÿQµ\ "‡¡ôy0Bì,ZK1;ytž2ðÔ«7Š=Vý£¡+®+ZË2Ç*z}Ç‘|LÄÁûL4v.«Är,Þ(VUüz‰e^É=Tu°Ü,„ VvÍNÍgHVXÇg_zGQg]Ü%'•¬µÚºl}ª[ ï<œŸpBè¨wÈïA•°_)ðHˆ¢ÏäÔà=;9ëQë>–ï F}ˆ86ØlV]H´d=(;`^ˆó‹È8^bÛCƒqXwE³âˆJ–Å’È|o|湉Ç.ý1ËšÖwb"Þ {'c§¤W,ÇMµ=©šˆ¦v$¡áJÊœmhjZLXzzýùf_çöÎ|–Ž’½üÉ"=ÜuM†z„X¹xà?¦±'¾6úß‹aß Æ-|îzvIK´´ó¼tÿ*uÞ 1Gº=‘SKÐE„_ìp<¸ÐTðÒdYÿ­ðNâÊ;j!̧§6ú6”K3(°žb‹ž@r•Õ# 8›eÏš]6ssàK0!¼nUbÛoÿ_ñ cÜ8à Ñv/À)„uM|Ò7ZZ{Ÿ øŒQ©8ÖHàéf¨ýiàb%7#/Ë{ò±’*â¢íòF€ ³0QX&Ó¨ý]¦>²öYÒEŒsM‹DÄÀHH;5I×5S •Ì_Ìf„€[Á-£üËA Å!’SšÑ¬ÿ;Ÿf·Qa¯V61åŸð^zh-ôSLÃ+IÈ"¥eÓ:¨ …l­µ -rʤª‹7~BuÝJØC¢£ÀÐ)¸/X˜L ˆe)æ\>¿wTØù"†?$œ¸ýÚkˆ¯´i˜o¶ÊZ.Cy[! Wh­Åë_“ÑDú"ͨ£¿2ÏÏÔ’~~ˆ1ùú €ö…*âᨌ˜íÇ{J)”Ïøû ñiÈïYw,9E'O޵R^0ÙvJöMêëñC÷-æAÈÒoâÇÕPÓ¹ÒjÀ2ì;£úm=ÓØj!¯—¤V¤‡Zv#@; Ö£÷ѺHmX çšý¾ ã°³g‘û¿žb6\Ç~ATËÌ%Iᾜ !§îÃÙ5ŠBùµ§\ä输m]c¬Èù >ÿ6û~¡Û6,}ìf ÉAVâ ç-ß.掜üéôË:Y;'x®¹ÈgÊ)-/ýTïÌ¢~KhÈFºåãžH∠éSû`Ê4‚mC;VÅ–9оliÛùÙÔ'2øÕ…z¾Ùý¬Õœ¥¶¦ÕÀûÛÅ §®gHñ¤È0…09…ÐWÄg]>¢äûAhtr«Vÿ„ŽzëSDݺÆÑGk/_5¥ÄWrVâ¯÷†.ZO ¬ rþCÑÒÀê6¸¢.WY׆smƒ¢‘OX…Ör·J9~J`g)#WàÆ‡aE·7¹WÉJFÒ7Ñ!ÓÓàÜQ>$í8²(š°FA%\(õ)ñаëë:çMÍ„µ£}êÑ…)‚…£ŒW\;ë1˜©ÅãäŠkvÏÅN¬æ31½‘„³24.þ -ô»‘á`ׇí!OüÕ(`®¸«'y×p¾Å›ƒVßXŒn…±K®!Ëy êøÄ…òå+í³ y,5‡ä‡tŸ´d¹‰)mIeIx§ { ˆ3¥ [ „8´œï‰é»6€"Ö t_)ÿ_)m)?g´mÁЛ¹lÔ«ÇÀóò¤/Óy}´dgƒçRCõ,ò6—c•ª¡—%çÏÔE,¡}Ÿ€¿Š:†5aª~6"@/Šß”ŒÚ÷¹ái¸ “çÓÛ`álhœuéßö“‚?6$ƒß¡ð á¨¤"i½BIzÌÞâíÉáÔËyQ´‘é¨JWXN Dd5²SWäN•‰· Ä/„Á LH•} ÍõµkºÇ0ÌÁòxKrq(È&árǺˆ=ÂÅ|„ UYH dsŽ)ç°™wÇÌýl(çm Eû7~|0¥ZyxmiÈËùµdb<øŸµYAUÅGJbhpQL{Ò\k«Í»¦L¾}pêÚêõ¦ä.¢¿í(0z)Ù^À:ãÂ-6 Õ±sÍO›~Ö¿þ©Þ¥¥+‰eޏƒ§r†õ;GV åþï?Pó9¡½^dm Anˆ1µÊï›sºŸ.…T¸vÉÍ1Ó(e%ÀïRü•ý>|5É%mf‰d‹ÎŒ;8¯Yo;ÌË ¢J6iIuuYa÷Ê•©ÚÄØÑ­®.`Y ̧ьÓÌŸ™h­ER@Ku°ß((|ìð´Lz1×;u"þȤ”0Ô‹UvÃß8Á2Èâ%Ç÷²à,§ï’òÏ¢µ¹ ÀOb¼Î&Ú6ˆA´_HÎÙaÈñσžµïÈ a‹%ô¯/¶dðûK éC<±Í×»žè{@öQöl" ‡Š®òl‚<ÛÆƒ{ÖÚ¶k+!ì |QTw`lÙl31Ú8g ŽÝœ…??5ž}¬!zƒ•/e|pŸWâŽe‰Ìf¶ú/ý¹'º€õ¨ï)3¦×P 1ÏÔo)0&MügÈú,Ø6‚<_̯u,áòݨtK×(0LO£¹…ưoўÒKrs˱®8\/–„=íñíºH1eå܈ñ²£Õe¿ÏSNÊ®âÔ<:™ÓÒRæ­µ –9ínÌa^ sOHß~:£œ{4 F/pha8šüùºìâs—6+K1 ,¨{åý€›dAg«³õ–Ð}T·h$ ï¯æÆt<ÅZ&«³ô,Â÷ù½÷EçÀ>sP«ÎÒ†"þ¶Þ1F¹„)ú€|ª¬A”ù™ÈÖ†Wå‘;i¸ïV¿¢*’Ù°ÁäiU´H¡Äæ¥D%” u=Í·É?¥Y¼ˆ€á²¦ á }RµÔOªÇÑüxi¾¥,È«kÈ߇ 1¸I> êºyÎüÕ@•èD…S ~ãñ\—aoñ¾­Ø1tɽOAà]/³…üþFÍ{þð0òY§Ó“óJû4ÃLµqÚ™“QÊÅsćé™N¼ÑÙô|ºZš‘ÔWB ƒðŸÑ¬íÏÞˆ…û~ÿHëÚt6 ¥W¹>þ€/µ˜ÿw¬O4:Í]Ðp‰¦æ­¡úJFk‚ÏŸ" ·wÁýeŠà×í?Ly×E˜Ôs2TnkúZ•ÛLl%£±è¾åQW¶™ø¨ÿ#D=ÐÞéäL²í 0˜Nz«·}’¼Nl¡…èÑ'›¿‰Æ£i¤¶u)iMÝ>!Ñ•¡Ð0è©MQsÁ¤,c0Æ6Z]ȘOå^üÌæ£ì?¡e¢ÃF‘k¾É¤ÞÏT-sù¥ËºÀ`|<)õÀœÛÕ3Í·+–¥†F(Ð<ÿÉ“&hr­>@< ¯zr¸d±FûÿjúøñßA²;Kð­G;‹ÓöÞEûÞD°“ÄNÄ,Óà<w³¹Ñ§èáÎ ›Ä´n-X ¥ÈÁÝKcœU1"UˆØ‹“wqÇŽwxNÁ„@bHKjå.…„7|Û½|^ivó€Ù¶%ð£7Ç¥)åaÜ,ö¼ì¡b”Ó±––e^€õÕH’8)sô¬¶ŸWòÒÓþ”³/øµ½~? Ÿé0æëïg qÔäÃy.ÃèÃ]LÊxBdºï@£sL¸v¨mª ©O}¤PLR,„)šÐ«P;¼8hœ›ùìR7yjY|'9öÚÐÚH ‡‘7] ÈM\¿Xèë1 ÌËf\޾ûÂn”³å²>ãD<õ‰}Y;A8o§Ð±÷üxL¼«ÙNNi¿õBêÙ(’™Ë ¢NÿR¢½$P$)—´¼#Q^¿½Š"7—Ÿ2ç9¢ÌƒÐ×R‹°ÌÛGôPµƒÍ £°¿]|¤‚ÚÔqx0ñþS—šøšY®Š sD ’v ’>ÕÄuT5A£¼ËM¢í~â²:k*ÈxO1–ùÁvj»Ï—²æxø(Y*Ò•¨%áœ<ó]åÕCåì{޵“f(uÙM¥ú©",Ó³ß 3˜ õ¨ÿ0,'CÏR«‘efÈ>>ްÕú^õÁK“Þ,-£Ç FUÛ5Íy‰¾þHÚzÚe *ØC:æRã$ßÎwðÖ´÷É—§mûwG·"Yw?8Ê×Ñ>¯±ðS‰C ƒõtü‚kËz’šGºˆzüžíy”27šÓúq^”zMµˆf—Ç´‚˜7þˆá†‹Cß>zP3õ<¯ÔâÛ—:õo£ ô·Éž—é>Ó¥m9ÌïkIý4ÑMÓYῲÄåÖ|Tõr‹Œc.ÎNN2!㟯|ê1¼ÜÌ:ß1Z‹~ù˜ŽCÜÃÛƒæb_ófüp¼øð‚±¡¼Ñ½0ã o0°ü *;Õw¥óKkc-Ò—‡äh$FZ~cÏÈt댵À¢Ñ÷ûö1᱌¦aâußp”(e¦£¬=H/ÚeVûü’a»§~ÒQ©ñ«=˜YÞ c¨×gnº¯ÚÏòô±†Bšñkµ®mp†Xõq‹Çý\!ø£`÷÷ߌZ9u>À¼áòàuâ7‹G–P–Ù&qð bYL¢Á_Œ‘öªÒfõ…ZÅ™H³öˆGð¼áJzZsŠk.CG®Ïª×@²ŸóĦ§¤é˜NÃÿ5Î_7â½ý£ q…Ÿ–imA6ìˆ^ÜQ:úW€™h¼W8ÒRÛÙC È€žƒTKÁ¹0©èÐØéxp$“¼/Ky”åè$î;å¨ RRwGËÔÉ%>ý¸A¸ôîBH–ÝwJѼ=´®ŠÕ›õÐE©~có¦ýBij|Ø{´é2Ö= Ï£ðtõÖ£HV…Îå‡Õ€ÎƒÁäJB’éîÝp®¬ lf8>óð"'»29äS(Ü…bqcù³¸õs«8ÛõÞwµ¦”ÆÎö–ÿz Ò÷ËÚEž8Ûýܵ¼Ú"V8¹çZuèwä+lº†Z³lƒþf"p´¦fi ‘öî~ Ét::8ÜõcXÇ?ÞÔTSó|u ÈHŸncTFÊžãõÿ14EŸNä¬R§>$ž}f(*!T Ì‚!=¢bcl57‘>ƒ\L)”ÚÅõ!ªÏ~À=ÿB;e‡Æ‘ LÇ"ý‚ñǤ© ”²>ãé·i›I0ÁõmÌV³@úµ€Î@ÈxÉyµtá­ígºº-5\LsÕ‡Ž,o·á£̵(÷SŸØÛ=ÛMû¤¢T]3¢.¢`·]ú‰ßq`cò°â1PcŒºéÕU95Ÿ0ôŽÁ ý®`¦ÚMº?¡]‰´nìÉš÷³»ô§XP5´ìîúÂÐù¡%š´ ­8¸@Ö³U·3XÝnƒ±Xí„Í“]4.*"(ù+-¤9±ðA£RO±¼Q¡8垣ÜUdÎbäûýtÊ L‰c¦ö<~3 o€­K«šÒ<ß•<í³ß,ÔùSØaÂE*ñ¢h§í`Ï[‰¥}k}|$·$£˜¼ð§˜obßo•‘ÒÍùy Â6ß­¤â JwV|4nÞX'зØu¡¶úxØkf¦4Ûæi¼htÅ3×K2Ð∢9F¹Õ6eÃãñjîr¸[’; ËCûrFYøÇˆÞ™tû<8Ï2´Z?–Ä`Ñö"=~6ö±ÍÛ2‘Ûpô´(¢xH5Ñz#&^í¯))»(ß87JŽ™^<É%ÇíÖÞX p( âe»÷­ÒÒ&¦H«1"ÛÎÛ~pk5€TH½øÈ¶‰e;‚º¸ñü9³4`fÛ˜Z Núò#Õá“2§Øíä3¢ÝZ®frçGÖ@m§i+ :†H)€c"JG%‡Z1ÖšÄ Ú0y)T?¢%ÙVÔ/é$ùi£Ïaéµê¿ø?Ÿäë¿ P^2I|:Ž^’íúdãÎ|ÄÖ¯³( Ƶšämÿj¶}Nô¹ÊáS„-ž$Ðb8uÔL’ÊÍcñ(–þ®Ï -²&ɱÇF(®.Ü>ÄAOCPŒ¡µŽ‹§`iøÚ*çPûç6ŽÁe|”S¢k nÍq6Ԯķ,».M#zКUpG³]ù®´ÂýÆ@­ îÆ"¦MÅ«¼å‘)h©&Üy &DU\‰GÆêûωczš­.ÏŠ üiwpÚ4¬_™Û«šgVû3{LX“uöñBn›ËfŽéá¾ÏG¾oÑÔ£%Jî‹;JäÛ1E´_Ï´­T¦Ô¶Õ UÖ¢"Ù%™/ÚÝW< 6ÇXKÔ€ÙcCžóg×Óü.}î9!BÐA±m/¿¹öè¢Å·ôÁgqŒÎJ|üãåßLêDxn¢©ó uŸŸ”ÿ*æOàx˜v“BAR,oqÙlk…/Z€Â¸ Ô«Kol©P"w¼0ôŠEŸ1O·Ylçzú'ú[¯ßµ1_9d ß!˜LL½œvilPöCb¡e»ÕÙ/^^µ g¤3hj‹š N¤ÍŠ¢I`@#C¾¾[‰H']¿ª»Äy©BOq§¶Õ®Ò(¥%xüJ®u4֤ݱº8•W–F F1ñU—ÔROoy _7;<ÖÄ_Ϊ 6¼¼¿ñš:Õ!—d©èºÀD5w{¬b´áIµ>` )l$ q›Oh•¨0v—EH~:%xº(%é#.Ä”?eá s†1xLì…u‘ÐrK¸•þEgûrSGbš­nˆß0®2‡Îôkw:!ð<%BÒ/_ö[×ú;ÿ~~RYs¿ë|rü5£NkT0Ppj*"ÐmâcÓ"€Õã™Õ`O‘[‰û­6ÏŠê©}Z;mŸŠá·ú”™*ÅäjÜ'2âŒç!<ŒO –Í4údkwo;ßIzÏøæ¬Ï¯N¼Öä•,•8!X\t‚GPz!À5¾ÁòR^ŒalÎ £mb\KìŸ7²Wç.ŽÓêÔs6<é3ÌÊ*7Ó ÆeE--§ã_þ«˜ïUn0zÈãYY –º†ó1J ùŒü]¡º·%CþÄw*vFEó ¼À‚Ï2Ë>ÅÍ»Gjy–bóƒnܱ£Ð› (œ'”ý;¹¦:Ũ`¡:†^|˨C´kRÙûÐú)-EÂwJw@¾h­RMóÀ6ÒyZ¨Ní9ÈE?ùòÖHÿÀl‘|oÃ2ùZ tÑåq$P®!É%ŠWsSùê­Ã>Ž$&“iÓܧÌÚ¤j4à¹ô¤‰×•àÕ?êBoCÃuØHÓ¿‹%#^XCÀw©à¨›€ö¾$aöιc'ÀmÚqŸÖ=ÔËpDßbÿ‹eÊ„›©¼àPÈrE¬Ðw•á§lßdÞ c›P‹¶pÈÔ³ip8ÁþŒ-Å»Tisë nå—¯#0iî=§¤÷º.ZßKj8ý>Wõ¦¨oœ5§^€ûßw2oéÎI{hxŸÒáýïÔt¾ã¶C‡sº@¸ã6FëÂŒÒNØŒ³e™®ÌîÃ9ÛÑW½”ANž ݘÈ2¤óÛjÉ)§cb—].{P±Úy>8[õ÷I ב£µµË8»Üž9¦ÌQºÜ_ÙD% ®³Ò{Õà›ƒ7Y?"» ¨ÚüK@àÜV÷xs1J8ë_JÎ_+uéÊ”!l4{>oAb#¼}#·l†ü˜–Ùù”C&ëQÙ}oöI(®ÒšNM£bSå´Î²ýY¿R²09»Ps,'5 ½8D¯`ùͺ@ç|ß®­nÒ¹ßÜ\Ùñ¸çþk B jE¢Ž[ùãöz9 ·¡#?¯»¼¡mž¬*í:¥_ý6:ÏRnôêHkRúh¶ävÑMöT“×¼Tß8J­‚ƒÙ2ˆ¶LGìD…ùt¶S…×øGNøØïŒ\ûMCÝ›e I\ÌD·ºò ŒR]]•Óâñ•i •WH´ê©ÚíÅx{/xÂdæüÇ%IkƒØVŽ\ :FÕ³^+ X¼Z‚Xp¸i%Ï“ù|5²4ßɊм=™KP¯• Ü“SŸI € ó£œ~ÀÞ×ou¡Úî»É(oDˆ<õ]û®‡Õ"ÿ1qÓ–{µ4Qª ¾þ–ˆ 0js„hLÙ­Û -7³W”"7$FDFUQÁ@ÒHüéüÌv˜/’)TÊö!ªÌ™t2¶¯3—SÿweÁóÔí”G۬܀c2çducÍRîÏ$§ØÆ¸Zù/w^“Aÿ_!Ç óØ»ñÜ}1U¶'aß/]ItÀÿqr-}Â5—Ýe±)›7ã„lLù*+æÆÌè„©êе„]Ç-ð†þ³@Ê&$úR^!`2×Ô9xÐO—î ©­õåŽK!|÷¾³Ïã?UD{°ñ~Uçîî4brW‚y›ÿ ç|b‚1>Çýcó`‹îÄüe1<ÿz+¹ÜQ˜y¾Äy¹iìhMpþƒbÝtÎQ`ÈÙDH}Bë Á¼ôÌS™ÀÄÒ¶¸ÖxŽLÀu§l²ˆ]xú¼Sú‡‡²õ;Nm•³« Bà5UA-þ‹‰(yÌ“ ª¥Á€:³VÜígie´²?#ìèûvZ¡A-­±˜Çu¬¯a S˜Þ>Ÿxöx,šcKø$×*í©ô1±9Ϻ_ËÓæ$9Õ0í»qÍ¥ÏO!:—Ò»9Ú¾ ó7¦Ó × MˆeƲ–m³6áAñƒAúbk ¿â!ç›GÉM”À(FÛ:·LFc×Ï€F·]*GÅTb[D½é&R:‹qœ=UÙ¾Ò3BèÉã³Ì?øPâŽZ%ª÷Eð;¢LHŽpÚ®ß.䯕c4ò±"ÔvîÂ<齦³{w%Þç혣þ/\IÒ¶4`µ/l[®°õTxÔæÉÞÓýþèÍobñ&9P€±ÈÈ "*—]_vòGbdì#å]¿ÛU“\Í^éba>v¶3Œ\iåØœP¤ý­aë†;ûQ£h{ò¡%—!Vv拚‹jØ Hmmœ¥ "þS¡2Jÿ…Ÿl‡H¼¼ý‚› ¥l»™â»ÉgÅ´¨yHúk&ä†~½æ‰Ÿ‘ÝÒ°kÚçS¿T?“„ú»±x¶~ÒJèèî°‚P-sx• ÝÁÖüuë—¯Ô=êê]Žö\ô!£½Ö‘1ø¼<Õg!9ÎîÄ „!Vã„*èÌ{p×¥Þ„_—µü%Ÿc®¹jJ_'¡,(í±ºpßQÙ%äÅÈ%>`4PÀü ÉWwù¬Z!JüyRo u«*ÃâS™ƒ+Â¥šm& RºÂ¶p %ñ3÷±½¬eºÇxcSÝhÀ³HI£Ky»û•Fü¾ÐÔàÁÿ#:O#ó<Çè’µp@á|@è¸W›ªÄëaŒ0 XgdB4yþœw'¾EÚƒ²9†p¼Ûóüñœ Kñùò-X®®R´˜ÔRlqUüˆ,¸®=^”‹ž†&âãøzÿi_ìÿpéo/÷Kðëü:\þ}ãøvü:Oþ2Àè’ÉÛphÈYàŸßçÿFáéôÄ"›ÿ †ëWÆ>žªaÌ´D†þÇ¢Dˆã[×ï­xïðܯÐ;_lT«KØæHÏàhÛðüN}Ñ:u¥Ä8PKÓoòœp³Ô¨A‹0Ñ޽°––+U'–~n £|Dßónó J]´Äù øŽ*Áõk5t¨¬ÓW¹ƒ —‘*ÎÂüß>o†b1ý³V;bÙ~Ò³¿dÝë‚ÿYpd’ b` Z’§/öÓBOqzoñs èslQ1¬Ê =›©–™úÉF¥Œù±Yw–ž,B™E@D?´GÆÞ×,­É$(©?d’™Ñ@r Êë¾D>wæ9žÄ€÷i®#ܤС\®“•=§uÓˆ£¡ÄRgÁ-{Ïø(±ûè@XípzÊ{?w‹.6ËM‚N› rÖ?=œŒCѰÐaëV#t#èÙH$<ärÇè¢%–p ´)¶ãy>Ænû[¡GeNš×2ç2Ác¤Hp{Á»Ç±å[ÂÆ¾ Â'Ë€‰=Âüt›Á/©gåT%¹®e‡{¨ ë £ QºF4õ‚žö ¸R®¢:$nÐöƲ¯ïtÁPîhkõ*J°$0çÂû Ù÷§ gu8‹z#h©8Ý:Ú6%µŒ/~/qadõ7S«õŠ#ƒ‡¸V5„³¯ɳ«M›FÂ¥l©-Ò˜èF½uÛ ï:Quìr¬¤ßÚ1G¿ÕBNòEçbùá_à:<0Ös87TMØ“¯*jÍß"s®O¢ ER¸2.6êlý^¹²5:¸¯YâÌõÛ·ïP€B¼¢Êɰ'9(ÙâÏ“«"3ú|±–ñÊ¡ü&å&aÝL­ QÖxµ‹¡•šô)⾉üq\Ä`›È¹,!ܯ`—çº>üÞý¾Så}ûÍÅH!}[‹}¥ü;ææ~ô!"UÿAWÌûƉgiy \]¦mWlª.ϺP êÊ!)ä£Õ²vV$'QPLô·wª~ANÈT`g Ôøö˜‹±ô*bì™§f¢8]è:pñy½¦ÊÒìéÙÿQÙ8ä.g×Ñ›t ugnYÎ2;êçz—σb>ë,4Ñ—sE»à?@ÎF G2VÞwÚÕƒ îFˆÄð~¾u­uœÃ´Á`ó+Pîa“oæ³2ªû„ÄX4c†ÿ[\›uÿïà´4tâõ•@ÅTpö[ÏVÙ2X$×h¤qtäxºéçø¥¹yªl—´‘ÚVÒ‡5f §[¢S¥Í¿í(ˆ1HØéìïœ(*8ýƒøG¹§6RŠS{C¦é{gÞ÷ƒm,“›zSüÌÚ¶¶aóõR&ʵx“‘á¶~Gú_æ^'Ô²÷¸+oû©Ã_fúåQ‘*²²Ç“7±Ã¾AÆ_´YdõîÈ~èOr~ë~¼‹ŸN0¯s;ÁqÉéä0aº¡Üš•37Ç膿ÍIßKØÃ'Ðù³­£q}näm`w˜³¡q®Ì|­Ø˜ó¸±n î§ GråÕÔ‚µ%ÆÝ}Ó7C‹ñF¥±VqK@q#ú¹œ×äô{­ño L³¡Ñv‹)ìÐã-Ã#ÜS\‰Ò07¹óJT[Œ¾}âÃecùùŽa“ü—Y¦ç¿a.CÙ?óD€§£5ãšâHÙ"éÄé±gÚ÷\’ ¥Àþù›Î¢¡`ñ“-VÖLSÐúeäÜW½×Ä7ˆ°?ÆGH é‹%‰ Üæu#ä7…—!k³55ñ D˜ ç’¨l÷BhöŽDµ8ÔL.«Ð–óT¹±ß¬%…¡5€ÿ ðhèQ H.ÉD}îÙÅxA€n“Zxe…Þ0#ÀT`%^®BEò++9Ì.Í™ àSYh¥¾ø‹#ð$åvN~°=Ž–8ˆ©qE¢.jÚf§YéM›] ~¶ˆNµJ¦Öôyß„Åd7²j`2Õf›ðƒÈ+=[â(Ag ÞÒ4´^ʼnRÔðáky9F2 MÉú]#ŒsèÒíâhécõÀʈÖuñ0ÀÓÚ·šœ|fL1 !¸îÁdqjExÞòùcª”ï>Y+›Pð:>™«{Nro%‹&0ÙVÊžsïLX®|²·Ã‰…nîªI§7Lˆ¾Á¡¢Ô¡×Úƒà!@C‘c¼ðJ8)S®C"5Å5QyWßP%§ _,ê9Æ w¬mÑOw©[Ú8^$Á”Ot%§²Q¼€u!PYz¨¿V½ý:2.hÝ e³TéB®¾ù®˜–pyÒA¸ÕD"_Κïª`\…EnVvÓ¸XžbÔÊVŸžåN½ð$ÏOĻϘÒ7Âe&åàt=  y Yèéÿ_?ÒLÇœÿhW&ÁÛTC§@³Èlöšlö÷RÃòþ Ëù¯ÉËxìfýdz…Ä'tqèÊØõ(p½)'‘4G ¼û\È(%Y v—vÁªåsòðòÀ$y} ˜ ¶óÂþ>NA‘¶ªË££UzÕv·P™96Q!ã€<ù©É¢[ÆRÑ? A²A BÃø‰¨XXÆ¥ù—_cÈ1¶=ˆ19ŠmäAsTúš¦:Ò”’;›¤8· ¢pSô3ÌE­Jò)¹¹•- +j|Ç.þò¬nqibìXÔ·a@j'˜Ã!ÔiìÏó¬@T_xÈó·H A°M¢1q@ž(ù2 …´­7\R-ZupáÉ>ýžK ºŠwj¡*´cvîNùYCê¨é–Záa¨»Yoa„î;QÅÑn…G­ Ó¿ .¼ñر p5ÐDžÞ›—RäÿŒ¾ëÂ7ª!J7+«ê`•ÅmœÃ›Øbr¯ï£žF—(¿ ‡X!¥’çû®1áÎ-ñ·˜â Mðv«¹õ9±‰ó¿±»ºê¸.úÛs £fã|ùõ¬S~9´º=Ù²öH5è,RaËë–aº¹{;M(ÓO%,SŽÙ‹™*×÷ k€äIY=ƒ¿wI-êab»€¢ªéc¬/0áöuÐí2‹œ¡¿eºVY1Õ7£°9 ,õÙô®?aÒºâÔƒ%åž lß]‹åªŒ-R Š@>â¥\½È°su #.k³25u/m代à),R8³§øM‘mŽºéÔŠ¿Š)çÉLwŽm¹²GEµ1v ÔÀŒ2T´ë컄N6Î7ŽèÖœÙ(:ü¡,áKÓ}Õ‘þã² Ûks“¦k]c¥šà"S…âì?5HY%ܳ`ˆ"ÔEº_È+`l &Y§=×t!‘Õe·]”ÿ?Èm4HÑó±ËIwó$PÅxÕoí&É´“þ] æ úxÒ£®®+òb:ÚÔðð q¬ˆÚ¬£‹Œ\¬ëi Ñ’öú&w-R”Ê´î§%ý¤¤VeQænR¢„T»|„p‡fÉ…Qéá«×S¸(÷رI0¨j@bFëäfág­î ÆÃ€ðô²ˆz`¨aàa׸ûÌZ Û «ÄèÑè`­—Ì”6ƨ»÷™\ŒƒÌ8wØf ‡csÍ“%r¤¦!¶2ÞÖe;lÐQÒ==qžnÀÓ8þÁk8¾ C<üЇùÛåßk‚d4ÚÞ五¿ø5±ÜxKHöÁpÀbnYO°i3‘F³ðcÿï6SøÔ€Àzo-ê7þM^Û8&Òyl¬d•òS¸awåOͱ…8л R,-;óÛ’ ²T¸Ü©LÞ,\bm?û#iMÆY!7oÛ‹ÖWPÍ£–â«èµ ;â-­FÔ©Õ|¾$û¨«¥¬\*%Žiqj¨Þ»²¤Õ!óNâ;ó!nŠe¸Z!Ï*¯E1ßu<ºoV0Ǿ¨üDpÊ62y.§& Ýƒ…/“½ù…êµÖ³,[/ ¼àI Rýóár›D…¥x-ª*3‡¾ìmŒêÚÉžexdpŒlüúHUÜŠºÉúþìûwHsâ­ ïøÔT‹A ·!v'¤=­ŽwüÙƒývçÿwƒ¬´6à´P|÷ï–0BžŒèðÈãljìo³²åÚºF©Î·Û0Õ';¥^4ÈfÜü‚á„ìAgÂq 1Ãgä÷Å̾Éã™Z÷ÿi“RL‹ƒãßái)¤xÝbe @¬x/& Ó|Â]âÃc%ÂÚHϰAõ W:fô=âéª|8æîYxc@ƒØXÑA±Ÿ&½æÄ%:‰k NXíÛw|º¸¯¤ä¸ #ƒA¢Z8©—⤬*â¨&nù•~Cl‰º­-×y/ÉiœíSCáVuNîçxv,;ö}l+´éCR©2ÙÉBlsNýxÎÇ»àˆTR"éÍ€úÂvF.øˆä—^=ËÃËAV\èHù¤^ÚŸç O:‹>:AªAP±–@$Dûñ²Ì’`Jð¶Ä”~­%!QP‹E½bAjÜ"…7xNÿ;-o&é‘C¤žKn’VáEñ#¬-„²èŸ®Ñ¢Ô0P4âlH¼›ÂЃH·£H®B—#1U¥d¸E]^Ùƒ€°–R“›Éüã×}hñƾšè†íÖ _.ø=(eÛ9¬P‰¤.^Yáíá€EÞÑ£+¶bìøßý*½cIj اzfr–ë;^ŠÚ£÷u)C"ñy k,˜£ƒë9\}V·!L"øÌ+y/<-:aìUêu•e·¸sÂ'›t݇7Bác™,áãfÛ*kþ¼¬†ˆSÅ,|S°U&RÉhàw›8›‘P<ûGÞ$•d?§Ð9âöDl=Îò=/„%)ÀÊ 05ÀÚMo á×ìL‰’!}ä ë<Æ7 ëÅýft´=´ü[/ }ã!Ü‹×SmdÈç¤Á7§L;c&·¶Äyyµå¼ ÍcÒÇ\ýÃ2QÐpÈWx …6,/`£kí4¿¥p¾sÌ>25pä(µÎ¤m¾ ˆ[ XÜîÌÍgGºŠ£¬'¥îr`çÿTN¡5?šÎ†Ø†ÿ5‡qvéD?uµw‘^Õ À?å6xøÅöÃ&Œœë°”£³k‡»žÎ°‹¬„“5Æj2“9šy¬FÍØtèjá_º¡*ˆ?WIί €Æ”¥÷ûv«®F×é-æ6É'*Ë©n¾J!¨‡Õ0SSDÎ9ÿû]è ¬ÿ[—23ê3….ÊB0†üU׿é8ýX0 ÒûÍ$Â$Jž)UþÛÃç·¹ÞðÅøZâ¼<Çþ©ãâ`šò_U©òž—ÝY~í¬ža/‹à=ºtTèG"’6ˆ¶ŠjN‘G^|€b<)dÅèe²ýywmÎ}r 1„…k޾!q÷é8íÊoð.ì¼$4uÀ}Ø]«4Ú˜`óÉÆ }[ñ &zî @óSŽ;/ ÏüÇ X¿LÎöqàèˆÞ¤ŒÒ"U®'úLHŘ޳(wš™¤i‡åç^^ß6(åŠd{ÇÞçùã¤Ñ CcGÐ<–Ö÷¤ÿ*< ×âl^ay¯¼HTñýöƒŠ…òO'Þ€¶³7ø†\Ãâ¸Ð= Rk™6qÁùµ›·Ò‹É”lôðFuÉ`#ÀË…› $ÉÚÀ; ïj“>„¯Mû‚ZZ©2| ±Å²–°Ó“аÕ8 W™¢¥o·J¥ÂQ}[ŽØzõRñ§àˆGúp«`²béT8Ú Øä¥}àüyP ÇËç·úp®OþÔëÙ7f¢ýîæÀrã¥@8M0³X.¡9`Øm (¥s !‹°¯¼ž”½'_QýÆýì´“£çö#ž‘ö‚¶"¨=Ý £­2UÇ'•þœzkǯ䠊ÀÏzMõ|ØÉ6Š©Qü³A=¹„¨¢ZªX„UW¶!˜ù ä  À{˜Hè¿mâe*!gÃk™|¤¢Å©áÛžèÉ€¾± '01é†)ð:¼•73ZÁðÔSç©o®CƒEŒä]Öà:oûlb³$[a3ôwvehÜÐåo–×}*^aòWK"ä­$äÞ†EÉxOC² É–\‹=„¡—®á?Ø$™<{Q¿@¹ƒeAgg/ÆŸ•ð+€ø 8›ñÈæ3º<Œ…J4 g>í#‡WdGé+°Í)JÌ)s@»PÊq2 Ýß‚â»bÁªÜ©ÄËÏÀqÞÁO|i¤ÿK2ö¬sž¼-åÀjÑ5åWê9ÿe¯°ÕYMÒšòÊî¹ÉR¢">GSg= PÃÛóÍ‚ðÇ,JF±IqŠŸtZT·Œº¶£iì>ÿ’)üú`€#Ѹå_Æø¦W·5ˆVC«%Ê¿ø ‰ãxÆè–ÚG¨ENØ#È3,ß Þ™µQG#µÅzI‚Ä4ŸD"ÖÕµ‹Jô*˜ýQ(?2Õ~ÒõN l Ðü@•4S°A¾‰Ás:èš?ˆÎÉþ%D*p}ìcÐ?ÇÅ)Lòvº ‘ˆÝ(9™Ç¹`ö ã“®Õ¬p ß2Áj!·ÿ÷cø­–Êñ¢–bù m/€—†Ö6¿ËŽ£€4µ¹0cbM3V—¿oU÷IK¸_× cÕ´xü[:<½iqûL!ðÖMË<º”ÅóA¶–Ÿ•³-ð¢ZßÌ2~Šy„¼äú# dìù„2xOí)'š˜•0Uv²¹¢ŸÚS‹¦Žvë‘ÆŸ­Ö•ÜW"F ©ò_ÆEÏÌ"k#vu0L@Þ½¦ûÌåÒjÜ…*1Ý;”S4Pè­|» T¸oä2ÖÅØƒ$R*a¢–›ÅÁ¯Šx[«$qºS?GïÔº“¹•„  Ó˜pÇž­¦J÷ò=3Íž,GäglKGÕÉMT±v^¢ ÊcáûzŽŸ:ÛêÔ‰ó³Áúº¾vªû¾u˜?Vûß:¯Õvß:€ö¤¢WI¥®Šš¥Óˆû·–Ñ¢+}érÞ?ä•/O¿f§[‚¡Çئ¾ìâL—¤ÆŒ‰)±w:Ê9$^³ eÇ ½¶41Ûšó$eÛi¸~Ç‹Ù;>ßÀåô+™¿aU3ï²ï9KâÛ»·¸¥4í¹ [Œ„4¢ø Ù©æ’ù‚Ô ãOõ»õ¢ò¶º.êÊ_néŒÕðú”FÀN̈¨íÎÞWzÅ™öáN#5³8šÐãåOœ•q 0p_WYB,›gÈ"áëe§¨0p Ÿo-d^œK¸¡¨ÌPÝwÝL‰Í¥Q!Û»•m~†PÝÊijzRíÆV¹æÓüƒŒ Z¥[Þ’ÅÌí ¡]å&tÓs2€¢Q'Bàæà#}™÷Ÿ-©TÒÏxDNq‚(–¢^—–E¾ÆlZË HC€üâÓKún®9XZ¶¼k–#/D>U˜ÏÉà·8ªÜ&›µŠß3#š’h´94û°ÀvÑƯïs1ÚÂý ßžõížy~ådN˜>^¬/^Í·x‚ÞŸÿnÿQ÷NLj{9ƒdtfŽ@ÑF|kÄPû¹ƅʙă³«dQŒîk¨ü¼¹?{p¯ÉjAÜþÂÅÀvœÒ À=À!)à pš£¯*/ïÄÀ Ö«z&„†?†Ljg&IêÔúÈõmêâ4SâÛÑ]®4ÿdo`%g‡*±c@"^ 2ù¶+#[‰ÉóìeO®ñŽTµ›¦ŒúÕö…˜9¶5ÛzÖÎÁØfç©cURÊàÜÉäÑ߯kÏ´ŒHXÌ:ÕµÙgq&màVÆC<¹ÈËU æwŠ‘Ëù´D°>é{6¹ãúÑ;Vð G…Ù äÔÕ€Á[:·³íÚK°•#É~áiv³çÌÖâ2í"§Œ8<\]@ƒÈt—Ó¿ ø%Ëøhœûf9¾qÐ Þ6ŠìDŠ{°ïò䧤¸/èdM„€`šš"¡.òÙiJïû èD… °C@OWv§™…‹Æ1ÏheP¨Î¸v(9t°ð˜S×€°býü¨¬ò¶Äzxö«Q ¸ÀÅ­ßüCí2(Vë_ì£K)9²v{eû#¿P{{$ç±Ù·Ö ÌGq¢äç+}HÆðâ™°œ­v»n×ÿ~ˆ`ÑZÁGé™t’1mF9û sÉAž]uŽUÕ(®êÄS[¥ƒ-6–§¢¶MÂ1iñ£zj²Qý:E…©ò5ºUmælé½Âm_ΰ*%DE‰Þ"±uNJÚW”!Ôîžà½Au=jÓð)})ˆ · ê‘<Á2Ôú@@‚¨k‡mhÐDEX$ðØp£Š¨€®ñ£#K{F§ºÖ=´H®ôòïVr ¡Æ]ZéÊ?§—<¦{ ÇžBÙ«”ƒk` 2ÜM!̇a9 Œ cÃÊ8L Wîh†!´’O9—ÅþWÊJœîÐà*”6ÏäN÷Ȝ૵‚ðt¨Ýl¨)H× ;nƒXL9[Í}°Çþ90'Ðô¼²ê]ò=ÊiÚ’§Mþ?¹sj=]©N쀣–÷7|³ç^­q:~ ~½ÖûúRµ´.Íöq¨ýÐ9œY#luÝ2N>˜£%ÀL•!îꔂŸç~€ômpº /z‹t¹E¸<†÷ýD%J(¹ÿPm‡Ö»,—ëªIjæ|l';U¼öµ]ÔM‚5¥)åð¿/y î,õ8lÌVÐ(¾Âƒ®"c™ëb;Ax_¡¶^y‹Àx2LȶOúø{6Œ‚Λ4©’„ï«Ù}/|˽uÖq؃¿{‚ø•ÏS¶ l»¾ÿjMÔI‚wÄ ðòÕwó»Ð«˜š&M«Lò¬kæóLW=å’’åæó$Aä÷:MÏï"3’½ˆ †å%–ÅÖvH uÄž@§Ãðùd[^#ŽnCŠ—}Ó v¸ÚfTÑTˆ¹aæÎ—«ªOð7qHí‡YbéÑÑ?q˜?0¿Ô¿#5Ëe ÜTÔ y½öÄ>æl”´4·€…dW C©âhï@mêìYs;Ô_ÏeLùþ!±;=—S{(jQ9¬àA,hy" KH®!«bH›`¯äº0רÚ÷­ ´»æåìÍÝïµ ÍÍø|K>¡¼l…ÝIá_zÞ£—ògé’WTÁ»¾· L$'írÉëq¾üU˜1·Q:óúغ1Í€ýmC<õtEM´‹íL•wž4ÁCå’ñ+yòð„Î)dŸƒì‘ÊÕ/È¡E#n÷OØoØÊìq­ê ™ MjFÓ0^kêüª ôy£eÙqå_ 1‘j ¢±Q¹- è.•޽+†‹O =¿PA…¢Ó¿é…lƒp©©Á?©?Áxý-K«ia ý\{N ”ÅÌ?5vºµ —%I;AþËývTÁ®è·ÙØg 4%äò`0`ø"Ò¨NÇÓ2äF?7ªC܇e'–Ÿ~^wõÁrpmÊàÄØÓ!¥ÐZŸÉ‰0ÛÜ8<5¨ôüZП ÛÞ’äN€eBIªáÚØ¼ë€@”(>[Cvð=צ³-S7µSǪ*.ÆW¶1£\Å£¹A· ùWø]`æñ#ȕٽrYG)7â>ž\š8f$ލºÀ?ß\„¦æ~ÎÒTOír­%ø‹ìºªBq=Åš„Z&ñý-P„¦Ö‰xùÿ Tù+îÓ© êO,M>¯ƒt(³ãjðê36( ¬‹C„ü—GÏÞGyÕ  ¨"Öзáç²¹qPH!ä5k¥È}›‚DCWì®/NFMwÞNœïâ„–kåJõ¿Îß;Í4BqÔãíšVŒR2û¾ÅŠóãL¾@¡)†Ž½,5ô»?þfGè›'‘Eü±obIõM_*£+3ré ‹9*æ`ª¦sêËŸ_Á€–”åÆŽS‚’ ,eB>ÁUEÅ r;1£`ÆA/Ä,êȯO_èäV†/`÷OZï—òp 2üìšòÓLg Ò™Iåe/ÆK-ŸeÞ Š°D\Ÿ”¨.XeiŽ+ËÅǾ“šSUŠB¥¯àiÇ©8ŒQXSqR÷šÁwªäÞ¼‰y:'kWxà÷sƒk¦Èu…û°‹`/Ü­ì ³]ßÈ·â· :p{@×MOM‹Þã…z@û,…Ò 1Ä:ÊWÏ’5ùÁß)W¶|›g ‹n—饄χÊ?þ9JZÙ1ä¡qœzÀÜŒj]\C‡þa¡6³±?¶,k¸ ìdf¬´g¹°ã®ÔÍ –oðrXh þ³}´”q –ÍMmëuJÔ0óŠBÿu‹þ¾Û# :i—õ³°i—WcY~Ÿ]J¼U/йaC£yµ1ƯGL|mÀ²k¹Éyć:P~ʬÿc»7wg=æpe½åÍû½Qd`‚*Ù†$¼z±6dj ’ø?‡ÕT—WÉâŠY^˜Ë8Öòz] ½NÉáûzPý¶·í»iæ·m³ûjOÚG‡íÜ?ÛL_UGûH€è’D»Eƒò¨¡>ºòå–‚Úw×fG/ØO®ç‚èO@»eƒ#SÑj[1ó”SÇ<IÖûéhn»±ÐYLâëü„;úr=å@«ú`‹ˆk:ü¸@ ¿³åëoWkðÌoK_`:–´Ýšr”¼MïQt:º.h™ÍŸ"ͼhœÌ§_ïøa¦˜*Q·ÜBA° >lnZ¼|1 ð£/Éé0Ãbï:Cý3±5lÝ…#ÖLe[?‘çɤ—7†­Æ'|Ò3¨ï–Rƒ·?=ìð8ÔÆúËC{4ò-â—õDu¹Á¡“ð(mÇU–ä¸WH™eÆ—êúO.Meê3‹’[Í—gšÓΰ㗠晡¨#VéLxçÓãEíèp ,&¸ÃÂÜpPqyŒ¢44uvSÓ,Ÿv§…žÍH"3 ‰šˆ ¥ì•¼4û•²ÉÎOæU龦ÆOŸßßti´‘H§…}+0<¹ß9³•õd*™«Ñd ‘R ¬k Æ¥‘?ÓóÑù»2W„2÷79òï3Ê—³ÜPÅD €N ä–7 ƒ›+ãDÇ+$[Ò¬;!'¥k9ÝÆjfZP¦þ­Ç‡Mf«Þ¬®W°Ð5âT?bÆa`«FI×u×”XXõ‡D•EêôúeÁJ3ØVçï´oV ƒê^";‡ë‰Ý¬ÖɵÔº*‰Ð„*7M3¹­Q­Q ›ÝìŽ`SXœ‹¦ä Z%u`äV†Ùëpˆ—˜[eŠH¥ßÊê×\dk®ÄSÍ:B ¾h…Œ Z²oê¨jãl¢ØBŽ·X%9í«nU§C4‹òè$l›ç7be=£'¾tÖÇ4DÒIy°†t. •=z€çyà ZõDîíƒC’a0€:H5À‚‹&)Ͱ+ìðÍèùýÞÔqºxªL¨àök8Ê¿e¢WµÛÀ¨mH¨M2¼ îPž°{ë-8©´ÆlsSk¿-?÷¦ïà”öF¬GÃK¡² ¯Â¼ŸãÂgÌs§ø7&ìŠÀ ¶€å’”JÇ@üË£w¤MK0NwŒY÷ç³ß÷ì¶‚pÐ| ÑÆ!.þÖQ»°iíî^’®gÝ"‚] fФ•œèÐ’ŸOéöý; “K€Nöñ /òôu”£Ll8Q´¦þÒÏH‹Ÿhïr‚)³‹…RÒ~C"LX*·ôrY¿plUrBxa{Uü‘˜ÙQ¯ú£Éˆ¾¥ÀšpÒ@ñ,TGxNQœáµ#³•O—ºwEv°MXQ*Jãó»°2ý]ÇHމœ’÷ÙÞ_ß‘ôI•ñÁP¡‘hä‰3ÂÀ Úœ¢ ÒK< ž@’¾‰;ê=@m6®ãìà @tBŸ©ãè aV"Õç’ÚK\¾xy%¯hë7ŒVÚ~g?’øãbŸ‘¬éG©¶ciΟU†pÄSÝ$˜€kpp‡ŠLìÞµ0;Ž,_<ä†~Ê«ÓØÿƒË«†¿ }g—3ôâ l1 ¿afE• ý„ïØ]ä¯XTŒ Ž©±ØåÙNÔ|z‡n(¤­ÊŽÙu#k{Z`°‹þ#÷«ñuVëÑJn sÉæêˆPCmÉ%¾ðesˆã[™C¡Å¤¼pŒä_#SY».XcáL^ø¤žŸ>—׳ÁÑâÍ‘8SÃP×Ê } 1™Ü£:Ÿ*½Òí-C· ¶ÂA{4´no2Ô"¤vƒï»a#›Á`áéû"{ZI7 0ö'£ hß5³¹ƒÔ®ÓãF”½ÙB˜ˆ¶Øm¡FGsIºõ•…PWí°áð_Þ;ߤ͕+QãGÌñjÕ;!ô=Îü–'µR²«¡Œ"Ô?!ì<òC^:0ŒÐN/yLÛ7Q0¨ iõ†ú ÒùÛ¿X–͈ȼ"dDE_£y}p9kÍ´ÍßàE»Ü;â3Ò›N¦Û‰ßcB.ÐXØÁzfíŒg¢¬HÚ‰B †ïtÑœÒG_çŸÛ¤N¬rÍhG°“9ã2¹úZ0)‹¢–¿ªüNÒJ‘öÏð›¨ÆÜ/^|Ñš6"fÿ@ž)]ýQWé}»kŸå§¯É•4–±Ï‹¢´ó¾[td>éoI‘œ×†ÈÝ¢ëORL§¦ƒ{ÁxTqè…Gk‹W—P2SšjU^Æ}ãE.Ù–3ÈwU†œ>*;Á(Ÿ¦yÿ0"õ— ³Æ 'ô3­ãøY‘þ#ü5kµþð×áª8þgð[þçü4ÔèŽcJÇöÞñ ²pÅï)^[ñ™˜U¢ƒ¬¯Û蔋 ŒHq¨º·ÔMÁxœÊ\89l@3ûj# °']MŠÕÁñ]!öÇcÀÂÍÞ81‰:û%K„=Ê›5êÚðhÕ0ü¯Ï³øÛÏmN£[@­É9·ú3Ö§€Êuc>s‘=RU„$4•Iu= ?4 y«žNì]όű7jcå3òÛÏnݰÁ¸*ã*‹§ë·ÕÎ…%§+PýeCÈo©ÌÅÜ$ŽÞ:°ÿo½ææ´[4›ï£3#¬6¹ Ü¥ŒB nãÚzCâÙB¨íXæ<#" &µ(¢èã{€]]8¾LטåT‡^«7AÆ båòymMÚe,H—'·±9Qx4­ÓþŒì  =æå"š?ÕlÀZüÊ4âNz>C®G”:3’Œã0U.£fü8rpí«éÜŒ»·ìÖ¨ F‡õ£»\µ^w#¨“.ʤxmaÐ9ø>ÅdâÒsq³±F ‘TXNàµTTM~@…t?;®:=¸;y1i‰—)Æz(]dæBÙMÚO·br)açš½ÖGy±’S ÑÔ(¼wÙû­’ :ì?XA\‰±¡¡ ÷ʉšoí3cÑÜVO ì êÛýû?[‡ž$tLN º²j”Г)ä*fPuÖsÀu23~òO"œƒˆ¸38ÊO„/…h\qO¥¯ƒ/‰q‰¨¡*¹ŽF5¬ÍMÉitûŸšœºêÎö¢¦Ñe¿´¢páÒÍé5º¬s¹É¹n.òržyÇâÖOÆV†Ämœ;ñ”e~“ñèÒÔfÿZŠT†ÙÕ£|ö·IµÃ¶ ¡= CU¥µ›dáX‡,nñ}0ŠÚL>*@âDWÊ]·®~×ì^€ôC¬Iäž¡jüm=ÏÇã[ž s8ÝöÛ÷"È”sþØtB¬<%ø§@1Ëõ,\ß \HÓx_‚õûŠë‰Ùò^G•®û w3åb§4v¤²·‘í'æ*ûÞÆ”Ž×¹ó—Ï+¯à»sЪ¾ ;2\}e×.åEä]›.Ô¸‘ò°Án‹ôÿN4¦'ÌàÖÉÁX’Írxgw‚÷£>Õý‹ëúšYç3Þ# AÐOr¦Rºa¦ °&·úø÷{hº˜ޝÙÕ!Ÿ¢¾SV¨9‘ )$‹ÿ ©Ú]ËBF0+†~·¤Û<+ǺáΑHf « çÉ>ôÒUË€Þ~Iª582õ——L1TÛòF8ÕµÈ „T5t×b_ û)L+!5‰¹w榣yÏo éœ Ý8ø\—ôëÿAÛkc „³g*Ù˜(ª|­bãÀ&ª‘én­è³Þ¢ÈÉ -Ç ‚ ‘SôãkŒTw*_JäªBVB³ùÆ ^±ûž§`qeëùÊuΦ,“›ÃF$l'jßf)0»†Ü³Rô‹[2æÔbªyÿ„ÙG¥ÝcÝQHµÈ,ÖÂl&(%§»EKK8 M³É2p‰­FƒgµLF&Û`YFü 1ŒÂÉÕžqLÙš-ª^úŒ£÷ï;×ÍFÀETU<ãláô»QI‚+F@Ù¼©WW~r‹·Œ \^I=>®Éb €¢Alº °Yʻӂ÷òîƉ÷ÔPÇùóªÄ (˵Q±çTžd9Øq-ü¹A‘«]Ÿ,™5§px‰ÜOIÕ‡â7sÝû07‹ì¼.D±¶GTÉ $C$(׫|ç¶ ’Š´ò2ém>³SÄ i¸²âaJn’¸ø¡k‰ ¿‹Œæ “³Ý\ÿ"ü‰Ð Uô™áI̽ƒb i- ·nda Ó'XÁ~öH|æí‰g¾æÏ›:t’wÙÔªØe8{†_$ÜgÎÄ7Zl[Ð:Õ Ç,Ôµ'µÌí‚Б–õž»ûü÷s˜šƒhzãø~S×xjÁ,%ýÕJ±·c°(Û‡ÒCR‚„ÐÙ¦[od3My:}!Ìõ‹‡|ª‘AŽ‹¦I±‰t#ÿu(.aFo£âá_ü„f«Æ2ä(,†$Õë Q ÷€¨¾’[·Aøºu$³VôJ¤¹ŽË ì-‰’ªI‚ÕÈ( B)ï%„6®ê¹ôІh%6ZR—ÝBSS?kÄlÒ]¶U2 Òs£î|ÂÛÚŸÂùŸùÞ ã‘kú2±)½gróìÓcƱú“sœ³ «’µÓ¯ä!–[?¨P‡!©­(Ã&Ld´·Tƨ'+ƒPTV;•»Æä³,*ÎclÈS¨ Áë%ý¯ÛC¾UC?ÍLàBÖq¯°¥2Ð.Ý3, üf'2ú=åPäl‘Q?“+ÇÏÎD!)BØVuxAãÉ!I%e‚›Çc3×1¸œ´9X„©×Ò&ï¿êdÛìœß7À 1Y òsy—õÿOû¡Ô<ƒíVŠ2c¼Š0¸ ÿÜDéŽhF½Ò(~e0tÁúÛËÿD⺖ÍlžÆÄfÓJs,…ü¬Éâ3¡âªÌuTâMÑâSWä ϯ®ö⽌€þùEmîþ}¨ã L¥zs…”G¹°ç©S„žFK4X×wx=éþ°ÈÆe˜êöï§*Žœyí<®~šÒ>úÖ†ÙQËùQW·€DWD~ƒ`¼ô=»5³QMÈXª‡¬ânðÍå:®Åo{ùäŸG¡xY€‹Å“µr=fõhöc>.:w&UŽ™ ¶í‰>|znÆ{ò1l4‰eë›É  ¸“˜âMóS¯U@NUx(96]@„³‘Éè^»ÐVß1èI4•õàÅÖ¥áJ.¸¦ñ™`÷F‰dy¼´"#Û›úÿd´žnïR#Û}Æ[äòè‹Uƒ°ü=ïøËV j<ç5"7äAÛღ¸!Ÿ¶º¤ÃaS±<“eYôvº÷»3©À@)0¯€¶‘§Â ¶öÆ)§œ‘§%ú0]™êŸ»-ýï?QmÃÛ¿fè»Ðµ³Óv´±±ûy‚ÛôÅe}2ãòùU/’Xè Õÿ KNÑV§14HÈû¼sB ÷d\Žúi#Œ4=Ÿ™øJ9DHõ¸;~ÈþhÔ•mUêÑå Œþ6¶P¼ ‹†aÚyMœÕÛ€ÉÝë=d‘°!¬·ŒÏ\Ç¢f »€¤™ØÞ[é÷ O%/y¡P î§ý–Œ×Ð]àÙüŸ1ðÔŽëbéeÁÉJÐÉ´ˆÅ\Ÿ+2u÷w¬— 1^ï]ñ[Åûèu‘þ.¹S]¬ I0~N#*i^MÇ+Ðdê/mÏã†j ÐØÆQÚ4OYIøHô[,»GAb%üA„öX‡4=õ[+O6r‰›…#@x=Çk|M¨ÚÐ$ÁZãè>Fmö¸?à¡}`ßËÌE¨ëYzüF±ñ)D›ñÒL£K¦“$ œÐÝ ‡6ì_©+øøK•÷-$Kß~—CO.43ÒXÎîѼ:шЖ½f¿ËîÚ;£wHKé#OS© PÈnˆœ Š+Ö‰mõ=5G}UËp 5]†¤"³DV!G‰Òð*¾*‡ÁD¬&œñ¿þqäÝæoŽÕ8¦ªŒ#TÎKB ¡+涪_ø"Ëÿÿÿ~õD¹àˆ@çªXÿlY0/0r!¡S™@¡ôU+³V&©ó¿˜> ¬90jÁ„. ÃQC)õ©ICëcüÆG”jŸ˜ªÐÓ´ö“L=ÜV‡÷$FXûð'B“·_‹ C Õ|;.чž›B÷ÿë¾ñ3YÕtDUŒùÝÃ’àýÚ仌›Í·kî{‘ýz 7SedB“7¸ãç•qÎk¤t0ñA•Ggåû a & uNIÀë"Á#°Œ}º çx’+A˜k§¼0÷ÑŸ¡Œ›D5/%—Ú-lP Œ ›–aËùç¹êÝÔvw‚^ }e;;ô–›ÙÈè¥ÞÝø¶™ ¿7 ‘£$mÓù¢õIG'Ñ]{`Ô`= jÁPtŸ#Z_Bˆýí(ÑÌÊÖNãþØÖÛœŽÙHiï…T¢ÞÈ—‰WþXàð×ð~¯qçÔ/•úäí6€ã™m.!—`)5‚†h³+§~j³YK:Ú_`M%aU·Ô/8UUÐP{ƒÔH„ÿ6Ôÿ|ÁLX*°#}IÃòò—ÙÕÑìóDzPÖyİ_ùÜOö̲!lñš¢út¼”ýèèÅ*²< ‡ oJ3«Û‰!—XÒ\Wå?)+Š7Â1†BN¼}|d~¶}:8£¨?1¼´,æÑVéß–L~Q}ÎZÊTÞ¯‘ó?"]È T,ñãPqgÝuÞÉÝãÇ÷#1[A "rz3¶tš"³›¾}1ΞI`c 9ÐDÞÞf¶okžºL½áç ×Cp?™2h²áEoñ­’eq¾IxZ¹ù@dŽPõUFm!K¯ãfÐãîrŠ‘qKst·<¿W«@\¾jBñŒç¸ÆŽ“œ d ñ§3ØÞ@oG؆HeY,i^äýéâD&)†EJ_jãUG¬Ž7þÚ "]¨µâ¤r§º÷‚íÀ8xÎÄ‚áx-ýi >£Tc|ÒwØÕr°Ù‰•Zˆ>ˆgôp¡’Ñå²|i'ê¸ò—>:±9ÚF1u_jìÒðüé:ËcË.<„ Іƻg£ÆÓEÛ\!Åœ#$E#ïàk?ü°Y™ í˜äà¾],{ÖxLÕxцå{D¢ÅÏw E†’À#IÚÄ-ÁùWÄU|b¡=dˆFð¨Ä»uÑÍwIø«¢RD[#›Iêaše‰q1(ÃFx„AíÿÔ]ž ‡0i2‘šŠàÚÑÿc ú5²jqMðŽhL‘¬5»£-=vµh9o '‘|[3¦¿?¥¶4ÅTŠe ~¥—ÞõZ¼˜E™Dµõ›%»Œ\ãZL}o‘hÆóȽIpK Jö‘ŸÕþ %«,ú{Óö«1ïöH‘¡y|ñ:GÒNyëìÓ׉̨q=:¯.¹}¡%þðî AÒ=ëØšraWr¾‰ŽøD’Â\–ªKœ¿óc¼ÐÜvjJ~[¸æ„¿·P¶Ž˜K‘†Ä¢Csˆ5Éb9£¬Î²Ø)¬Žµc}mIžZ)†ápfçT6™ûÁõÔ’×Âdª½Úø1ìz (82e-5 F}Õ4ŸÃ6Gtpol¥V‹7pyÉ-»€÷íyþ‰y_ëJ`¿r$à±µµmÇGÛÌìøÚã_dȿ܅­(æèoÀÔ§²X]ÒMçÕæw/È'°åùãÄrâ_Ã,Þg)%ŠÖEÜÆS­\Þ•‘i.nvœG´B$Õr˜þâjûmô2EƒFnQs´áFeN ðkG/§(_­gý {,ñEHúÜSs"nR­`†û«,:?ä ½'­ä&õõ‹òý÷=¡åÓóNË™üÆj4HØ9A§‚Ýlÿ_k’_ÿ}øÃ·Ì_ê+$„äU]A`Õ¶£š÷ÅJãᦛ°L<š|.ºÙ9œÀwQóFÂ’Fö¥ãÈlnPS,‘ÖŠ–ã÷G6|RâÆ aÙQ- ‚Ecºa² nÑ ëp&pP’°¢m™Yì.Sì÷]·s»*ñN#å›ùÖ`º)~©×…ç sá²ø\ÛhÑr\Û|ƒ´Ë‡îÅ–h ‰›Ç—ôCU H|DÀy‡Oö›É÷•4C|F~ì¥Ùù¨’+ Ûý¼2¥L‹À\D P†ˆûù ¬ÄRÓ؃ĸ”¾‘gô›ÃðÁ?9ë ¯ÂJÓ·C¤Š8ì£&õ9€+™ãί¤$€£;Õy¯âKC‡,õ}ß gþ_ì‰2p§Èq¹ ëL•ÆxXŽ®wÄ·çoyþä3Lh3$U0ÛvVa«íœ9sS¦Ô9já Ã’K nŸ“Ïe~ó>³0tuHÑÍ.ËB‡0îª"ÕZ`OZ^¹S¦I]އoUê«9ˆªwÒZ=1]E ™éíëž’9ù¸*8ÆxéÏŠ 3VXÆ_PÁROQ%®}˜®¸prd ¦.Ì׫÷S®JÓÈrR½©ñ$Až=·>L¸tü¡0ƒ¿Õ/!g?hab sĪ ~˜=NH± TÊO.–s³<ÈS[–î^2e%pvgŽÅß–Ì«Ãì1žïŽNÿpj?ÿýÂwÂí~mèû“ŒOb?Néíw áÿü¿¸÷M«/Rcþ]ïá/‰1€â_ÿûÂdJá'¿•ÖŒðùÇÇ ì*¦áC }““i6!ÓAô üß$J…ÈÒë#ù¸é‚ÁÎÖŠã†`»rÜ(Vÿñ.Ç#A§ árPZæëHFÍyž}_¡úüç‚C2 ƒÿ|«CËBK§Æt_DÝî›Î.`ûù÷ŠíäåÓ˜˜¸˜aªQ‰ßò?…˜ OÙ³ã2GùbæŠ. àfŒ”8põû('Âèäóéµ>’uSÜñæd¢“qQ©ö6˜JòM¯±ûæfö£ÂË4q²µÌzŸ8þþÂÇAÃZ¸+HD²zUmïÊ*W%$C­ÇjíQù¹*UñÀÏmǧ¨£$TÚ ²“?â;²Ïô³N’ ÃAÂq‘‰~sÈ)޵i Æ6ãŠTWÅs~‹ðµ…’:’Òvr†5ÊõZãúVcXÔà^ÙÿAc¶¿`÷¬ žsâ; “»«<å¯!À߸òÞV±R”ô£u. aÏÐ\½&| ©—X¢C‰ï¹ó`VN¿·TþBnØJÝ•.+CPµN@| @/2ËGSÐ4~©‰WÊ UZ æôJÏq‚…žn\„9–×ÄË )ûМ¯KWù ,¨|šstD¯+h´Éù³$ 4¶†©ã]w`_*»&ᆑzæU’~3’%R?@RêáݼÚRn N˜Í&Ýúçdl¡zFïÜ4$þ2 #¦@]¿P<Ï/¬r¨—Èäî F‡ñå~$ã¯9}~ƒI•Ø`E3W_ÍéY¡ÊñÑ»Ï"7W_0Ьd¸ö•µ½I 0¥d&Sp6F—þoê> ɧóåTTfTd±»Rz èhr+è@p:~€ã7pAl]VâZÅ Œ"£òÑL‚ØÊ]‚-édÂz¿+}%Éó@|Tè¢ÆD”è#ø%‰ ŽI®¯‚йó”ê³a³¹Cy$Éy+j’Q8HCJÿ©“öÐ“Ô–ß ¡¤áÒ²§,ñU¿cÀ×» ­ü‡&v"=LŠŒl¼ÄÇc¹.ƦR‹°šWŸÚuhc¦$–km4Í’ºœdDLæa㫈 ª©ke××H¸HiµÉ¥²’W…†ÉÎÙ®k0L) ¥.ðŽ;`V„+œb—¨nRò‡Z øí%¯œþLëöÕ¯Øÿ.[zã2L˜=`»ÛTÍÑ`ղќ/§ \¾Zf¾0úÞ\jø;ÑgKrAìUS“6j ŸWMh8/“ÀϰE~ÜlåÓ‡ `¶:_åÕçPO=4¹©Ù­L­±$óõ¢Íy5HŠ qDIíFå#Pžt;ŽßòæŒÓÁ n½CÍ‘ŠKÎø€5z,¨'òç@>ª±ö ¹‚`²¤Uú‡qâ'™~-9 –i;ãûÅKû“rýi¤oÞÍØí 3v/,/^KwÎYYâ ™}€QÓê€ÈH³c'J€²ÿûniL™“1è°[m1ùýAÏ!o휖ݗäÛЩoÇŠäHYH èôg(cÈùO¢nœ ÞÜWv^š€›üôÔ÷¨­?$%ôó,/^'„Ðó&*ñªÑ2“ª”|XïÛHNþÕƒ,Æ(1¢[DE`øPpW:Øð—’B·eQ„Â^»Žk?q9\|™õ2 :Dü¬îÒ)A)q=>¹¬çHWIO¼¡·ÿE‚?ü†õL•[p[tà¬3*œKf¶¬°NÆð‹S.BkyHwÇi°2•É’xFÍį噶Cr|¼>²èrþ#FÁ³2¼ÎÞBFÅ”ÓëÛºdOõÍ®Oj›Š—+T7=Ê>cù¹›4tf‚Éù£wíÊ«ÓY&Î,•9ðÏX~Šêóþ­…øu»Ù à¥Ï»ø‰»aø¼ÝXr]Bãuy٠¼$ñ’#„ÍTÑaAÙ.ÒG$ù©©•䆉v{tj0ÒÄNžŸÉP¥¼”â(äp+€ñÊ`þƬνƒmþÞ×ñB¼TÖ6mej‘Aj}• 6˜'-4±Úšªo¬hmOSa_¾ñ|gLjws¾3?ù7Œ_`&:=ÇÙ€)½¡YzuêÂÉðFfú¶Ë¹Û{B­áÀ7DëAƒ’dÞ­ÕL7%…³ÙÊÓÐÙ¹›R ’ðØA˜k&¥êgê_hžž]Û<Å(•ˆ6a€C“êöVÊc…ýÑÎyp0¶L÷޼+jþa{DÙ7n RèÏ×83|d•KlóÊ´${„£5κ%Ñœ‘%‡úE¡ñGc?ÃúWðØÖÒb:(ÀÊù6p*ýà„m[¿¿ÔÂðnã‚€òÒY7]?ã2ÚRS cùæä§èLWgÞoÓÆDÛ`„á ¹Œ¹)õŒ:ERÁP=dOrÔ+P`É@€½à* á&Y0`é4çƒáA"6Seà‰`<õT¥þKžî€. 0c&æQå²õžÏ'¨[k¿vŠÍi•¿s ©y,9,¬œkª²æ_Z2‰,ŒÑ(­õøÛçœrÚh_ ¹þfR4Zž³xÓ1 ·zá,ªÏ`1ÒOü¸ÈîH¢÷_4ôÖ5Æ’Šïº†«âQž|ye?¬žùÞ ò½ésß÷½†qJ¶(*À{Äçܨ[S•ËD¸R¸¾qÂPé0xú#×›-ÝÆ¿Zã‚õý‡§ÿÿ}v‚÷õÍGæc@ºr•YÏ?=zº†ÏÊ“¯ÃéXÈY„ß™]:í5nUšY€ÆGh½`±(!çyO¨Už¹-VlÄ3 Ð³ yc²¬þÑÓ*–ôRTPÔáBöNð¸îÎDrBÚ\ õ9~3•›û}™+w· š¥ä‹àa¢vîH@àâ8I‚5§–¶ñ(Ò3¾"½\?€¹VÌ!6me\gŠL óyzƒ1]5sY"Þ·Û .¡Ù% ©@v¨u³s-ÞJŽaj‡*°_™°ÇAÄAp9ù¶Òü! î{Û¾KÊÝ 3÷¼ë{¦Â5’¬Dëá<¦›5 ò/•ÖåN]o,zïKõ¦ã¹|”ï2Fó‰J¶xðÐO­¢\·Œo–ïYïTZÖaR—kØE½*lUAõ«ÄÚ ¡êÄ‘Þ^^+OIéØw•lülEêD‘3$TG…Õ³ŸÅ`+ÕZ÷ÿ<.8„Кì Ú÷’eu¥´æ‘9üuI œ9¸€8r0ƒÂV…Ú‘}ré%=²~…%TF~^…=|ÆNå+‰Ž‚“Ψì9›7Ü o©M¸ÁP`©ŒúïfÂ5ò_8‹`•ÝeuKeô·¸y‹ÎºÞ-ŒMÜHa§ )Ûƒ6«ÐЂx7IÊ«¯êPÒ™7סhÅ8„›"mZ ,qbôîÎ"¨rô@þ‰êCxA$ÎÂ'¡sŸE„ôo¨FØXÑá×µ²Cm›EnA-¥|=Ø?+6'p5W½oŠñÎ_®™ üÊw7ª6U;±Ážjo¿° „)ØùF‚¸!ŸR ¡Æ.ByœžºeØá˜àÏæµ1”}‚Hó¦“X„:Ñ O½B©¤$ÝE’]…pû€­(iÉâ_µ ¤%$qcÇÀhöMÿÀR¬ÁÍ u©º¤Ä””êïI)Â:êëÖ²õ¸žhмÐÿ~ú„ü³YöÍS¢#xr<Ø2è~FÐjC+êwµBu~[`“Ç÷ÉTߌ È²r ƒàÐïn¡€¯›YÒdi}A¥‡!Ï‚¶xápô_v[„)?þ¢¾ÊÿAü0²éÝÈ<ôH d7IiîvC42çø¾7™?Up̧:S;†dø‚ïL‹ý¯˜‹¦…/ç‘)üÑ÷ùíäÕäGt:‚/«a˜~5hç²oP¨«‹Q‘d¶{±—O zyT’:Ì"×&Q¥Í¨¼’eä5ü¢Å¼…Ä{Õ¼þ±Ë(˜›Ò™®èý¶¶Ð¸´é®”¾(ǹF"Ή>Ö’7žïtÏÊzÛè 6s‡’×Lþã,§…¶§k•}À ¾Æ†,©Tf™Dç¥ÕlRÊZؘ³qÄÿVÞã°¢Ô¹œƒˆXq]¯`{$„WÎtmæ¡¡ %Bß æS2ŽÐ\ê”Ó6/Ÿæ÷s?HK´Õx¯0ûrù‚iÉ9-iÌÔfBhk€Äzèù.­&!/^N fuõ§lݬ¯ºR’ð ³à]JNÙ# ùfT]ú¥’5˜ *½Ãøç¿®³ã1PÏ@ÖíŸØ=vraBïš\ã„lÈëåµ4•œŒÊ1HKé¤m"”ŸÝêÓʘ'ÄcjN IPú®4ì8„º¥wwú&#Ç1ÈõƒØ‚L*>î+ ýK2RZa­—Bz* M÷BYÍmt#b–$ëAyÐÜ7ot"†îÆr×õ‚裀í2kxÝ q á=Ž h?7¢–2OÌ.±N«×Û•·y÷No ÖCW?-GVÁO@È”¬Å9¤(1&Xßí}ñ2œ.AÀ?Ṝª»·ØîtR—6 MõAŸˆ‹3q†M@÷ïú¿gJv¬½äÛ‡ ÀëÉ××R‹ªÌxÙì‚ FÝ2qÄæ4L°~ÜYC[*Ë0Ã~k¬ mñ'Üh¥7wLk)Yˆ%—´AÛ8õH¨ØbͮˮڡT!Ãe°™`{ôvvÜáªr7»èÚ`.%œÍSY÷ü Ë#” ² Õ0Òq«æQwvÉ3—¤ ‹g„*n5:f']ÒÒ­äÄB“^ÓÏÐXóTQ›Ì’6Ï¿² _]>0=ËÂñJ2oËèÔéÅr›GÝŠð$(B‚TçËuì gzÝrh´ÍCÜzOe› Õ£'`ˆJa¸ŒW§¢¡Ò1»Ï9Ùn[‰Ýäñ79öÉ$˜YÉ×kSXÝçÎ=oïlÃŒ<4ÉÛXØ(ëD"kÈ_àϱE¯ÇT‡U;(ƒ! nôP€“áNG¨umV}€è9¥h{´Ñx6NëV$öR#–všÆ–nI°Ô’ж-¥´Ï&ï°P§æò9”Sˆ…€ÛH-$eV(–Ÿ“ki+;€óÑÔÛ¦äPd+8„àÓçK iñ PîãO/‰×ÿ†ÐO7zÃÝRÃ3®µ«Ýa,ºð£ ¬[þ0;Õ!íhV-07æi×É{DyÊ»~Ÿÿiàä|7ü™˜ƒðÖibzRøMYùðÜ™°šLõŽ‘T†LJÒ¥˜å!ekÝÛ0ëèo>™ê’ñÒzeUt@ö”f‰ÂŸ÷¨è5àä©í5án2¿ÄÛ`’×Ôx –)ow4Ù=KÊŸP0óJ/¢•2¡ )¹@Èç*?T>ÿ‹fÃ3É3¤Íþ­e`¢9çUî‚ç´=c!ØÀP‡²t1óŒóüËYÅHUµ\‹¬ ¿–ºF_ÜYﯼ¦‹Žÿq¬fè‚÷ËVS þ“ÛÆÆ`u¢g©1â a™z÷œiT:•ÒÊ£í&¨Õð©ÑìÁ _'ö<›¶þûœñ!0,h_…¿Ôž-g«onœ¡„â ôì°1n}·ò W•8øè ¾H¥ëƒ=åéïíár).ñ:\FLÈní•v´Ì=ë1´ªÅȖ᎗[x‘6J0W²û7³ÆO¸ÈS_ 1È/«¬Iï6õ¸lšŽ:SL¡…ç¢Ìc°éVw9ŒW8éPo­ƒ“šÞBÞ]¿ÒŽ%IwÆÓJ+¤›Ûôtª÷ê‹C¬êC¶×µí0½âùáúmǘºD‡}]4.ï`{~ïßêÀ׋bGv³>±J‚Dõàñ fQçR-i©B*i“º$8ÑØâ®ÐÉKqºMŰÓÜ­_:àº^Ó õ¨ÐL­Tè)#ôøÙR|®¾‘¨Ò%¤j9ô!„5פGÒš’ws úUæ0$LÀ.œf¤=¦ç=% ÇœZݳâÔpÊ=Å£o®\îÅ>©9¯ÏºòÝ[T›G%ïyG”ì…Æ“c kG0¯Ñ8l?G0»Ç£ÿn7Ž5ë‘Öuå€× šV·-“ùŒXÄHðö³pv1ŤO“vBå«*ÕäÍ$~$µ ÞX¶Eä]ÏÃ$û·ó `mŠ“ÛÑž¼ ÚÜA_âÔ ˆPª¤)!%*Ç݉~¿ŒHTÙ@ýyn»½EËJ£¨ø!v*_©Mèt‹Ö<: ñ?¼£ÌÓnðŒY<“¶‰¯øÿ;¨À¬ÜÒ³ÞðܼÔêÁ*ÖrÞ›’©œWó P’Ž5s×ÞKêWFÑíå´ÿaNF 4´»p; GhȵõÙi]v"Ó !~ÝùKóùTHÄ|d3«Mo×Á®lH¤—N7¿Êµ¨ %í§¤¦ªë'ÞFÓQO9œIþÑ2zÃu “Qò­Ê…Òò8oò¥ßÈ:“à fju…ë·éïp\þ,ÜNNg}ö;¤¸\ž æVù O.ZV&rý"‡‚Ñ“4Kw³Ú°%ì±±¾Ø¾Í·u¯õ‡èi•©æÔM+‘fÞ©¤ûM¿çù«¡YœZ2IÀÕ-_KÉg·gÍÙB¿—ÄŸ{¢9h™>®8ô|é©èLÒ­°0:Tx<?ñ|º¥ ý-«n™F|A„ ŽÝø3øY—SXµ1K­ÿJGº”Õ¦Ô 8ÉÃËp22¬±Ä·¨IC3ÿ\Ý›aÆb,‚žfJ˜¶=®Í‚£i”ôësì?ÎÊÜ÷ñåÚEÀFŸ(óy‰UÔÂjÁŸ ¯ðÁÜïŽÉ«ðNü®„P“~³sÄv“×2L| i§îïùÊÜ*¶~n™Ëæ£BØ2¤¶™ ô–BÄvþœúZ¨ 1øãh‚iƒ‡UµÐ_ñ ž|sR ÔÖ¿"÷S9áÅ.úãæ ž#áôñ<ü`è8 ¨A‘2n”~uiÚ+&íDå@[Äö®z;ø-èë°ñÒÄ´ê­Ë ̓Ÿn-3íMæf_[‹ÃÜþ¦À~@Š#^T3sóF¬‘ùÕì§,WœŒuíþ© ™÷T;Á ṉ‰¦ÂH³µizò½Â+!#2Á7^&j!ÈŒ”Ú‰íK$ù qCZé}#×’0’pñ'3Ô&X9Æ:4¬·¬eLc°Dmƒ÷Ï¡^ ߾܀ӰpÄå¤ìŸü8„6Ïßõš&WÈ™W;«@mª _…f˱‡3›Ž®²n¡ç2 Àhb׈j¹„Xº[Z¥}±?»;‰ýâ.(¢bÿCnÙ®ÚÊMK¬û€”ø¤ ’|0ÌÍÝ 0A™¸qà‘§“Š“_‚ü•E F”Õ±/fOi¿5d«Ž«ÛJeö¨z¯Ò°p9„­ë–Ãb!‰ò@B$#™¦ úo¨˜Õ×<¯ë—+À•J9XZ“XhÍnÈûu¬÷9œo8~e dN6û,mBS /g³ÏÛá%ÿd%Kò¶u¾.B»Gar h*W<Ȫ“Meðóvál– G‡—b–gOÉñD®js½# ¹}Kò4©C=‘®(IºK\—ÓúÂ3IËÂT„Kü·Q=³Ëв=K7lê6ÜLE½†£¤ýà¹h«ËT¯d¤qù±~­ú¤,˜1¶jÿ,úû¹¤®ãÌ<Ø¿5q|‰ù÷GR«H>Èçô=›W´u¢ßSH"-7ÇÑ-²´®í~Hë/ô²¹ *ü¬ã–ˆÏ•qla™ÕP v¼0I/xE]"_"¶a8:»á|”Á¯#Ðÿ:xhuÙ¡jýçÌü^bå…Kòª/µï¯EͰÏÞ"¡O ÿO©Œ™é,•ÔÄø­ÌkBÓžŒ·ò«Î¦`©A7ÎZD; ®»ú@ÔÏ5*$,¦Í~ÙV£|C‰$r¤u»â\HˆVªʼhýûü6ŽÂÞÕà hœ»º2uœyú]zýàØìÎ[©³eAñʧ]t¼irÎ(¤ŸSº‚f䈡)t袇NÇ#QEÉÕ s'$íu¾¿é§ëO˜ä‚oJ‡×<Ãqµ’Á˜ÙØè^ú¶®°¯JHTÓx¶Ç ý®R¹æ ©QŒÊè©16z—ýb¯z¹ôd]bðßòë JñÊ‹td–óû¯\q6…pý¥,Àh ¨Ň@ÿxœwdÜÃï]ˆdŒ|y£VÌl‡û°O]ê "¹¤¹Ä¥Ô‚Ϊs§×ÄôK‰Î,Ç4‚¨íK4è<”ëê¨øí½ÂÀ¿æðþÅ!)R^ /ý"ÇŽöéÿäCOžniv[içÁÒBí££fí1FÌ·°kü´×Ïk:ŠG”õ<%·Òë¥{Çñpü7ýÞŠÊ@mR¶Ywkùâ•|W|GD[ © ª•¶V#ËÞvxû€­ßŸÝ!}NF:žZŒûJÓŽâÓ@°ÃÓÓ<ÁÀu1ü‹«â´ØÞÇ'×pç‹¡ÁcF- 3«¿Y4´‡ ?ßbÓ|ÿ‚ܮҮ£UóY)škh…ÁÄ@ñ!ô2*¿#ñÕ0½ëšRL–àaÁ¬kÒ[pSíñ¿972—¨Z¦ Rµ°êbEmi[ŽûOŸø%ëþY½Zþ•“úYú\;ˆlÄ2VÞOùö"½˜ X­;Z èkwàzF9ƒÉ°ÛXd9"X…ëfݵ1d»û³Þ þ¿â7—°Ü$fvH¯Údi= %Äàè<ÉpÎNb…’‘ãâË7á^,Ìù&±+m0ùú§d[±~”¶ò„8.Û †`N!nwö7Ûv1.-2äÚ[±²5IÙ€õl7¯ÖaÁ {e¥&|4Eñ?À€Ž„¡QFtìv3Émh¶¶!“°ãT],• c'QÀ“}±­ÓpTîXȘªÎ$^+!V@WìxÔ[Z­\Páûå˜×âMwó®Ó—YBsš­¾@*W™Ä)r|dqôÙý@=ùió¢ƒ/D^ŽÌÄ]±º–¦±çj½EðÍÞ%“5ÿ 2Â~A¾ ´EýCcé†Úäø$¸Ò%$¨ÕÐjàTBq NÄeˆPõ©¨-;XÙ”’žƒfo;±94bÖ8….wÍs®É\LWéDàΰŽÝJ—kúõ¾0÷û×õ¿„¬0ÀXB`Þ1 ì<‹¹|SÈëIY$v, é¯s² ²>ºr4rP!_U°ß £è9G2Fz¶ÂlDªÒü|Fl?2f'À?iOªÂ·JMûx€žÖŸj¶zÛzŒæ wÖ—[³Ã:¥>$ÕS_º¨\SIŒZ›£‡(>Þ+LÃÐ’%¾Àß\-‘ƒ2,˜Œ;°"o,ÓÍnç‚‹0Ó&ÊÓpræû~"Aû øâ„dB+9Àœ„Oæ ɽÿ^ö ÇIålEuÀhxï€éÅ' zò…ûõ;¯¯YÐo—!?ó}¦6dÂPÍÙz.sí3°·@tëtJ4ú¦~8w_ûôM’Ìñ Óé²!•+9g@±•P {Â=~…TiSø1îN×壃êû¹xŠEu°¬ÊûhN•zq]/l‹Lo?Fh1y…¯¢q¶~ÚÆ#æ3¿§§èƽ'‚|ÿ*4¹ÀP»»Ç!¦kìoÏl°=Âó_åŠ48C†¢P<(ÝIJïá¹c¿†ÿE¾µxƒ8Öå™g%/Naë²¶ÍK=Må· %"h™g ÝÞÈßÏfÄFn±HôeçìࢮüøË<ꥒQZnj¿†@I'oÈ›[Jp!â׃ĬA{:ü¦se¡…¤ŽSXf­OðW9áëf¾û ½"ö­¬ºHÉ’¨‡iè»@%FöÓ¦Pƒ’Îÿv™0£˜ZJ5Mz™ÿQæý˜l€ek»H¥#C&Þs&ÒîQ(®KÈòÓ!]uöŽÉçÁ=hœF9´Îˆ+q‰V}ª"ªî+GÚу¿eÎéÃ)OÒl(fÈèŸg»a¯)•öx ±¥ÅNËF)–ë½®'"v4/'Éd‡KÿD=¦ „ãN¨º,´h#*&ÊœÞt¦jù}\ú¶Ä{ënH-ªNÐS-«×r6 «è†%®¹(i{¼ìUK5LÁ9rìJ*Ö›'U¢jGóa·Î/‘3ºä¶úÎ6v3å¿éËþs0DW ’Äx….@–ª(Û^‹˜(€€ùòn&<ð!=žòóÅ*E'ç¶XÓ¶Ô.‡|¯öUÐÄå×%s% ™ª¬‚ë|XºÁÀrF$k5fœc ÚNä£Ã¼ÙžÓßþÙU¢³#”;“yVÐkkÏÖ‹/}«6—Ì™H( Ë™uÇØ¨í*é€{ÊQva¬:©D®*±ÇeHfKŇj³7ìû¹àQIýÚ3?ü´° ¿Ç£PV™/”A©¥~u2óea.w~tˆtƒ»8N:S {.ÀJß´¹Ó‚icÌ™‹ºT=›¡†Ý$g 8,Q;ך‘©« ÉGáÚž—• »veK“5ˆlbV1kå力»ÚçÝGÍÞß{¯™‘,·±ØÙ´ b©šâ®£ r>ü\éO÷ëÿ 3>hiúê2ÿÿCüYvÜÛ€r‘_µç:Çš©}Q¹ñIÇ …Âóܼ÷VV˜—›ºýS'%͈{Ñ v|1Ö*¨¾¿,¸ÀüýRãÎQFaèdôŠ ìì:¼1¦épB£¥‹„/Óžj•wŸÔ?åºÚª€Yÿw¶bÔêžäe0gEY»Ma.Áׇ•¤%Éjväùi/¥8‡åwƒ¹Ñž% =Yoøì•tºÙ}3ÂFW¿÷£`»ß­’øWO.Ç–‘ÌwíWH9}}µiKvˆ§Ø6,®8•U>ž:GÀ†Ç4©tÂA/Á¦ ákï‰äÔû­9¡õdômOEbmLÜ4!åòü»ÿþÅ)E.ggÚ¾–;šPâ)…W&ÝÊ@q2ÌqX‚é‹8¦$‹ ®Ü-f®Ó‚áÏ45[½¨í…•p/ÌO Ü]$@»Ðý(Ö†‹²“læ‹¡1UÚ»ž¸©d¡è¼›GýÀltF}Ï'·´Nt¿kqó~MàÌø ¸ ‘ý‚^°X®vºèD4‚¡Ï–+ðvk¼µkß¶ŒiGžʪÈa·6v+%.ÞÄ2õ[*¶Þm…ćgÑWr*š–Í`PËB'ÖâWª8gHMzŸÃòCMŸöhKa²“¡õéÇdQ-C‰lRn,›Q­´µñÑý’9`ò3HkÜyôÿWºc¬4\˧E'÷/ñN54.ÅûïRaK½ªA#£‚XƒYŸ®“TB}muÔ´L+€àqÇUÖI|¿Í‹/f/p1¼7º”Lf®x‹œhë1u“ËY˜è¦‡8)/¦©ý;žbWŸv[ãDf›<¬Íݼ`LI=JuæšÌ}y _Ò -Û“r«ÍZê]µâ|"~ò«¾þ3WŒBì…% ÁbKË(ÚH˜ŠµëúZövà˜ ì1Z^¢å‘.¤Ew8#«ÊŸ€žËßñ‰JoÛ–ª—¾ŠkÚ¨4ò èä…ý3¤Ê‡À‹4,Ä üüöëº3ÆUaq»ï— PT;}ݵ›Ö¡«o1ó²@Ôœ+·2ùgY.Ÿ) ÇPí:ÅÆ EÊPÖ?”}ˆöÞW瘦}o ˆë€È1‹OA'6qçu«`ò<3¡Œ£°·Š{Í9ê#Åal…AGlù >¤c¢ï>bD{ïÅ8YµüÕ.Î0Ïÿ!<P-C%í/¿TŸdvO‚T=1í©ðJÄߪôV•h$Âï%­o’7¯£ Gš?þ S-·.2U´÷ÉFpn}¾Ï–ùÍJmÄÏ…¶æH k½¤‹²£tÄY0Ô¤ÝóÙÛ?DͶ¾Üçó]%‚h*ǹÿ*Óªÿ@x~Ë¥U}€c\ò ‡†~ýäÉú!Ÿ7aXˆKr¦`hs‰Ê-¡þU®Opk޹ª7'?ՙŅÙDøÕ‰«õ­Qñ.åë\Ì#Ök¦UHÿûñðáòî±Ï`9U¾ÚÞ‘‡2퀣ò“@°‰Š¤éyò£2ˆ^«€4’fEÉ A×ĊСn[GÌú`dF®áH¾Wñ¬"ÇFNŸ"Ìž\ÅUî­´›Š¼mi¼ Ø$Þ tÕC²’.óÇæS­ü³¼í&)+bè?‰©ù‘œæ«­ŠP‡Q9ö¯k»é¥Ò¨NsÎP0«<yð/ß)µÇâLv‡¸äÁÏ+ŸVÉ/šµ—5Xù† yÜ~»<’Þ2ÚäÎT´›í`Ÿú5'Ž„;îì]ºGùý—ì<¼ Ã'é«$;¹'[\åÛ>*ѽUP¥›{Ð+8\Ñfz–f)3ÃSN‡~êWIJSù«Ã©tت¡Ý.~RÆ‹¯Ò`ƒ G5ª ¾¸Pf£Ìä±M‹ŠHâã)=ªíW`ÔZŠ,˜‘ÝÏGNSoo†éÖÜ–­;!-*ÑiíÒ™ûÉwG<„Ôä@3¨_üxòãÎéX¡§äK|T.ü‡>æÃô×ï.f ÌïcŸcbQ)$†(n_®ôF¤?ÜSã´^à4+pÇàK"NXÉçùRIµ ø$‰]¯ßìTõUh¯ÞÈ{¨Qvš‘êI›jsØ=Fne®N§ÏI|9º)þ4§p½ó|ûct.VJÜH½OQ&g†‡ û׊:ºœÏ¡Ÿ$ĦËgø3(èéb÷ØÆçrÚjº×17>¼Ðû‹>\€»Æ†;š½;ÿ7Äúò7nÉ÷ª‡!ònì7"æÓÅ&F©úê—5É rŠÞ­üƒÄËâm(Í%¤SçÑ…´qVAŠóå0èÃ,àþÿLUätzŒ’ çÞM›%ØÏ‰'ž‹O[w­qÀ™V²ì<î²¼”YÒÜ{/Q£Lvß»N„ÜËéáØ\ƒ s²=²•c:5ëÄÀ'á>¹OcŽAµ£A¾ãçä*cI¼–£Ò™¿ì;ýÁ¾ë’5Á}·ñb2#t-ìÐÄHÑuázË´ôéyÍðjé²·|tj2E<Fâ¸F«< «ÉÊ\Íw‰ž6bb¡¸—@„¾òã]ª†sú/E)¦€$‰ñÇêQë 5HÞ9äø.Z}‚™²Õç;ücö)þ'—ÿ$ X&ANRya㫞u•µvEFcúBùÆá‹ðngàB&‘&“æ¹lÍZz6åX ±åX檢Í`ððé0ñË1ìÊiJöÎïÌwšéý‡‡#¹ÆcLÌð†¥‚‚VÌ¿9ìž¡ûèÅoó¦Ù#3)c›ã¶öÙ¬a˜ÓNøz¹@,ö#dœ@"¡ßPhwu«Kª«•ð õõJ`ä¥ >±~˜Å–ø»÷J`$ÝȽY0QåÒuÚÅS6ºïþ­»+.?RºÈ ‡WTªVW_V4ñ!éD© ¯¥-¿~ÉBˆ6Ä^œk¼DXÓF—‰Ø‘ îÛ»´2é7ŸSx-Ôð@¾uŽçlþÙr­^‘o*äÆÉ ‰Êá›e2‹ñ˜ŠÇµã—'=µŠà•]Á8â)kŒô¾Ë ‹G5üçÏ.äªñm¾O€r–Õù}\,0v6µ@ÍGBìõ>TGWeȵ¨Og³/Å52¹=*¶nc±¹kðÑnÝ“W»eJ&a黩>,4—*¬™L¦Gïò[©(6¡ò§òà’Áï£ ‘úûêÞRmqO!,74ÚÁExÚCJÿq.6íK#®¥òÅŸdæZÁb‡l<‘[ ü5 ø²Ùì½I®”¸÷—ú} í%~­`½…€]½)¯ïž”Vð"Iu-í5JrCW x” o`%rÍl²KPÝÏ Ó©]€q”ÍôÄ=>·Œܳ< Æ)šÙ\®'²„°,7ÀÔ‡yTW:\P‹°}™ßßGðMW`«¤Ü_Œn‹ß“ãæJ:Sà‘g˃‚ÑJ!dü=d¹fÈÅqƒm™„ú†²ÑÄ`zÄ!Pø~µb™2>ZGý[ùZÎ'×ÒAV­ä9þ´7Êч„›bÙøÐ@†m{ëùi¡ž©-Xb#šœ¹Ì§ÖȘ Ñö©ÍÔLš"3#NL6—9“G=£‚\=<ö½ $1áÔK®|’„ß ,Ó,!»àµím^Ýq ]C®@¸ý.Ê&RP‡ˆ0úXšÜáü,/‹·<¯?¿#[B”G0ï\Ç*½I»{+°lË'¹$•è3ð‹_¸CPâ{g’TM\§[YÄ{1OC¿D"V…‹ÞÃõè–…dù„G.„­o7m8™Ou—á,Ž .·¶o"°çÂ/‰g`ýIפômŒ¢dïàAjÄíú‰/1XÃÆæõ}­½H®k"ø o¨EGA¶Þ·Î;Ì + •²ýtLq:GÃIä‰)ΩÿpÒU»xlÞM±µQΤí9Ðn{ÌHñúó~ÀeÑrœã%6NÖy‡më=YZ°aBÎBºÌ [<éÅ(±ã&5ºv ¸¤ ™¢îÝ…äGÖY‘nÐA<¦A/·­‰­4÷Ïf8k?¦hºrEag¨wÀÉÖW £…ë—V ¢z³…ù¬ŽùJœ”æV)‹_C¤ê…h,w5¼áè!SI,3g¢/.¬ß{ðÔLlŠKÚ‘:|ŠoâVmXgêlù?q6–ó½T/3ÃçªÛ6uç¸]†Ìðºæ¯‘ƒg/˜(ív¿iÕ©,j£Ë©-%ƕ٧(Y$XÂfÑÒ?¹o-›mzqã5ñ/È×Ó~Iw-þÿ;LD{ø²ÉÄf[Ü\nÔ;øï€»8ò¶æõÿ"ÊtÞá“ÇÁ]ª¨ê÷ÿïvdÄ‹Õ …ý2Jz] "®©i ´Ë­òÚ5ƒSÿ}mbŸá±†Æ+~–L,²qÀŸnL|¾©ò¹ {ÇŒÛäß(T™ÒP".ÄJ>µˆÕ>¼‡êËÉ/õwÁÜ•šp„Xª¦Æ“á7²Z; "-Üœy¿(M@±Å±R¥]ÒÊhd6SŒb(‹ æfÀNo4”OƒË†y€W‰§¶Ú\˜®„]Kœ4©Nk•<ÕÇö Á¦Y Ehž«G¶°†A¼4jLš°•ã>'<ÜŠ8Ã×cü»Ù,T%ǰll°ö@ øÏ»ä}£Ìe–y~Õ2¨Öœ(ß³ç™wR0e*X¼fÚ¡þ²]¸ìǺ š`Æ©íˆF›«õaôF±8^ô¾JÞüÌüqlùèÖxÕOƒÄËo1yóË¥ ì*É™ ÿ˜¶öóÙd%Ի×Òú/HÊ´Jo)áâýÏ;dÑU¬?Û Ö Q’Ê\CBJ¨¯}ª[Z»lԇب\ìVêŽÎvîž~Ñ™ Ï/–jÑ”Õ;Á cD8ãü¢D [å‹ÉÒˆíÕfã‘ ´‘2½o8¢ pS °½ó©RtAªžìâTã[rI‘ïÐápåqJ ¥êÛuæK‡ šèô{r#}­È«‘X<ª´N…Uî×ËYW[äŠó>ï£RÛÇiã¥1gŸûÝÆBú;©½ü¢äÆÉ?¨à¡ä£÷–aæ.ZRN¸Âš_e?jÎ/Í<÷1)MÝ”ŽjçÝ7Fáo¤Ú‹|s7TJ<ÇÆyª ,ŒM+š—®,ÛúóÜàÿ·žXé7õA@š‘JW¢· Næ@cXQWH —÷üŠN¹çsf"Þ˧ó½sç°þ” ` î“à¶›ŠÄþJçªJmXРV]ä¥M™`°þxågε‹ÝžšŸÝ*ÁEâ`#ÔŠŸÎ`êyØ*Ʊhum$^9p$1=¥Ý•,ÓVîˆYñ¢‹ÂKF$Õ,f&*~™AT7>3ü=Žã6â0eLéKØ2K#Óg|M*Ñv¹ó A %Á¡úÍ6yçó :çM-í©bIJ“ö”a:õ¸UL*k„ùûGøn;Á#’ /¨òV ˆ;‡åšØƒÔ5&A'cðé0öÔéev~˜g¡˜¯Î¶—YÕ(÷ãR#Á+ÐÓædçTVÔSƒQ|¿ÅÎTÃÙç?GP¥Ìæ«$6‡ñ!Ñ'—õ¾W:OžEÐmÞ¹‘ÄåI¡r‘¥d|‘nÿxפ€2ÈK{nŽÌŪµE^áªïÍàš6ç ‹ô“‘ò]öâ…Ôahsá%†ZôIDÇè;ýó–#ÜÇëY©:b0Ï/Z$•êì@9—ŒPEœ·Q´Ë]ãÌ@QðgŠsÊ]çßÉq“n9dg•'q͑٠£Tྲ]^'¯ýÿAÜnYZÚ´|ãFëÍn‡ëA©kóô  ×~È„ù³d€DëAN{´ûNÀx„}YÊÄsa/?H¶ŒsÙÜÿ>±‹PÏßÒëFÒŠ sú‡õ­%kÑ7&eoUÆÅµµ>íÅy5ìíëë8@ß¹×j Ûõ÷ëk©`BCR~§²µˆÚCÁÛã=QágÚÈ%Øj┟f¢æ Íó^–;‡mOFíG7|1;¿LÅ]fLw·h/›CÊÔïô “9×}ö H&®¶x’r\ÑF¿yØÞ…A7!’âÃ'º+ÐȽzÿYU"$Èh»QÈŸñ“†.»K'IÁÞÊÙ±@‡1€Ô(+MUøZEB¸£SZËϜ[Æ#²/˜†yÒ0¡  ñ>ë®ãS¶â¹Ë_º_‹û_ÔvîZ¶–úãGv’΄—…tRñQ‘çIÀÏó_"Uß߯Sùü—-Ãpy¨L;œ? ÙûKk€Yë[Û³býYêX²+»º€N¯,Î Qo¥»JAGJ`Ó­oy9Çó9øS˜ñ7×à•tF<–"û*Ýë+nÉÅÉÂËñ˜2*ÈŠLè¿H”Ïž×-ÑþÂi ¬s‚Gæâ‚tB<½Ð†æò²·jê6Ýež »ÊÈ8QÓGËÕɼËÎêNôxá¿#n–ËɸS"™iÿmW™Ì¤*À½ÊµµäŒhŸg–,ý°<“ËÀPyYç PdWdA©ü·”qb’ Qއ0ïÁ¤F‰cÖ$³tRÓ¯b&ÃÎõC¾Ô‰l2³À{p‹u•ý7hÖÃ2›ÿ'…ðßî c-=ØK.)"b¢–Š_Õ¢$­Ê"þü¶ä—Ý»¢›¶È "YÜÌ@k2K¥S¿ðîCmæ2΂,óa@<”æzQsÿ,uaû…á—ž4ÂW°"ö<¦\v:8ø‰¼Œ˜Z¨î±‰Zä~ D©-ìûžC]¹Ü}\þH;~öóÀDñùu“Šu µÝ=Ìbƒ°L)>dL¯Ñ4<ªÄ× ×½‚`ì'dÏ3AVRÈ•1H«w‰ßÝ Lžok¶,ºI? þÀ(]Qò2ù›iì=CÐíP!vAªeÎCVÍ(P´L¤×ù^MbO„66žw»Ö³V\cÌLÄNxˣǖÕä,Ë'‚Äó¼÷ óÔ)G ÷‹"@ãQ ©ÍÆÄ?µâN^^Z^€ÔHºó,=¦ßõvìÒÝñð=eUÆ™EpUöMsiB äà`5h&.…LH»C¹¸Èg?1½íÿdgîñËDÚÿþûÛúàÎ1>h•—›UMÉFË!·=e|9qnÕ˜ÀÛ¡ÜxŽbRs×´Ÿ÷ð¿æ+ú?^à`N¬GËÓ$ (8ˆ#wB³æ×ïçþ±Níw†mКçyeìjG5<áüû¦¿%“óô›óú«?ÏÐÏÚ^ù þz`pß?A/Ÿ¥ú~{óíÕ¾'ú««çêξCßžˆ òjßϰkòV>ÒüýØ;äÔÿOÒù þz0ð} a Ä]Mæœ;õP,u~þ²+ôW©ˆëvÈÁþz7ê&λwùÐ!2ˆ!µO?™ËÀÀɹÜ@p5{¢Ô<}͹É‚»Ä## MÑ 9p3+Ž0+7òﺤ÷6qñ›.¹LHXötpWã&¶Á¸ƒ×R$'_sœçu=I×yN2¼è¥©×N¸×͉Ð|ÇàsœYý&ùÀ]qSc9°3Y-€‹Ô³ÚÁè_!‹ï\<ƒŽØow„ùÅ8º'‚íØàƒ@¶Ó{“5@/2•þ«;–J|a„jÜøLj±/·ýê’G{]n}¤(¼’å%÷Cf6…jr~ ¹ƒÅ¨-Ãsáщaj™Ñ~E¸­'N&ªwŽTà¹bxá¡1¶ð ÉÒ¨±nµnLO³ù Á週`÷œµÇáꓱLÀ&—$™x"i¿¸Ð|öÝ&¢˜îš½Öž!7“ àa Uæß?öEpÿy 8~´ÔÓXÇÿ-©¶ª81ÑjBìÌ@»Ã …7Fì/ŽÜ¨†»Ùžà¥ñ¶ZþM “k *<ZßògGyÙX›ßÿôð}Ï:6ý!5Q¤õª¾öfÔY£;p¯¦3fx ôãQRȣ㠖ôÈÝ꩎ñÆàRGͦ‰sùú-“ ç2­; üI RDP¹hëÓH}k‰¯ß¥xÖÌ2=$ùTÀx"ópî¦k ¤t*ép*ÎÔÐj];Ò¼&vƒ*Fa~“.³CAH ]’©M¸H·Ï“Š0Sr±ÉEñ>AˆÜ‘ý;¥º$W ûhx~`$§>¶ï¢nËx”@†ŽåOqÀôaAÓ¡ó:Ç0ÒÝèØß›‰š€p‹™s°CMÚ'ãšBþˆG»Í%jY`G •=ñt¸€OZ¹ËÀ0{B3õÛÝÂ\ÉÙÍ'àò9=lî)Û¢"39¸–±»gØ/²õ5Q3 ‚€„F9m_oOÛÌåÈ¡¬Ùf³Ñx ä@o{–¶RÖýšåþûW: ë¶Ý?~fˆ} sÄ; ‰§­Xì:}è¦|ïg ýzÐgï ùO §\îúÊ6²CLœéAÐ UEŸ¿xë†?åSq´—Òww$™¯ì„iújÅ×XoW[ËßL™Mî¸ m„y` ÕËÐ×)a&ähÈ;eËZÃÄz,!&è§%»Œô(=nžìf¾jü6šþŒó”ˆxÕÄ ÂÎK=±yuí  "P$‡ƒ;‰O·ËcpŸt8­)I ¸«ðØ¥E,¸manb‰ã•±ìêy(féÚ=mÏÅAþÜëüÕíÓçP5®G ÎìY&``Ì"D®xv÷pÖ+µcÿ@±¿³œÕuf°¦òŒƒaîÅßÙ–}/ ³Î)hŒØ4ÑE0ЄL5ƒu¥½I+ÀqƒZ®^“WEIÀãJÐÍ îH¸Ÿ éL?%ÚjQ†rñ»ìÙ=§ü/G¬GÇP;=£Ê~Teº„n•ÝùEc›È¹¸¾ÆÆ„dK{«iþ–c’Öz3saðÍüŸ Зã¨ÙoZ íM‡â†xŒå!»ˆ›å裦B()~ƒè[¯ÏCó;ˆÃ‹TÓî7Úgû‚úæä°Iãôò,snræ¬_L;M`Ú—o:²7DØáâ’C¥'–0Wua¸œÍÔM¨êjïªüî‰ffjâYuH&ukŽdµÙÆÁ„Iâ;Fû˜âÆ}Ÿm‹ò¼Á޶CÍ’¼‹œ¼Ä{ Oª|2AÀH6P þ²"è¿êo®e‰Ò×ÕŽ\wÍVwW~éÞÝüW˯s;øÄaðã=1ެmU‚k\þÍs))ñ‰ʼn 8ÈŠÝ8Ts¶ëÓ›ÖS‹c'eq a»áÕõЫ ´:F‹äö›[M û»zô¿ãð0Þ²:‚DwÛ·oUdhô½Ú‚m^¦"I4ã_§6wã~…·lsá›580&ÅœxW\_† @Éøùî\¥z5ÿw^¦öàµ<ÝÄ…kA– :ˆíŽ!… ý¶tîK Op™Ùz3¢2üëþ­K§+´~Ÿ§WFºað´Dw£ûjöˆr»³ c¥e8„’Î[Ö^$Ùßäº× ¥m–¾â Õ±ß€+¸ž±Þ°—¥ÆãyWOwTÔ¤7V½iõ)›!‹ÌíI×J°ì*Oûd(«Fè‚ÃRqDŠ6ÿM髵„$ÿZKÅ\G³¡©Ém¯y¿à\=BAóy´ë ÅÒÇè.÷;0gf„-Gv¶Á÷E¡ì?³‚v¥†GSåÄpƒ˜F%jO¦¦JwÓ®ÆWúê@ç&‹¾#`:CvŸŠž…ÑYòq(ò(–£`]²:ǰ&Lòú‚`?ªlSî‚/síÉsäÉ‚D*H¤}×¼S-3DÎ[¸7k½ð„ o;³˜Qñ ÜÑx¯zV$Ô¡=#n Ô”H€¶×Áhï±`Æu+ÍáVüþ7·’=ÿ¥IÜ“±‚3iH¯û”|mƒíëF<0âÏ5v°#å–2,ç«—È`Øã¡Zƒ ,ÏШ.ƒc#÷¥ë–>ËDE¸ˆ‚Jˆ¯Åᇕ©Ïâ*ú9QÖ Ñ õÔÿ9íÛƒ 0Ž'» m´EKØ^Ó¢@ŒNÁÒ<ˆM;|îžQvc…7=`̾®ÌUžÛÇ6°}±w¨„¡oäi ¶šÔŸC–C4«Õ¥ãžkç>>ÇLBüÞŸ ‚uÔðã®I´›ÉCòœpðkÊö_Õð43]WÕõ)€ µÄ¡ ÚfŸm’‹´k„¼ï+Wƒ³‰&²‹ÝZû¶­Oîµëy}D²°års‡IYÔùµœÐ‰œ™Þþ¸;˜¦©îÕeÅu„_“Íšý33`Ð÷ž²ÚÜ T*¤kãà­oa á(ÐHeMK´JuÊ}o:ËÕüŸìž+¨|¤M˜šY¨eÝÿt¢—OUðÖÆ¼h"acé@D]+–*‹º÷!êÎ"‰HŠ_‹ö¶ï&^…-d-AQlKè§WTÁ)‚ œÌËVÙÖ~„uuY@)Q¬¥Pð¼÷ʱ&-Å}¦»« ~Ò Ê­Gˆc¶µ4žñrf”†žÄÁóMY ¼šÖ4 „“M'Å—ƒ‹3H>†wµù½ícì¹of>„<Á'³?I=KÀÇÈm5aÛÅÎj˺†åguQ¯•| ¾;…_ØØ¥[,ãÍÁªÚª¶3X•ÅÏŒ)@ÿLíy×¢Ux¾‡ çÆ¿muë9ÙLŽW§•j¶¸£%±O`¨{ð{ìÌåšÐ;eaÀ“Ý÷9Õ«He|P8Xi ÎR1›ñM\röÒ6%'Í©,Ã4Ù-ÚÖV¾mÝT`ø>ÄWUa¦À|»e§€D×€œ§o­î(­ÐÇ=¯Õm‰1¬`øÙ-hè±µ¥¥@ÂýŒ2ñØSæ7ÑÀQïj^/—{0…dSD¢Hï±C‰ð·\&’°\‚½fÌf½•îØEu½:Lö—UÃiB¶G ÿKW")ühÑ ;S’TŸÿ^¢?ÁÈ“¤g |²H l*æ„ØG àµ=ý¶ϧ¤™$_hoLLÇÉàéìêyÕÊDƒ+¹=ÒÐñ®÷ë"[x¼Êò~¿ÿäM<0vÙ¢3yIÓpþN<ƒ¥çõjuçr%«ÑpåÑ/xm¡F¬ÉÔ®ˆ…OÞ GÁy¥T8ä@”Þd§K‰›Ã7Yôö™ÎF*MBé8õpªGA5ÿáâ/„nõì‹pü?ë<<L°’mws5ðtÄ&rÛܦ¿ÃOë¬g'0 ¯ T4%V/©={ 9 ’òaãÙžR0Á™á¢n¦K¶öP‚éèE &7Ëg^—"ë™Ñí|£²Mˆs< œoGCšÎ~—ð)®eÕ£bL4E¾$ Oñ¶ˆÔö4EÍ7N</ü¥^Å´ µâµ 'at\wHó¼Ù Š1xäi4TjH*ñúŸFET^$¶î±+“B*OV×%ic5+jž¥ÈÂ}•ç1€“÷rÙl1è6­“Ÿc(ËS×c~t &ÎöÊàµIíJëü9Ù¡VôD’j„ˆª A‹¸Vmæ¾ÈÏíT^‰‰(Æ™ûcäÇ!àSSõu+óÃoáwé(§ÔÎoúŸÃ§i Ä®áézSƒ~ÇRˆãx—½úºÌÅ 0ßø6`ƒÙðýP­â4¼-—ÄÒ¢ãé:zνö$ø/;M=ÕWséö‹U-·¢ßÅq…WBÆð’ë&‚žG)Á[:D°‹û“qP¢§ªGæ3çÒz6 e… DF v;†­j`[@Ÿó ´ þ:ÅAÈÌÞ” jCjóÖ™àå<"ú(< Pß„}ÆìV(h%즴©ÝYDœÕó꽈d‚rƒy^ð­mp#`ó‰ŒÚbò芥4›²Eçþ.N–…ê°e?TáM{âü ÐÍ>  d¸ï]¸¢ôFä^Lí©“ Å;›öÀÑý}BQ¤ûZÞf¾¼Òð6·¨¤”óeCdx˜âùü>Ð_|‚ovèKÝ\9î&šû;þƒߦ?V«R€7IÂlüK(9SÜ,q\´µñjÅæG]¤eo^¬qÁ|tjƒº¿øUóìG=ü-:Vë_uQ}.7½Ë¿ èŸÎWKÞ\|åÆnå4‹Zt~ê¥OÔGýj«åŽ€‹Õ#< ŽëÅÄò%j_"“Έ g’ä<ó/ÁªâèŸ_@Ö±…ÙÎw‰Oßg¦ˆ¼éÊð‚¹.5\–åpÜ&‘hœ5y¡ê,÷,r.œæЧ ¦å‘”æ7 [úκ'Öt#Á#6«žbxo³3 :FÂTiš³ž³R³.«E<Àaö]•öƸTã"ŠîÓ¶}}O1#Ŷ~êéf 1˜W*ÿûLj(µ›ù@ùŽÀè-‰®¤qì1mF`"¿–QÍÎÀÑ^Сq;®õ;áÄ|Á+¤g  ‡_AÄ·„¾Qyà*¢VñV¬ù]E•½l…+¸Ñ£š”3 RÚÞ܃—UãF®¦¶vñFe0íÏ*È‡ÚØ.ÛÆ£±niHì\”°1ßì[ÃsàÎΠ0o++5Þ‹ÓôÕþØ3>D¤µ¿ ßT¿F3 Aã¶—†#„ï¸ãÈ j6«èeªN÷¦ž£zÌÅ¥¾sþ•T$Êh%žha: ·T)FÍtË1i%FŽÒ8ø’¤„SUámüÉF%ÒÓQ fÂë®úSj^€þ:[§2¥wºR;KH4ßÎ:£ç½™g«)†2äûêìæív³Ò!žX†Ì-Wi¢±å¢Ö#2êøGO ö¿CÚ@«Û¸½gÿ€L]ôªœ“x⯲%gǵNoƒZÿh|„qzÍÐÛ•pÆLEv^iŸ½&k4ø××RÕ´»³sã•.Õfy„Ï•Vüðq µŽ;&F¿U«)šOñª±<“1t¾}gŽYuäúšò?E2Jc“ÄÄ|5‘ iVC?|©J§õÇIEV™ü½bõ&²ÄüÏÔ`òn÷Ý­‘xÀîP«@¹ I~rAì[V¥>¢9vH=yªâ…`Édíøsƒ’À†â'Öÿ %ñvÒÖ?Öv£,AöeØ/bË ß" %¬4ö–©Ç-ôö$(]õWôÔ¶:½x¿€_õqÃàF ªá`~ݤ6ߣßAëè÷êÓòwPßHW!ø‚ܾݹÉ>^õªW›ñ‡‚ÛÞˆë ô÷=¿ßŒ"É6žˆ: 0¬à€ÏuçV$Õ/2g~`s>çé e$éˆV¬k ½|Ò®>2Ä»ê–U} G]ÂÕA±ÊA&Ãu2Þ«ÛÍ m›%³S©ËèB·dP7Ö“åÓY$ê#lY¯Ñ½”›MkònàÖ 0I·›K“-aXwb£VöO¢ž3Y 7[+FªY>MïØˆ½.­üxD•?+1ò%â$_L±·Ï§ÑÀ·b²9lßÈG(½ñ-­¬׭ќߧgE¥*ë‚ðvOQ¡ùd ¹¼¡<¼†Ìòvý°E¬~„ ð—3öéµ"Á$ Ç gúVŒ_´¬æê%wÂ(Cgä&d` Ns1³k¸Å7ýø9E\$uIkœ@»P4œ¿'éY¢42!1têãòÍlµ³›;RúÀR•,òtYñý·ÔùúÇÉXý´ŸíÚHù;Tý¶'ûmX|=ó£á[õ.‡Û¢ÿ[´?í°m&ד©hù(?í¢£äÝ||‡?i`pU}|ûÇŸ%õ8Ï“qGÃéz|”ùô-òl~>¿Q ø$T³Ê®$V^¢’4È$‘ÏÅùG uìÚ}‚@а$úNé&Æ‘;ÄIúõSðqX•Ý^Dúã³ÖnJ Y°€wÔ×Þ„ÐP\ðÈ}åúwâÇ^f¸›kÎkÁ`¯‘Îàà†zჷƒh. ú[³ç€dº(Cf›sA[ÆZòü¦žÌO}ÿ$M¥Ì@ ¨`.ldãuÃÎqndáÍik2À…y–À&%ö 2–!+Œ'†ö2eÉæÆéCiØóF[RÖtùjp1m]ĬüÌoù=îÕ"¡ª§íawTµõ#¨ïWg !7á²ø™â8ò>pp¶¾~±dZëW±‡täGíp"Ó™eþâT*r“„üâ&~¦ôƒgOgxNŽ9zd0{x ê]2ñ™öìI &–¾Á›¦Ü^A¬"t&Ί»0Síd+MmÚÓyú¼¦ ’‰ ¬ÀÿQ¦ÎtVÇßÁxëºRã,V³b-¿ý;˜ôç8Ã}Ðoýý©Ó‰FWFƒ“çºÜ£ìT.1‚$hl@³ÁÞË´+ÃØ#0×igõ ÇKH®ÅV’шÚ\ã>X%X „ÉXEØ%Wü¾ötw5Q€´+L³ÞëŸ ì½ÇÿóH{@˜ûùe^ÿÿÿwdèö`1Z*ñ5ðêäúvçLÖÆ¥cQjW†í›_íV°”ÀÇœa¡5Áã­ø,¡IÊ,5Ô¬À5ãý«É ï¶3ÒÙ7份±8înªe¥›/Éq„¿õ2»…©þÄBŸÛö©'Vä¥!ûT/û•ìnlúrˆ:§ óÖ๢¢š¨tΖ^ÜÖ g¸çæ1Ìó<®[7jªÇM…³{-Õ³ŽñþR!ÖÌ\œÜý‰ÛÌ~Õ±‡ÖªмG€ ªØ&#c_f¸¯Ô?xœóÿ#-v¸!ÉÂ_Dökˆ}¦º{±:ˆO銉 ÜF„Âßáë¦@ÿW5e@ÐÿE þVÉd©„Üúïòé‰gÈ ^—®Í­°ÕYÖþîµÌ >j„‚Ω£Ä+°ãºŸð_€w’‚I$„äUÇôòÿ`áIuR i1¢ïåVéIäùèÀ¸Êš'(Ù.¥ânx€ÊÑF›ôYñx!PèSѼÍÅËYH/QÆ•¤‹‚ÍL–ð¯/}_ÃþIÇ¡]OÈþê¡Xø²ºÖSÄ@ÚIDÒÈKˆÝ­Ñ‘ñrôêx%O§ˆz„y_c6$`¼£>¿ÙŸÔx.M’Öúm¯#Ý‚èÖI0æ ‹UŠO’ÄcŒŒ%¼ßc|˜.K7t!³é÷Ú±GX¬JX­³þ†[ù°=ƒ[ßWàíᘠïcôb¶‹^ÃFûil&(ÂsAÅ(#ðO¡‘!éû¿8ª§ù´™°íwÜÑ?ÏIv „ûÒÙ5©k8x‚!ýÃãâˆI²üY™”cË×ýsþPÜVxø¯^vü†„¹ûNuõ¡y{üyy,]¹ >ÝüÌ ›]ïßÉ´¢Ïø¶Õ›÷)÷eÀ<»g„ ðë}A€qŸ[J„g½FŸß ÈVj—†^¦Ó#f'ÕÔÝnšÉ[\WA‚]‹œkÞ‹€øIo"§Ù¿™•øÄàÓºáöaDsß‘N¼ænØXÏ:è}í0ÍY& jí³°´N«a(‘åïTˆý—¨¸þòLéöÃ¥ÜI›'DdÚ¸‡®š._»¸„4ñŸvO80Dz“j?ÙwÊÁoÌ8Êe=ûn,Î ÓzèqB‹Lµþ†8òÕ:Ìïªþv²Xã1:!ÀÂfµpÜ×|Š€V«œÁ¤e/ÂÎPôœ€%¤Y ÛŒÆzÞ#.*9O.H ™ÒìŠïQmeÿ\~(àãÉrHK¬Ž'o5>Û1ÒÈjû‚êˆ÷ú./ÔÓ(«·ÝØ|öC’—Ê05ô£pÑùÒÌ8Ü›XQ 3úЀóèêuUUæ8ºÌÙ¯˜®¨ç©Œü™¥x ¼‚émˆjR ¨ ä2h.˜ LßÂ7Ѽq½ñ¯§å+IâË1ú¦ºôø\ë îD·ÃÓ!Á¶ì¡’µÇ’à­Ç… üÀ+y€Å5€ñ-Wúõ’½ÿG³ 8€J|JÿNòorÂÑæçJmüÍÛå/%¸¹…©ÕjE0›½RÄ"›ÇbN]7¿<ßRö7Œÿ0BŒ:ÿ™°»6º7/·jà:‡XÛfþ_Œ,©ùCØ(Õ 'ÒÙ ]› ŽÃJ:"×âTT ‹v!„z#}Ú|P¯üI¯:q±HÛ+ãû¬žp½{vE;áA&ÔW„{<=ŽõÆ–Ö‘[íQصéU½Ú΂I[ÅÇu!60‡–±¼/o_!VÒJ[¬IåêgÖ󒟤z–³Î÷çcžèÚ |‰Î¤†€µƒö —¼ÆL¬òWo ‡tÔqÀYÇDÈËÁ×4£J>ýÔ÷š@P€¤mU³IaTl×t¶W§ÒÖÞ(öŽ3¯§ÂDq™G‘¦³sâà õ¸Øm*õé¾  |ûU~DÙ ‡{ÞLPzÿs‚V°„Ÿm-¯Ýá‚óîß½ÇDÆŽ¶Ñ*%i*¦;VzÄÎyWe+‚‚xºµ:™G]PÅ„­aEhs­Gqêß.µ†¥3òñÔ•ä0!ã)|ÌÎE¥‹ÛGcmü|í9½=›¡Ä/¨NKGò¶•¥ýfapöÚ… ÉêKoøXDöÑÍ↳ÂÑïõ1±GÚ]kQ·#¾†¤õ~ôk´^þá'½û…XB´~ÚÉû [? €¥,î”G@ok4ŒLQS.,‰ì‡b´z»)œâø,!X b^iz¹ç5IÌ3ÖëGé^gò›*bžwC²Éa2{‡NÊÓ•‚‡ê°"ŠÿHì"¥‚ [mëå±ó‚ZËu׿"oÎÄ…C²b± >†ÿiî²Á°ÖåÃ`È)d Þ"cƒŽ(Û˜æâ9>%½ªô6Y6ÝP©ŽÿD^´Çù·‚Éc}0ö‚C÷G°¿ ª±U ëÎÀ$¡.î–®%sâª}_à½G{= š¼Œuûej¿Ýë. ““Û-ý8äT¡ç<¾‚f™ëj;‚+êeš*±1Ù,¥PdoWøuqèK ‰å;dÁúàw~ëðTj5‘ŒÀ>ÕëµØ2_í$ý}jCÍ"gש֠–'JZ¬B¡9©À8Þ†6Ìúgw|å‘«œûޱ¿-À]¯½#¥Þ¥Ç$ÎÜdâåæŽIϬb ÚXâ܃©üŸ¥i%>â"ØbVUÁàY¢´¯Œ Ø$£gŽM^ò5³áŽ&“ XéŒ.È=¹â/µtÈêípy^}÷NHÖ„Ö>]Z(`Z‰nÍ{_§`¢u ðï®æU»ý¯ÂoZ"t•y"Ñư"‰3ì“•Ù÷¤ÏÁ?Œ±Ö:=1Y ¾ÐŽŒ_?µ¥ÍWèiu)‚s=ÑêvRQ¯áT². >ÈB1»öwR0¬Gžð$jդ͇rï6^ Šôœ˜¬§Ñ€ o¤o®®ÙZj7³Â"ðq#Ìÿ ˆgJÐNÖž…ŸðÔÝ€pÐs÷÷—”™bñõdD5”Žúç$UšêEè9ÍÊ¿•oÝIFU«K0ªehƒ:sŽË#ƒtJÙ¶˜ñô#ßQ)Ìë<ºsØÑ¹¹D $bzg²9ËA% œƒ>ž§ËO‘ðe:WP® ¦J¶¤>P¯Þà>½ØRÕ± Ô¡û^uá燢·.ZÍ9kc–?¡`J¶aKwð‡Û”Äo~²£ÀehGÓù«€˜ˆú_J㺋jùÐ9P(ËÖl™Î¿¼ç”ÝSÉ·àtõHåä;D¡eOÞV=BßœQ¹'šŠJÄ>¼VªÒϴȤY[m‡u/©:‘gíª‘>ÀØ\ë:ˆø¯íÚÚå·T—Zꬮû ¤Wí•xFp1£š€çUÓ±"»ëÖÇjü]ÏqAª5µ¢4ð˜'åö·öuÎâÓ{è‹Ý–€ááAg°mU¸HŸΠš1¡‰“Îk5vnöÐÁÕª^fKÞ¼ê6b雋†°: ;®U%s7^Ö4Üà ó™û^Í"`/zˆ#8g¸JÑšb@S7KÜã¢Ün`°j¯PxCY€ËçMÏ@­4L¿#i¶âd ZÖkÌ•àj@<)¿Xê«$æ™Wî‚úyvÙÆƒ€{rsî•­÷ž,£‡¸aZ‡¬Èùó}|qW §Z<(ôªDñ¶qzÑq¢+ÏçÙ1V82‹°é¯qvÄ£]³¬É×OFõbÃñ³w­øÀJÍè¬]#%âÿ>òD¡³ ÒX>ÿLh”äÒÏd…oÂZ%£•«ºUFÞ8yI&¿Ãý¤Žz¤ßõB)Öw¢ÐžsüÝâ˜y±V£¢ŸámFš®ÎodŒ¹Ë¾SÄÆ•°“KÁâ?d{*å\wnf2ù:#Ì‘ýìL$“å ÅjÄ]ÂFQ'úåŒSº¢ŠÊ6뉗í¤e£õZ÷MN ^P‘ÑH‹#‘hñ1éÚÐxœˆÿz:Ž@íEÔ®Ä&Þ0X›JKyô–ÈCÐpö4x¬ÃBlK¶óûÂp¹}bö¤‘+RUlÚÈ–Ûr3¯1é/ó}ZºÊ9H¦I) Œ®°=Ô_õ6€«~ QId‡ð͈ü}k¤€ðLfô1à§]Ëþ¯Ùn·SýE¨þýÍéóh¬Pþ ó¦ÚXß•Ðã8¢DCÂ/®‘@bÈI­rý×w¬%Ù÷¦teÉ{êÆ–©¡ èNjTš9·ûhÓš“—Z×¾e.T覴ÛhÉZìia7JN¿8%"&1Mèš[·UZ9쳌1[=-ìQêÕ§"tÑ&£ù#bäé]s®"ÞYÏòÿzÀZdj"ë¯LK×~ÂÍGuï¾n膙`E_cû`{­Ó_¹1ÒŸ œÇAÈc[7ñ ¦j†/(B;ÛÇܾ¸Õz_ÀÎ’#•Üþp…k&º-oˆäõÞ² x5' Æ 94Õv—˜Ç¸s}†BUÄ®ñ¨äô§·e÷è쥡²_Àk”zn‡åºEׄ”Çv¿N´Qi3f²ú¸Ù[î¯2Qm1† ü3._wl2B?ib½+ÚëSKd>ÊðƯ† Y<¦vµà>4eD‡$®.U°õðÎ;‰F‡ŸH£žs+<‹î9%Ôa_.¶i¸nFTXêeÖO˜ÐNNP+^7ü'}ìù˜Œæl€ê µ\”-ö<™ðîˆ<™8€:¹ É\(3¦*(mã?jû|êW•ëàâamÑèNmÚTܳXkáÛIQeÙÖ­ÃKNöÚºZù¥ÕCc¾}:£¬”yR•¥VLãhH4H¹ET§ò¹hV5œ÷¤6œ@q`cLfv„+P½>\Ì{›™|÷{Ï,ÞÖ = ò$†¯ïb $üo⟞ÀÔZÒÞ”".ë»2 ÑBõDøjKBÃ7ç|nMÏÃûÀÓOÜænÁ`ߌ¨‰r@!´øé‡{{9^ˆ\_:Ù¸»3¬,¥…Ó”`ŒžeZkó#´ ›ók#å¸~7Æs dLªOÀ;Aw\ ›¥Ñ•úG¨²x CË,>B$„< Ô°>b‚¯ŽÕ0®ÃKÆC> ù|`TUÔF‚@hÒaœ€_S'ãŠiÍB§ðµ\”ÅiÕÔ ‹Œ«"–%CÄçÎ.6)þIøuô ¶GÝãðHªm ì°?¥2e=v¶’kIé·éãª8Töäñ Þ‘H±>ò@á¶¾´fËéLg ´ƒ+¶ðÊñý´Ìùè\|‹mûibú“ÛS~Ýÿ7K?mBý»Kûn/Úëû];í]þÐv¤€¨ï¶þ%ßn›ÿ¾ÝL¾Û!ÂýKÿTyßQí_‡lŸû[mÓþÝý­ï۬߶Áð}‡CuÀ-6x³¸1¿ððÐà¡6j™ˆÍ”8áû·R.¤›É1Äï¶Ìþp üï‘•‹Bé%žš^¶ 3 ù>Í.ƒ´]80b̀ع _kdz}âíyÞ?:Ç£´ª¿÷ ãñe}:óýƒþ£œÊ \a1(Yžâ^¯wMÂŽèFÙ½ÕIŨ‰Å,gŸìyÇìžgòNI5 ÿ^Ó`ù\¶/™.<:‡éŸ³ËÉí®Vp*´V£Â-*yÅÇg×2¢Á”@1¦ÉL“ï·jw‹5éål ñ6u[ñÖ«•á.ªJ`ÉY¬äD# "-Ý›;§ËO·§Ç½dníáMÖð¸ä8õ…mË,sŒ³øÆ&²¹0)]ìâÞ«ôRª‚]y¦kd?¢ ÿkÇÙ!m¼Â¿ÉFÅê]wERu¡„䳯x–Inº‘M¹cocI¤0[hï+9*¬€˜AeЄ]¾Ëׇ5ô!OøacëMÃyìQ|¶J¶œÛ9òcƒBoªÙ—õæ»ËItÃÂ+Ë>¢*öva&k½Í­iß]¸€ds¶Ü(±iË.BUGK¤aC;“oC³E³ªÀÅäÿ !x´uƒåŽù“ÑkÚ#Ó Q%Ëe5ZåK—Ñ«ñ/zv6eAyq;*®9nªXÓ8$|° ;g/_H’÷ºc½´pm¬ˆÜéu¹,º¶‚±ŒKÿÀÿo‰˜ƒ‘™ïüCzkÈ cБŽT†/5ÞÀ¸ÚÅa²ò›3ðªöµ å*Ö%ÆÑóhš†3’ç€Y[dòb˜¾qù»E¿> †ªr1Ý‘ˆ(ùâ¯-nù£µ9…â•-ú©chPŒOСZËqæÄÆè·¿à°K²_SϨª@CE69ËŠ„¨Ìª8I¯ +´ia+ÖNÆn瀤IµÞ ëÛ8·¹Ã–:{!a:÷¨}tŠŽ×‘æ[JG€ç½ÝË6¬B)y¡ëpy̱8G ˜e*gª!ïŸu¢’ú¦ÝHË z0šýÉ2#±ì»@순(ÚL[/µmãß‹^1"a{öËÇPê·ês5e² A4Õ7A—Û`óuÿrRô]!œ3Ò'BŠîk9°vn‘dN_öÈ zôž(Öž:è—$ïßyb»fû2×èáÏ誀!&-íaÖÍßV ìÁ¤XÔ èYíWòõÆ­Ê_›÷càCň֘X‘gëÞfAÑ$æ‘RÙhZ¿uL U¡Þd;¯Æ¾ Ŷ„ðÝéè„H$Ê¢ÏÈ®oÕÅ×ò0Ä?.t©×šKnÒy—mKá›è•´nà¸Y}¢Ž?íª%ɶµÉ«uÍCÏŠ`ºeë²2¾êù)ql¨ò,Ñ­yõnÜ1¤Ö…Ò ï–a;×e|bA,É”qË™è†f2š­aMÃO|K?= ~×þvìÒ9Õvx¡íè e‘£KýJ¯žÚW?Ì«¦ØMªV$]ê‹øª°ø);¨]6þ*ßEôsZgÎO÷­ÚÞ÷"êD=„ò#ºÂÑ­ dèòí¤ ¯® 8Ü»4þrám>—~V¨ùøŽ$²PDITj£Í-ãˆÒs«1œ/ÝzÇž™¼>ƒo kgÉb8EÖ–â\+“±½‚=ñµtš-n쇚cɶ>œ'B]YÔ·jï9—¤¸Ù¿÷ä`|ÇîïýhiaeÖœaU­7ôÉ€ó9lN7˜>¦B„Ô4·ìÞ˜l‡#Š ìŠEþéSÔ¥ ü[Wƒ’]‚_OBQ·wQWpk>çÒÄÏG ܆pæÍú€H²¤­^’KG¦ÃCg ósÛ1ËU6f県|ÿIÙÿ0\±ˆ›‘Ë2¦G^7ë&jˆå¸åëñè” ñÛ“Ÿ&¥xùJð2¨y7Ô+„Z®Äþhöç°ÖÏ|ÑTb´ægkøˆcý`dõÃQ¶0t«ü Ä2å3)Õʲ9½®å®ße÷,ç]òÉUÇøÚls–o§%õÚ|ƒ;4JŽò†š óÿUk¨XL¸]üuD´0‰Zé]#µ„ôãÊ¿ÓiTËí[Ãß 3KNE¬æ²Ç÷ìÆQºÒtM!îí’9GB©=£÷|]Lp ðZ®$þÅ¥ÜÓçz²Ñ}T¿Ùt‘³œ¡k²…¼Ë%a„3înÐø6?cƘKM‹™ždÊ—JžOèèöTƒÐÛ¡IëM!ê=2ŸñäsÓUàô±ýX „¨­(hmæZÈîÀ.:üI„*î¯C°Å"™ Û|A2AEm@¤„õ@ŒÕ±Nüñ*Œ˜y<îÚJ 38Á^p à´õkÜÇé–10Ä»ÅðÚµWEŽuÁYòôÕ©º;X‹u—;æ²Yµû¥Ò¾£‘ü¸ÆÙgldºŽmŒ«-^kÚi–Ã…,â’²Õ—5 °G…#NßÿjèuRõ `s?-£i‹ÔN¿Ä;™ EÍ‘ ä5ëÔÿ2Ef̶Œ±ˆey÷Yõ!ÿo¨§mÄ&ðûGmOn»c–­áe£8Äq~ðŠ©–¹Šm\ð° ƒoÜ P°±xŒ5 L›å¼Pø¬rª&,¶üåÑQ2à¯#ñB«ŸB‚~ÊÛ¹ !4KR¹#17ù`å¯_Ñöž «ØØ}¼çHÏ ð<é‘I>ÂiÇꊽyÀaáØ(\½bð¤­î<˜KõW„G•ƒ'–ÂZ®(¸—H(à ¢Cš¡(ËÝ`ƒMD$Ò nõÓ—FTsYzž0Äÿ")Á¨*Öù¤ò©ÉkªÖ;#(lócTð´N.ƒèª¿C¯75}·ÿzO•ÝÎUB•®ì±¼øñ3‡jOòtÀƃpz⪽o¡Ek"+^œ—ƒ§AŒ ?aµRœ2YíI«ÚÚŽ3À¬%dEu1,ã‰Çú>¹A† x‰=iC©åþ+,K6óë’ÝÉ ó$ðz—cÝ;u!s§xÜÙ;1Ò.Î cÅ>#€J¡þì ëÁõºÃveHJO±|avÉð©@f<ü; b±ŒùV´Qs~¼€¤,ï]€¯dÞ]'“­|y¶.ÏœšMSßì=1¨·Ât¥C¤fxÛƒØ6ÉüAíªl¦ÙÀÇ•J uB´Q?Npñ?/^*³vÂÈ{ùmò;=ÿ7K‘y…v~·òævª¿"¥º~Õdä[F?•þVÍ–Çõ_´.;ú³q‡ì=3À´™À çø‰÷t¢PƒÈs«—Ò•r©û :%çŽ6aî dºqYûØé@¾úîh<Τ¢rØø(n5ÖÌòwÑÔ›+œ—–Ú³ËfT7Pe$ÜÍ—A·’ôU½ÇþÚ„ÜhÒVš ¨Ê–¸ÂF‚œœFC6åŸðZ놜å+$¡/÷D¨ý!œJ!þêÄAhÏqV±ëÕ‘÷]HÅÙÅÚ&ÝýΨøç£ÜŒ6·‘¿lðƒÁÞí]'Q5ZÌ@Îu¾Ð|¬ÛZï_Ä" ðÙI Û¤Ãç¦"´4ø0«ZMé «3.‹) c¸‡G§o'Å+Î ¬{ÖóWeœ¾d/¬t¿¯ØPª*æ¹íø¤H’£+c™$ôtdóeÀŒû ÙkÿyàZØÕ‰+ÿsO¬>Ü_q”ý¸ojggc·· Zå¡ÊyÍÊ“{4&ñerÄ%ů)€æLe³Ûw o¬ŒïµÐ.Òv?oÆ ‹Å§’X“eh¡A‘Uâëû4y}+»0:=)™òmUY»CãrüÎ q“G­bã®GÎjßšQdÀ8iúŽŒ“;7¢má夭ñ[žžüOKBsöJ’|˽²GYî5a“–tIB†^T•þ—òªT`ñ7hì{4µñKœòÞkxÝx‹ÄÙÁj½øZ¾hòï¤Ám/_ðéá ¥2‘4ƒ`Q;xŒË“äŠi´G}ª>Dõ‡ô÷f)¡Ý˜ÉPµ0 øißbÿ„ŒÄ}Ò>Mt•ħ/° xçä¦ÈgÚÀÃG1Ù·ÝÖ=½t¾ú]r%šø4-„uÑ>C1k~“MÛÆ±fäÁz éÖDœü@``_nðãYaÄY·íÂ+KV{»É² ârÓÏÜÙy×q{jXÕÝ´Œ:5jâ4úNgÀÈBæÅ¢ì¯‡Ás|ftÈ'ü*J^)a„’sÉÒ÷q=Ÿx#föüLÿ5X¦Šö¦ÖSæà1®kì[Êx¿Âûp®°©."gœ¬w»yéc­íz{Ÿ”bÎdá‡xñR1 ñ9ù9µÒìàí¬<ëAøcÑ ²-šIF:ÏÞ 8M‹Æxø³>¿WüªPVÊû€±Ì\u”Ö {uÈ]²àOÞ“ËÝÅTÙ‘SÚž¿p„3«EN½‰æþÄBuG½fî•ÊVêÞó±_Ÿ?z`|¶Ú6ˆëúÌÙò@@pÀÓÂ0?· |•aRD›E·uùi-Æ\È‹]½CгHÖV'tÓ |×o¥þ‰Ì…Šv{;W„ XFÞÃP4w6q3ð§_R ^¨XywÆšûÍñ9±zXÆþg!Ë:’±IŠ\çžvíÓL Þ÷=Î]F™Í•ï<Í /à  q O<¿ dØÛÇÁáM÷íŒPŽrbA_ 2,ç>¶Õ1‹ûtº¦a +Ó¨Ç˾ù Û=ý Ñš´¸9ì³wdüÆ4Cn– ãtƒ»ÖÒ¾·¬§¯~Ó?ž–ýÅY'ÖSÛÐáß›³uðzûÙ’88´ü Dìö¤Ý­¬¾ 9þ4¸’›V÷;]§Û§gg†Ólù6aµ-þoJ؃² ††e»vÞÚ4ÛI€K'œGCD{h¡¥“×O26ùa—;»YSò?Ëz7ýTnŒkx 4æDø˜C70•ü¼ŸPò–øOÀTH[W~+yRkýÛÿ%TDw™B7À¨]šñ3g]›¶OF„a¢ï¥#ž!Ú¨æÎ&¦‘þ89ˆ¢ûy ÆsœòÞRŽ;ÛŽì{cð²J&aä ð¨ä ·ÒO…Bç4õ{@ÁßûGkWh/tÐc·¸vÒj¨ö!‹Í00¢Å;?8kŒF®]^!G¦aÅá£!<åðŸ{P»^g›4ÙòÓBf-,HIÇd\õ£ûÚp„ˆÍCíYhʼÇÇZš÷ñU‹¢|C ™eóŒz®„|Åq~h¯i¹çv0<¼L#ÉînT‡1<ðDæÚ®º£vÃJ‡P@€|Š:¾¶[H’rè­Ë8RªQ­+ž9ÈRhDÿiÒ¬Ç÷Ô±uâãV%l¶Až¾äÌàÑVÓã©Ù¸Í»ŠŸ º/«ñºˆ†q³ªº,ÕærÑœëc*ÝÛî<ÄDÁ~*VIÀykÄØáÿF1‰¥`oîXãàƒ»×âǬÏÂ2”r«ç’¢·F~ 9Š^jiªßUÔ› ö€ú4…ÏþSýç¶f(—³ ˜S*o „–Qr4`5*ªDBb ò­fm§Q¼”-_&§MåÛ—¤îâ§1¤B%·wôÐV§äXûE^2¢“ñø‡((¬ÌÍ7Þqâw¼b NA IâZRJË´Ê9e5%ó{ºL[^Å” •¦pÙué{Ö$‰<³³)SOÐNÅ ?s¼#êAu´z?ó«S²(•¶'*ô‹V6 ¡Ú5ÏÏDmã ³Œè˜4Ó©«·A¡›9!X6²zÄ„áï*” û¯²;A@òe6¿íÆŽˆ?laOH=î¨óïZ~2ÞãÌù€ss’Ù€ôæýþm1–Ð¥Q„äÇK ßõ!…¾¶ã¤ÃdÚ RˆõÊ£sPx °yÉ#q?:ÖØá·{7[3ïðŸl #‹ebG9€ó$Ã­× BCK Û1ýE¶ð›Š¢-ï^æ_¨Gåÿr!îš»Àp±J½9A¾Ž3ÒG¼§1…ÍKÉŒÇNÀ¨#òùžo&ô»s@FjüwË·¡~…™9Yï*¦»ñ=Îx{¢3bœ9¥©½lÌ®ô9€ù”¶¿6¹¯¬ËD3˜ýʃð®«Uÿb|ت–í ¸ê_c¼cbˆD-äõ+®Ï}úráÄ­¨@oáä*ë=ò½¼ó¯CÙt5ÛP3È"Xašná÷ÕºÔœÜÝñïÿ&ÑKɸ,9U{ý[(›H=ìS)šDñùˆ,9ŸpÛÕ¸+”6É|•’‰=・ŸËA%ÛÕ¹?fRU_Ïò’ýÅI×BžÔ'Ƣ讖âÙ\Íyåên•²z‰ò¥\Ò¤…2paý}3p¿³3›»!Žôƒô¢Š]Q`]Çnvö4‡olÉýY˜¬•'ftK2»ºçâ²AÅ8ÞÆ6ï§ûaa $ÿHc¶åùf›§bHŠ˜E‘gº« žÐ±_u´ÊöSì—AF>”ÅÁb‚¹z1«oðµ&R)ÒÝ0bDÿ,ÑyܲÄ~ÜüˆóüenÕ>þõJƒ«Úö*A”~R‘åòf‡Þ¾*ó¿ pwQLAï(7Ë:°>€TýR¤ßØ·;!5Dz¤¾øT´t^Pô`9ê:â…½ˆ„Ú<;Vˆ¼×JPJ°2a²xWë utÌj²T˜b†€Ý5¢®Y¹Z?[»zdò^°GWÙVB×–8šÎЧ’)zÿX³–ƒL”dõðë{·!öÉ‘Ž(Ucn9(Ù$-%ƒ1!$ÞyÓ¸3¹0\ça:50¦Àò üÅZ‹à Ëó}`¯Zfò04m1¡»xð6éšIÍj5¨…ôƉòûòzýÐJ½¾*Qí.;‚fÿÙicnV Bþscantailor-RELEASE_0_9_12_2/packaging/osx/buildscantailor.sh000066400000000000000000000077611271170121200237420ustar00rootroot00000000000000#!/bin/bash OURDIR=`dirname $0` OURDIR=`cd $OURDIR; pwd` STSRC=`cd $OURDIR/../..; pwd` STHOME=`cd $OURDIR/../../..; pwd` echo -e "Building ScanTailor - Base Direcotry: $STHOME\n\n" export BUILDDIR=$STHOME/scantailor-deps-build export STBUILDDIR=$STHOME/scantailor-build export CMAKE_PREFIX_PATH=$BUILDDIR export CMAKE_INCLUDE_PATH=$BUILDDIR export CMAKE_LIBRARY_PATH=$BUILDDIR export CMAKE_INSTALL_PREFIX=$BUILDDIR mkdir -p $BUILDDIR # For Tiger (10.4), we must use gcc-4.0 export CC=gcc-4.0 export CPP=cpp-4.0 export CXX=g++-4.0 export LD=g++-4.0 export MAC_OS_X_VERSION_MIN_REQUIRED=10.4 export MACOSX_DEPLOYMENT_TARGET=10.4 export MYCFLAGS="-m32 -arch i386 -arch x86_64 -arch ppc -mmacosx-version-min=10.4" export MYLDFLAGS="-m32 -isysroot /Developer/SDKs/MacOSX10.4u.sdk" function build_lib { libname=$1 libdir=$2 LIB_DYLIB=`find $BUILDDIR -iname $1` if [ "x$LIB_DYLIB" == "x" ] then echo "Library $1 not built, trying to build..." LIB_DIR=`find $STHOME -type d -maxdepth 1 -iname "$2"` if [ "x$LIB_DIR" == "x" ] then echo "Cannot find library source code. Check in $STHOME for $2 directory." exit 0 fi cd $LIB_DIR # make clean if [ ! -r Makefile ] then env CFLAGS="$MYCFLAGS" CXXFLAGS="$MYCFLAGS" LDFLAGS="$MYLDFLAGS" ./configure --prefix=$BUILDDIR --disable-dependency-tracking --enable-shared --enable-static fi make make install cd $STHOME fi return 0 } # Check for and build dependency libraries if needed build_lib "libtiff.dylib" "tiff-[0-9]*.[0-9]*.[0-9]*" build_lib "libjpeg.dylib" "jpeg-[0-9]*" build_lib "libpng.dylib" "libpng-[0-9]*.[0-9]*.[0-9]*" # BOOST #curl -L -o boost_1_43_0.tar.gz "http://sourceforge.net/projects/boost/files/boost/1.43.0/boost_1_43_0.tar.gz/download" #tar xzvvf boost_1_43_0.tar.gz export BOOST_DIR=`find $STHOME -type d -iname boost_[0-9]*_[0-9]*_[0-9]*` if [ "x$BOOST_DIR" == "x" ] then echo "Cannot find BOOST libraries. Check in $STHOME for boost_x_x_x directory." exit 0 fi BOOST_LIB1=`find $BUILDDIR/lib -iname libboost_signals.a` BOOST_LIB2=`find $BUILDDIR/lib -iname libboost_system.a` BOOST_LIB3=`find $BUILDDIR/lib -iname libboost_prg_exec_monitor.a` BOOST_LIB4=`find $BUILDDIR/lib -iname libboost_test_exec_monitor.a` BOOST_LIB5=`find $BUILDDIR/lib -iname libboost_unit_test_framework.a` if [ "x$BOOST_LIB1" == "x" ] || \ [ "x$BOOST_LIB2" == "x" ] || \ [ "x$BOOST_LIB3" == "x" ] || \ [ "x$BOOST_LIB4" == "x" ] || \ [ "x$BOOST_LIB5" == "x" ] then cd $BOOST_DIR [ ! -x ./bjam ] && ./bootstrap.sh --prefix=$BUILDDIR --with-libraries=test,system,signals echo ./bjam --toolset=darwin-4.0 --prefix=$BUILDDIR --user-config=$OURDIR/user-config.jam --build-dir=$BUILDDIR --with-test --with-system --with-signals link=static runtime-link=static architecture=combined address-model=32 macosx-version=10.4 macosx-version-min=10.4 --debug-configuration install ./bjam --toolset=darwin-4.0 --prefix=$BUILDDIR --user-config=$OURDIR/user-config.jam --build-dir=$BUILDDIR --with-test --with-system --with-signals link=static runtime-link=static architecture=combined address-model=32 macosx-version=10.4 macosx-version-min=10.4 --debug-configuration install fi export BOOST_ROOT=$BUILDDIR cd $STHOME # SCANTAILOR cd $STSRC # make clean # rm CMakeCache.txt # needed in case scantailor source is not updated to compile with new boost (>=1_34) test infrastructure [ ! -f $STSRC/imageproc/tests/main.cpp.old ] && sed -i '.old' -e '1,$ s%^#include %#include %g' $STSRC/imageproc/tests/main.cpp [ ! -f $STSRC/tests/main.cpp.old ] && sed -i '.old' -e '1,$ s%^#include %#include %g' $STSRC/tests/main.cpp [ ! -f CMakeCache.txt ] && cmake -DCMAKE_FIND_FRAMEWORK=LAST -DCMAKE_OSX_ARCHITECTURES="ppc;i386" -DCMAKE_OSX_DEPLOYMENT_TARGET="10.4" -DCMAKE_OSX_SYSROOT="/Developer/SDKs/MacOSX10.4u.sdk" -DPNG_INCLUDE_DIR=$BUILDDIR . make $OURDIR/makeapp.sh $STBUILDDIR $STSRC $BUILDDIR cd $STHOME exit 0 scantailor-RELEASE_0_9_12_2/packaging/osx/makeapp.sh000066400000000000000000000026451271170121200221750ustar00rootroot00000000000000#!/bin/bash if [ "$#" -lt 3 ] then echo "Usage: makeapp.sh " echo " " echo "dest_dir is where the output files are created." echo "scantailor_source_dir is where the scantailor source files are." echo "build_dir is the working directory where dependency libs are built." echo "" exit 0 fi DESTDIR=$1 SRCDIR=$2 BUILDDIR=$3 export APP=$DESTDIR/ScanTailor.app export APPC=$APP/Contents export APPM=$APPC/MacOS export APPR=$APPC/Resources export APPF=$APPC/Frameworks rm -rf $APP mkdir -p $APPC mkdir -p $APPM mkdir -p $APPR mkdir -p $APPF cp $SRCDIR/packaging/osx/ScanTailor.icns $APPR cp $SRCDIR/scantailor_*.qm $APPR cp $SRCDIR/scantailor $APPM/ScanTailor stver=`cat version.h | grep 'VERSION "' | cut -d ' ' -f 3 | tr -d '"'` cat $SRCDIR/packaging/osx/Info.plist.in | sed "s/@VERSION@/$stver/" > $APPC/Info.plist otool -L $APPM/ScanTailor | tail -n +2 | tr -d '\t' | cut -f 1 -d ' ' | while read line; do case $line in $BUILDDIR/*) ourlib=`basename $line` cp $line $APPF >/dev/null 2>&1 install_name_tool -change $line @executable_path/../Frameworks/$ourlib $APPM/ScanTailor install_name_tool -id @executable_path/../Frameworks/$ourlib $APPF/$ourlib ;; esac done rm -rf ScanTailor.dmg $DESTDIR/ScanTailor-$stver.dmg cd $DESTDIR macdeployqt $DESTDIR/ScanTailor.app -dmg >/dev/null 2>&1 mv ScanTailor.dmg $DESTDIR/ScanTailor-$stver.dmg scantailor-RELEASE_0_9_12_2/packaging/osx/readme.en.txt000066400000000000000000000062211271170121200226140ustar00rootroot00000000000000This document describes building the Mac OS X version of Scan Tailor. This requires that you have installed Apple's developer tools. These are an optional install on the installation disks that come with your computer, or can be downloaded (free registration may be required) Apple's web site at http://developer.apple.com/mac Downloading Prerequisites First, download the following software. Unless stated otherwise, take the latest stable version. 1. CMake 2.8.x (tested with 2.8.2) Homepage: http://www.cmake.org I recommend just downloading the binary installer so you can just download, click, have it all installed and ready to go. Be sure to have it install the command-line utilities. 2. Qt4 - Tested with Qt4.7beta2 - some odd problems when using 4.6 stable Homepage: http://qt.nokia.com From there: Download -> LGPL / Free -> Download Qt Carbon for Mac OS X I recommend just downloading the binary installer for Carbon. Since we are building with dependencies for Tiger (10.4) and forward, we cannot build a fully 64-bit executable anyway. The Qt Carbon build is ppc/i386 only. 3. jpeg-8b Homepage: http://www.ijg.org/ The file we need will be named jpegsrc.v8b.tar.gz or similarly. 4. libpng (tested with v1.4.3) Homepage: http://www.libpng.org/pub/png/libpng.html We need a file named like libpng-x.x.x.tar.gz, where x.x.x represents the version number. 5. libtiff (tested with 3.9.4) Homepage: http://www.remotesensing.org/libtiff/ The file should be named like tiff-3.9.4.tar.gz 6. Boost (tested with 1.43.0) Homepage: http://boost.org/ For Boost, I recommend the file named like boost_1_43_0.tar.gz. Instructions 1. Create a build directory. Its full path should have no spaces. This can be in your home directory or even on your desktop. 2. Unpack jpeg-8b, libpng, libtiff, boost, and scantailor itself to this build directory. You should get a directory structure like this: build | boost_1_43_0 | jpeg-8b | libpng-1.4.3 | scantailor-0.9.9 | tiff-3.9.4 3. Two more subdirectories will be created there: | scantailor-build | scantailor-deps-build 4. Install Qt 5. Install CMake. 6. You can either locate the "buildscantailor.sh" file in the Finder (It is in scantailor/packaging/osx/ in the folder you created above) and right-click (or control-click) on it to "Open With...." Terminal.app, or you can open Terminal.app directly and run it from there. This will build all required dependencies and scantailor itself. It will only rebuild the parts needed each time, so once a particular piece is built you will not have to wait for it to be built again. Make sure the building process finishes without errors. Warnings may be ignored. If everything went right, the application exists in the scantailor-build folder named ScanTailor.app with a distributable disk image in the same folder named ScanTailor-VERSION.dmg. The VERSION part of the name will be replaced by the actual version, taken from a file called "VERSION" in the root of the source directory. scantailor-RELEASE_0_9_12_2/packaging/osx/user-config.jam000066400000000000000000000053621271170121200231340ustar00rootroot00000000000000# Copyright 2003, 2005 Douglas Gregor # Copyright 2004 John Maddock # Copyright 2002, 2003, 2004, 2007 Vladimir Prus # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) # This file is used to configure your Boost.Build installation. You can modify # this file in place, or you can place it in a permanent location so that it # does not get overwritten should you get a new version of Boost.Build. See: # # http://boost.org/boost-build2/doc/html/bbv2/reference.html#bbv2.reference.init # # for documentation about possible permanent locations. # This file specifies which toolsets (C++ compilers), libraries, and other # tools are available. Often, you should be able to just uncomment existing # example lines and adjust them to taste. The complete list of supported tools, # and configuration instructions can be found at: # # http://boost.org/boost-build2/doc/html/bbv2/reference/tools.html # # This file uses Jam language syntax to describe available tools. Mostly, # there are 'using' lines, that contain the name of the used tools, and # parameters to pass to those tools -- where paremeters are separated by # semicolons. Important syntax notes: # # - Both ':' and ';' must be separated from other tokens by whitespace # - The '\' symbol is a quote character, so when specifying Windows paths you # should use '/' or '\\' instead. # # More details about the syntax can be found at: # # http://boost.org/boost-build2/doc/html/bbv2/advanced.html#bbv2.advanced.jam_language # using darwin : 4.0 : g++-4.0 ; # ------------------ # GCC configuration. # ------------------ # Configure gcc (default version). # using gcc ; # Configure specific gcc version, giving alternative name to use. # using gcc : 3.2 : g++-3.2 ; # ------------------- # MSVC configuration. # ------------------- # Configure msvc (default version, searched for in standard locations and PATH). # using msvc ; # Configure specific msvc version (searched for in standard locations and PATH). # using msvc : 8.0 ; # ---------------------- # Borland configuration. # ---------------------- # using borland ; # ---------------------- # STLPort configuration. # ---------------------- # Configure specifying location of STLPort headers. Libraries must be either # not needed or available to the compiler by default. # using stlport : : /usr/include/stlport ; # Configure specifying location of both headers and libraries explicitly. # using stlport : : /usr/include/stlport /usr/lib ; # ----------------- # QT configuration. # ----------------- # Configure assuming QTDIR gives the installation prefix. # using qt ; # Configure with an explicit installation prefix. # using qt : /usr/opt/qt ; scantailor-RELEASE_0_9_12_2/packaging/windows/000077500000000000000000000000001271170121200210755ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/packaging/windows/CMakeLists.txt000066400000000000000000000112541271170121200236400ustar00rootroot00000000000000FIND_PROGRAM( MAKENSIS_EXE makensis PATHS "[HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\NSIS;InstallLocation]" ) IF(NOT MAKENSIS_EXE) MESSAGE( FATAL_ERROR "makensis.exe could not be found.\n" "makensis.exe is a part of NSIS. Get NSIS from http://nsis.sf.net/" ) ENDIF(NOT MAKENSIS_EXE) IF(MSVC) GET_FILENAME_COMPONENT(make_program_dir "${CMAKE_MAKE_PROGRAM}" PATH) FIND_FILE( VC_REDIST_DIR VC/redist PATHS "${make_program_dir}" PATH_SUFFIXES .. ../.. DOC "VC/redist directory." ) IF(VC_REDIST_DIR) # Get rid of .. components in the path. GET_FILENAME_COMPONENT(VC_REDIST_DIR "${VC_REDIST_DIR}" ABSOLUTE) SET(VC_REDIST_DIR "${VC_REDIST_DIR}" CACHE PATH "VC/redist directory." FORCE) # Find CRT redistributables. FILE(GLOB crt_redist_dirs "${VC_REDIST_DIR}/x86/Microsoft.VC*.CRT") IF(crt_redist_dirs) LIST(GET crt_redist_dirs 0 CRT_REDIST_PATH) ENDIF(crt_redist_dirs) ENDIF(VC_REDIST_DIR) IF (NOT CRT_REDIST_PATH) MESSAGE( FATAL_ERROR "Could not find Visual Studio redistributables.\n" "They are typically located in a directory like:\n" "C:\Program Files\Microsoft Visual Studio 9.0\VC\redist\n" "Please set VC_REDIST_DIR accordinally.\n" "Specifically, we are after redist\x86\Microsoft.VC*.CRT" ) ENDIF(NOT CRT_REDIST_PATH) ENDIF() MATH(EXPR bits "${CMAKE_SIZEOF_VOID_P} * 8") SET(LICENSE_FILE "${CMAKE_SOURCE_DIR}/GPL3.txt") SET(INSTALLER_FILENAME "scantailor-${VERSION}-${bits}bit-install.exe") SET(REGISTER_EXTENSION_NSH "${CMAKE_CURRENT_SOURCE_DIR}/registerExtension.nsh") SET( SOURCES scantailor.nsi.in "${CMAKE_SOURCE_DIR}/cmake/generate_nsi_file.cmake.in" "${CMAKE_SOURCE_DIR}/cmake/prepare_staging_dir.cmake.in" ) SOURCE_GROUP("Sources" FILES ${SOURCES}) SOURCE_GROUP("Generated" FILES "${CMAKE_BINARY_DIR}/scantailor.nsi") CONFIGURE_FILE( "${CMAKE_SOURCE_DIR}/cmake/prepare_staging_dir.cmake.in" "${CMAKE_BINARY_DIR}/prepare_staging_dir.cmake" @ONLY ) SET(MAYBE_CRASHREPORTER_EXE "") IF(ENABLE_CRASH_REPORTER) SET(MAYBE_CRASHREPORTER_EXE "-DCRASHREPORTER_EXE=$") ENDIF() SET(MAYBE_CRT_REDIST_PATH "") IF(CRT_REDIST_PATH) SET(MAYBE_CRT_REDIST_PATH "-DCRT_REDIST_PATH=${CRT_REDIST_PATH}") ENDIF() ADD_CUSTOM_TARGET( prepare_staging_dir COMMAND "${CMAKE_COMMAND}" ARGS -DCFG=$ "-DCONF_BUILD_DIR=$" "-DSCANTAILOR_EXE=$" "-DSCANTAILOR_CLI_EXE=$" ${MAYBE_CRASHREPORTER_EXE} ${MAYBE_CRT_REDIST_PATH} -P "${CMAKE_BINARY_DIR}/prepare_staging_dir.cmake" DEPENDS "${CMAKE_SOURCE_DIR}/cmake/prepare_staging_dir.cmake.in" VERBATIM ) ADD_DEPENDENCIES( prepare_staging_dir scantailor scantailor-cli compile_translations ) IF(ENABLE_CRASH_REPORTER) ADD_DEPENDENCIES(prepare_staging_dir CrashReporter) ENDIF() SET(extra_deps scantailor compile_translations) SET(DUMP_SYMBOLS_COMMANDS "") IF(ENABLE_CRASH_REPORTER) LIST(APPEND extra_deps CrashReporter) SET( DUMP_SYMBOLS_COMMANDS COMMAND "${CMAKE_COMMAND}" ARGS -E echo "Dumping symbols..." COMMAND "${DUMP_SYMS_EXECUTABLE}" ARGS "$" ">" "${SYMBOLS_PATH}/temp.sym" COMMAND "${CMAKE_COMMAND}" ARGS "-DSYMBOLS_PATH=${SYMBOLS_PATH}" -P "${CMAKE_SOURCE_DIR}/cmake/move_sym_file.cmake" COMMAND "${DUMP_SYMS_EXECUTABLE}" ARGS "$" ">" "${SYMBOLS_PATH}/temp.sym" COMMAND "${CMAKE_COMMAND}" ARGS "-DSYMBOLS_PATH=${SYMBOLS_PATH}" -P "${CMAKE_SOURCE_DIR}/cmake/move_sym_file.cmake" ) ENDIF(ENABLE_CRASH_REPORTER) CONFIGURE_FILE( "${CMAKE_SOURCE_DIR}/cmake/generate_nsi_file.cmake.in" "${CMAKE_BINARY_DIR}/generate_nsi_file.cmake" @ONLY ) SET( scantailor_nsi_command OUTPUT "${CMAKE_BINARY_DIR}/scantailor.nsi" COMMAND "${CMAKE_COMMAND}" "-DCFG=$" ) IF(ENABLE_CRASH_REPORTER) LIST( APPEND scantailor_nsi_command "-DCRASHREPORTER_EXE=$" ) ENDIF() LIST( APPEND scantailor_nsi_command -P "${CMAKE_BINARY_DIR}/generate_nsi_file.cmake" DEPENDS scantailor.nsi.in "${CMAKE_SOURCE_DIR}/cmake/generate_nsi_file.cmake.in" VERBATIM ) ADD_CUSTOM_COMMAND(${scantailor_nsi_command}) ADD_CUSTOM_COMMAND( OUTPUT "${INSTALLER_FILENAME}" ${DUMP_SYMBOLS_COMMANDS} COMMAND "${MAKENSIS_EXE}" /V2 scantailor.nsi WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" MAIN_DEPENDENCY "${CMAKE_BINARY_DIR}/scantailor.nsi" DEPENDS ${extra_deps} VERBATIM ) OPTION(BUILD_INSTALLER_BY_DEFAULT "Wheter the \"installer\" target gets built by default" ON) SET(maybe_all "") IF(BUILD_INSTALLER_BY_DEFAULT) SET(maybe_all ALL) ENDIF() ADD_CUSTOM_TARGET( installer ${maybe_all} SOURCES ${SOURCES} DEPENDS "${INSTALLER_FILENAME}" ) ADD_DEPENDENCIES(installer prepare_staging_dir copy_to_staging_dir) scantailor-RELEASE_0_9_12_2/packaging/windows/build_deps/000077500000000000000000000000001271170121200232075ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/packaging/windows/build_deps/CMakeLists.txt000066400000000000000000000436411271170121200257570ustar00rootroot00000000000000CMAKE_MINIMUM_REQUIRED(VERSION 2.8.4) SET( CMAKE_USER_MAKE_RULES_OVERRIDE "${CMAKE_SOURCE_DIR}/../../../cmake/default_cflags.cmake" ) SET( CMAKE_USER_MAKE_RULES_OVERRIDE_CXX "${CMAKE_SOURCE_DIR}/../../../cmake/default_cxxflags.cmake" ) PROJECT("Scan Tailor Dependencies") # This forces Visual Studio 2008 SP1 to actually link to the versions of the # libraries it ships with! It's harmless on other platforms. ADD_DEFINITIONS(-D_BIND_TO_CURRENT_VCLIBS_VERSION=1) INCLUDE(../../../cmake/SetDefaultBuildType.cmake) INCLUDE(../../../cmake/SetDefaultGccFlags.cmake) INCLUDE(../../../cmake/ToNativePath.cmake) INCLUDE(../../../cmake/PatchFile.cmake) INCLUDE(TestCXXAcceptsFlag) ST_SET_DEFAULT_BUILD_TYPE(Release) ST_SET_DEFAULT_GCC_FLAGS() GET_FILENAME_COMPONENT(source_outer_dir "${PROJECT_SOURCE_DIR}/../../../.." ABSOLUTE) GET_FILENAME_COMPONENT(build_outer_dir "${PROJECT_BINARY_DIR}/.." ABSOLUTE) FILE(GLOB jpeg_dirs1 "${build_outer_dir}/jpeg-[0-9]*") FILE(GLOB jpeg_dirs2 "${source_outer_dir}/jpeg-[0-9]*") FILE(GLOB zlib_dirs1 "${build_outer_dir}/zlib-[0-9]*.[0-9]*.[0-9]*") FILE(GLOB zlib_dirs2 "${source_outer_dir}/zlib-[0-9]*.[0-9]*.[0-9]*") FILE(GLOB png_dirs1 "${build_outer_dir}/libpng-[0-9]*.[0-9]*.[0-9]*") FILE(GLOB png_dirs2 "${source_outer_dir}/libpng-[0-9]*.[0-9]*.[0-9]*") FILE(GLOB png_dirs3 "${build_outer_dir}/lpng[0-9]*") FILE(GLOB png_dirs4 "${source_outer_dir}/lpng[0-9]*") FILE(GLOB tiff_dirs1 "${build_outer_dir}/tiff-[0-9]*.[0-9]*.[0-9]*") FILE(GLOB tiff_dirs2 "${source_outer_dir}/tiff-[0-9]*.[0-9]*.[0-9]*") FILE(GLOB qt_dirs1 "${build_outer_dir}/qt-win-*-4.[0-9]*") FILE(GLOB qt_dirs2 "${build_outer_dir}/qt-everywhere-*-4.[0-9]*") FILE(GLOB qt_dirs3 "${source_outer_dir}/qt-win-*-4.[0-9]*") FILE(GLOB qt_dirs4 "${source_outer_dir}/qt-everywhere-*-4.[0-9]*") FILE(GLOB qt_dirs5 "C:/Qt/4.*.*") FILE(GLOB qt_dirs6 "C:/Qt/20*/qt") FILE(GLOB boost_dirs1 "${build_outer_dir}/boost_1_[0-9]*_[0-9]*") FILE(GLOB boost_dirs2 "${source_outer_dir}/boost_1_[0-9]*_[0-9]*") FIND_PATH( JPEG_DIR jpeglib.h HINTS ${jpeg_dirs1} ${jpeg_dirs2} DOC "Path to jpeg source directory." ) FIND_PATH( ZLIB_DIR zlib.h HINTS ${zlib_dirs1} ${zlib_dirs2} DOC "Path to zlib source directory." ) FIND_PATH( PNG_DIR png.h HINTS ${png_dirs1} ${png_dirs2} ${png_dirs3} ${png_dirs4} DOC "Path to libpng source directory." ) FIND_PATH( TIFF_DIR libtiff/tiff.h HINTS ${tiff_dirs1} ${tiff_dirs2} DOC "Path to top-level tiff source directory." ) FIND_PATH( QT_DIR projects.pro HINTS ${qt_dirs1} ${qt_dirs2} ${qt_dirs3} ${qt_dirs4} ${qt_dirs5} ${qt_dirs6} DOC "Path to top-level Qt source directory." ) FIND_PATH( BOOST_DIR boost/foreach.hpp HINTS ${boost_dirs1} ${boost_dirs2} DOC "Path to top-level Boost source directory." ) IF(NOT JPEG_DIR) MESSAGE(FATAL_ERROR "JPEG source directory not found. You may specify it manually.") ELSEIF(NOT ZLIB_DIR) MESSAGE(FATAL_ERROR "ZLIB source directory not found. You may specify it manually.") ELSEIF(NOT PNG_DIR) MESSAGE(FATAL_ERROR "LibPNG source directory not found. You may specify it manually.") ELSEIF(NOT TIFF_DIR) MESSAGE(FATAL_ERROR "TIFF source directory not found. You may specify it manually.") ELSEIF(NOT QT_DIR) MESSAGE(FATAL_ERROR "Qt source directory not found. You may specify it manually.") ELSEIF(NOT BOOST_DIR) MESSAGE(FATAL_ERROR "Boost source directory not found. You may specify it manually.") ENDIF() IF(MINGW) SET(ZLIB_LIBRARY_NAME z) SET(PNG_LIBRARY_NAME png) SET(JPEG_LIBRARY_NAME jpeg) SET(TIFF_LIBRARY_NAME tiff) ELSE() SET(ZLIB_LIBRARY_NAME zdll) SET(PNG_LIBRARY_NAME libpng) SET(JPEG_LIBRARY_NAME libjpeg) SET(TIFF_LIBRARY_NAME libtiff) ENDIF() MACRO(LIST_ITEMS_PREPEND LIST PREFIX) SET(tmp_list_) FOREACH(item ${${LIST}}) LIST(APPEND tmp_list_ "${PREFIX}${item}") ENDFOREACH(item) SET(${LIST} ${tmp_list_}) ENDMACRO(LIST_ITEMS_PREPEND) #=================================== JPEG ===================================# # Patch jmorecfg.h to: # 1. Prevent double definition of INT32. # 2. Build a DLL rather than a static library. IF(NOT EXISTS "${JPEG_DIR}/jmorecfg.h.orig") FILE(READ "${JPEG_DIR}/jmorecfg.h" jmorecfg_h_orig) STRING(REPLACE "XMD_H" "_BASETSD_H" jmorecfg_h "${jmorecfg_h_orig}") STRING( REGEX REPLACE "#define[ \t]+GLOBAL\\(type\\)[^\n]*" "#ifdef JPEG_BUILD\n#define GLOBAL(type) __declspec(dllexport) type\n#else\n#define GLOBAL(type) __declspec(dllimport) type\n#endif" jmorecfg_h "${jmorecfg_h}" ) STRING( REGEX REPLACE "#define[ \t]+EXTERN\\(type\\)[^\n]*" "#ifdef JPEG_BUILD\n#define EXTERN(type) extern __declspec(dllexport) type\n#else\n#define EXTERN(type) extern __declspec(dllimport) type\n#endif" jmorecfg_h "${jmorecfg_h}" ) FILE(WRITE "${JPEG_DIR}/jmorecfg.h" "${jmorecfg_h}") FILE(WRITE "${JPEG_DIR}/jmorecfg.h.orig" "${jmorecfg_h_orig}") SET(jmorecfg_h "") SET(jmorecfg_h_orig "") ENDIF(NOT EXISTS "${JPEG_DIR}/jmorecfg.h.orig") SET( libjpeg_sources jaricom.c jcapimin.c jcapistd.c jcarith.c jccoefct.c jccolor.c jcdctmgr.c jchuff.c jcinit.c jcmainct.c jcmarker.c jcmaster.c jcomapi.c jcparam.c jcprepct.c jcsample.c jctrans.c jdapimin.c jdapistd.c jdarith.c jdatadst.c jdatasrc.c jdcoefct.c jdcolor.c jddctmgr.c jdhuff.c jdinput.c jdmainct.c jdmarker.c jdmaster.c jdmerge.c jdpostct.c jdsample.c jdtrans.c jerror.c jfdctflt.c jfdctfst.c jfdctint.c jidctflt.c jidctfst.c jidctint.c jquant1.c jquant2.c jutils.c jmemmgr.c jmemnobs.c ) LIST_ITEMS_PREPEND(libjpeg_sources "${JPEG_DIR}/") CONFIGURE_FILE("${JPEG_DIR}/jconfig.vc" "${JPEG_DIR}/jconfig.h" COPYONLY) ADD_LIBRARY(${JPEG_LIBRARY_NAME} SHARED ${libjpeg_sources}) SET_TARGET_PROPERTIES( ${JPEG_LIBRARY_NAME} PROPERTIES DEFINE_SYMBOL JPEG_BUILD ARCHIVE_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/staging/libs" ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/staging/libs" ARCHIVE_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}/staging/libs" ARCHIVE_OUTPUT_DIRECTORY_MINSIZEREL "${CMAKE_BINARY_DIR}/staging/libs" RUNTIME_OUTPUT_DIRECTORY_DEBUG "${QT_DIR}/bin" RUNTIME_OUTPUT_DIRECTORY_RELEASE "${QT_DIR}/bin" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${QT_DIR}/bin" RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL "${QT_DIR}/bin" ) #=================================== ZLIB ===================================# SET( zlib_sources adler32.c compress.c crc32.c deflate.c inffast.c inflate.c inftrees.c trees.c uncompr.c zutil.c ) LIST_ITEMS_PREPEND(zlib_sources "${ZLIB_DIR}/") ADD_LIBRARY(${ZLIB_LIBRARY_NAME} SHARED ${zlib_sources}) SET_TARGET_PROPERTIES( ${ZLIB_LIBRARY_NAME} PROPERTIES DEFINE_SYMBOL ZLIB_DLL ARCHIVE_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/staging/libs" ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/staging/libs" ARCHIVE_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}/staging/libs" ARCHIVE_OUTPUT_DIRECTORY_MINSIZEREL "${CMAKE_BINARY_DIR}/staging/libs" RUNTIME_OUTPUT_DIRECTORY_DEBUG "${QT_DIR}/bin" RUNTIME_OUTPUT_DIRECTORY_RELEASE "${QT_DIR}/bin" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${QT_DIR}/bin" RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL "${QT_DIR}/bin" ) #================================== LIBPNG ==================================# IF(NOT EXISTS "${PNG_DIR}/pnglibconf.h") IF(EXISTS "${PNG_DIR}/scripts/pnglibconf.h.prebuilt") CONFIGURE_FILE( "${PNG_DIR}/scripts/pnglibconf.h.prebuilt" "${PNG_DIR}/pnglibconf.h" @COPYONLY ) ENDIF() ENDIF() INCLUDE_DIRECTORIES(${ZLIB_DIR}) SET( libpng_sources png.c pngset.c pngget.c pngrutil.c pngtrans.c pngwutil.c pngread.c pngrio.c pngwio.c pngwrite.c pngrtran.c pngwtran.c pngmem.c pngerror.c pngpread.c ) LIST_ITEMS_PREPEND(libpng_sources "${PNG_DIR}/") ADD_DEFINITIONS(-DPNG_NO_MODULEDEF) ADD_LIBRARY(${PNG_LIBRARY_NAME} SHARED ${libpng_sources}) TARGET_LINK_LIBRARIES(${PNG_LIBRARY_NAME} ${ZLIB_LIBRARY_NAME}) SET_TARGET_PROPERTIES( ${PNG_LIBRARY_NAME} PROPERTIES DEFINE_SYMBOL PNG_BUILD_DLL ARCHIVE_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/staging/libs" ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/staging/libs" ARCHIVE_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}/staging/libs" ARCHIVE_OUTPUT_DIRECTORY_MINSIZEREL "${CMAKE_BINARY_DIR}/staging/libs" RUNTIME_OUTPUT_DIRECTORY_DEBUG "${QT_DIR}/bin" RUNTIME_OUTPUT_DIRECTORY_RELEASE "${QT_DIR}/bin" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${QT_DIR}/bin" RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL "${QT_DIR}/bin" ) #=================================== TIFF ===================================# INCLUDE_DIRECTORIES("${JPEG_DIR}") # ZLIB_DIR already included above SET( libtiff_sources tif_aux.c tif_close.c tif_codec.c tif_color.c tif_compress.c tif_dir.c tif_dirinfo.c tif_dirread.c tif_dirwrite.c tif_dumpmode.c tif_error.c tif_extension.c tif_fax3.c tif_fax3sm.c tif_getimage.c tif_jpeg.c tif_ojpeg.c tif_flush.c tif_luv.c tif_lzw.c tif_next.c tif_open.c tif_packbits.c tif_pixarlog.c tif_predict.c tif_print.c tif_read.c tif_swab.c tif_strip.c tif_thunder.c tif_tile.c tif_version.c tif_warning.c tif_write.c tif_zip.c tif_unix.c libtiff.def ) LIST_ITEMS_PREPEND(libtiff_sources "${TIFF_DIR}/libtiff/") SET(tiff_vc_config "${TIFF_DIR}/libtiff/tif_config.vc.h") IF(EXISTS "${TIFF_DIR}/libtiff/tif_config.h.vc") SET(tiff_vc_config "${TIFF_DIR}/libtiff/tif_config.h.vc") ENDIF(EXISTS "${TIFF_DIR}/libtiff/tif_config.h.vc") CONFIGURE_FILE( "${tiff_vc_config}" "${TIFF_DIR}/libtiff/tif_config.h" @COPYONLY ) IF(EXISTS "${TIFF_DIR}/libtiff/tiffconf.vc.h") CONFIGURE_FILE( "${TIFF_DIR}/libtiff/tiffconf.vc.h" "${TIFF_DIR}/libtiff/tiffconf.h" @COPYONLY ) ENDIF(EXISTS "${TIFF_DIR}/libtiff/tiffconf.vc.h") ADD_LIBRARY(${TIFF_LIBRARY_NAME} SHARED ${libtiff_sources}) TARGET_LINK_LIBRARIES( ${TIFF_LIBRARY_NAME} ${PNG_LIBRARY_NAME} ${JPEG_LIBRARY_NAME} ${ZLIB_LIBRARY_NAME} ) SET_TARGET_PROPERTIES( ${TIFF_LIBRARY_NAME} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/staging/libs" ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/staging/libs" ARCHIVE_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}/staging/libs" ARCHIVE_OUTPUT_DIRECTORY_MINSIZEREL "${CMAKE_BINARY_DIR}/staging/libs" RUNTIME_OUTPUT_DIRECTORY_DEBUG "${QT_DIR}/bin" RUNTIME_OUTPUT_DIRECTORY_RELEASE "${QT_DIR}/bin" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${QT_DIR}/bin" RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL "${QT_DIR}/bin" ) #================================= Boost ================================# IF(MINGW) SET(boost_toolset gcc) ELSEIF(MSVC) SET(boost_toolset msvc) ELSE(MINGW) MESSAGE(FATAL_ERROR "Unsupported platform. MinGW and MSVC are supported.") ENDIF(MINGW) SET(boost_64bit_flags "") IF(CMAKE_SIZEOF_VOID_P EQUAL 8) LIST(APPEND boost_64bit_flags "address-model=64") ENDIF() ADD_CUSTOM_TARGET( boost ALL COMMAND bootstrap && b2 --with-test toolset=${boost_toolset} threading=multi link=static variant=debug,release ${boost_64bit_flags} define=_BIND_TO_CURRENT_VCLIBS_VERSION=1 stage WORKING_DIRECTORY "${BOOST_DIR}" VERBATIM ) #=================================== Qt ===================================# # Backward compatibility. We used to patch projects.pro, but now we achieve # the same result by other means. IF(EXISTS "${QT_DIR}/projects.pro.orig") CONFIGURE_FILE( "${QT_DIR}/projects.pro.orig" "${QT_DIR}/projects.pro" COPYONLY ) FILE(REMOVE "${QT_DIR}/projects.pro.orig") ENDIF(EXISTS "${QT_DIR}/projects.pro.orig") TO_NATIVE_PATH("${JPEG_DIR}" JPEG_INCLUDE_DIR) TO_NATIVE_PATH("${ZLIB_DIR}" ZLIB_INCLUDE_DIR) TO_NATIVE_PATH("${PNG_DIR}" PNG_INCLUDE_DIR) TO_NATIVE_PATH("${QT_DIR}" QT_DIR_NATIVE) # Find all *.conf files under mkspecs that mention -Zc:wchar_t- and remove # that minus at the end. That's necessary to make Qt compatible with other # libraries that use wchar_t stuff. FILE(GLOB_RECURSE conf_files "${QT_DIR}/mkspecs/*.conf") FOREACH(conf_file ${conf_files}) FILE(READ "${conf_file}" contents) STRING(REGEX REPLACE "-Zc:wchar_t-" "-Zc:wchar_t" new_contents "${contents}") IF(NOT "${contents}" STREQUAL "${new_contents}") # Make a backup copy, if not already there. IF(NOT EXISTS "${conf_file}.orig") CONFIGURE_FILE("${conf_file}" "${conf_file}.orig" COPYONLY) ENDIF() FILE(WRITE "${conf_file}" "${new_contents}") ENDIF() ENDFOREACH() SET(qt_maybe_skip_tools "") IF(EXISTS "${QT_DIR}/bin/designer.exe") # Either the tools have already been built, or they came prepackaged. SET(qt_maybe_skip_tools "-nomake tools") ENDIF(EXISTS "${QT_DIR}/bin/designer.exe") IF(MINGW) SET(platform win32-g++) SET(make_command "mingw32-make -j$ENV{NUMBER_OF_PROCESSORS}") ELSEIF(MSVC) #MESSAGE("MSVC=${MSVC} | MSVC10=${MSVC10} | MSVC80=${MSVC80}" ) SET(make_command nmake) IF(MSVC60 OR MSVC70) MESSAGE(FATAL_ERROR "This Visual Studio version is too old and is not supported") ELSEIF(MSVC71) SET(platform win32-msvc2003) ELSEIF(MSVC80) SET(platform win32-msvc2005) SET(force_frame_pointer TRUE) ELSEIF(MSVC90) SET(platform win32-msvc2008) SET(force_frame_pointer TRUE) ELSE() # Visual Studio 2010 or above. SET(platform win32-msvc2010) SET(force_frame_pointer TRUE) ENDIF() ELSE(MINGW) MESSAGE(FATAL_ERROR "Unsupported platform.") ENDIF(MINGW) IF(MINGW) # Figure out the correct values for -*-libgcc and -*-libstdc++ # An empty value doesn't mean it's incorrect. SET(static_shared_libgcc "") SET(static_shared_libstdcpp "") STRING(REGEX MATCH "-(static|shared)-libgcc" static_shared_libgcc "${CMAKE_CXX_FLAGS}") STRING(REGEX MATCH "-(static|shared)-libstdc\\+\\+" static_shared_libstdcpp "${CMAKE_CXX_FLAGS}") ENDIF() # Patch qmake.conf SET(CHANGES "") IF(MINGW) # Set correct -*-libgcc and -*-libstdc++ flags. LIST( APPEND CHANGES # Get rid of existing flags, if any. "/-(static|shared)-lib(gcc|stdc\\+\\+)//" # Add the flags we want. "/(QMAKE_LFLAGS[ \t]*=)[ \t]*/\\1 ${static_shared_libgcc} ${static_shared_libstdcpp} /" ) IF (CMAKE_C_FLAGS MATCHES ".*-fno-keep-inline-dllexport.*") # This option is needed to prevent out-of-memory situations when linking large DLLs. LIST( APPEND CHANGES # Get rid of the existing flag, if present. "/-f(no)?-keep-inline-dllexport//" # Add the flag to QMAKE_CFLAGS and QMAKE_LFLAGS. "/(QMAKE_(C|L)FLAGS[ \t]*=)[ \t]*/\\1 -fno-keep-inline-dllexport /" ) ENDIF() ELSEIF(MSVC AND force_frame_pointer) # Disable frame pointer ommission in release mode to ensure # good stack traces from our built-in crash reporter. LIST( APPEND CHANGES # Add the flag. "/(QMAKE_CFLAGS_RELEASE[ \t]*=)/\\1 -Oy-/" # Get rid of duplicates, in case it was already there. "/( -Oy-)+/ -Oy-/" ) ENDIF() PATCH_FILE("${QT_DIR}/mkspecs/${platform}/qmake.conf" ${CHANGES}) # We also force debugging symbols even for release builds. PATCH_FILE( "${QT_DIR}/mkspecs/features/release.prf" # Clone *FLAGS_RELEASE into *FLAGS_RELEASE_WITH_DEBUGINFO "/(\\$\\$QMAKE_(C|CXX|L)FLAGS_RELEASE)([ \t\r\n]|$)/\\1_WITH_DEBUGINFO\\3/" ) # Set correct -*-libgcc and -*-libstdc++ flags. IF(MINGW) FOREACH(file_ Makefile.win32-g++ Makefile.win32-g++-sh) SET(conf_file_ "${QT_DIR}/qmake/${file_}") PATCH_FILE( "${conf_file_}" # Get rid of existing flags, if any. "/-(static|shared)-lib(gcc|stdc\\+\\+)//" # Add the flags we want. "/(LFLAGS[ \t]*=)[ \t]*/\\1 ${static_shared_libgcc} ${static_shared_libstdcpp} /" ) ENDFOREACH() ENDIF() SET(qt_build_script "${CMAKE_BINARY_DIR}/build-qt.bat") ADD_CUSTOM_COMMAND( OUTPUT "${qt_build_script}" COMMAND "${CMAKE_COMMAND}" "-DPROJECT_ROOT=${CMAKE_SOURCE_DIR}/../../.." "-DTARGET_FILE=${qt_build_script}" "-DQT_DIR=${QT_DIR}" "-DPLATFORM=${platform}" "-DMAYBE_SKIP_TOOLS=${qt_maybe_skip_tools}" "-DMAKE_COMMAND=${make_command}" "-DJPEG_INCLUDE_DIR=${JPEG_INCLUDE_DIR}" "-DZLIB_INCLUDE_DIR=${ZLIB_INCLUDE_DIR}" "-DPNG_INCLUDE_DIR=${PNG_INCLUDE_DIR}" "-DJPEG_LINK_DIR=$" "-DZLIB_LINK_DIR=$" "-DPNG_LINK_DIR=$" -P "${CMAKE_CURRENT_SOURCE_DIR}/generate_qt_build_script.cmake" DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/generate_qt_build_script.cmake" VERBATIM ) ADD_CUSTOM_TARGET( Qt ALL COMMAND "${qt_build_script}" DEPENDS "${qt_build_script}" WORKING_DIRECTORY "${QT_DIR}" ) # boost added so that boost is built before Qt. That's helpful if build # errors occur, as rebuilding boost is much faster than rebuilding Qt. ADD_DEPENDENCIES( Qt ${ZLIB_LIBRARY_NAME} ${JPEG_LIBRARY_NAME} ${PNG_LIBRARY_NAME} ${TIFF_LIBRARY_NAME} ) # If you have multiple versions of Qt installed, this script # can easily pick the wrong one. MESSAGE(STATUS "----------------------------") MESSAGE(STATUS "QT_DIR is ${QT_DIR}") MESSAGE(STATUS "Check it's the one you want.") MESSAGE(STATUS "----------------------------") # Dump symbols of all DLLs we are going to link to. OPTION(DUMP_DLL_SYMBOLS "Enable if you are going to build crash reporter." OFF) IF(DUMP_DLL_SYMBOLS) FIND_PATH( SYMBOLS_PATH . PATHS "${build_outer_dir}/symbols" "${source_outer_dir}/symbols" NO_DEFAULT_PATH DOC "The directory to write symbol information into." ) IF(NOT SYMBOLS_PATH) MESSAGE(FATAL_ERROR "SYMBOLS_PATH directory is not set.") ENDIF(NOT SYMBOLS_PATH) # We can't build it, because it requires ATL, which is not part # of the Visual Studio Express Edition, so we rely on a pre-built # version which can be found in the Mozilla repository. FIND_PROGRAM( DUMP_SYMS_EXECUTABLE dump_syms PATHS "${build_outer_dir}" "${source_outer_dir}" DOC "Path to dump_syms.exe, which can be found in Mozilla repository." ) IF(NOT DUMP_SYMS_EXECUTABLE) MESSAGE( FATAL_ERROR "dump_syms.exe wasn't found. Specify its location manually by setting the DUMP_SYMS_EXECUTABLE variable. dump_syms.exe may be found in the Mozilla repository under /toolkit/crashreporter/tools/win32" ) ENDIF(NOT DUMP_SYMS_EXECUTABLE) ADD_CUSTOM_TARGET( dump_dll_symbols ALL COMMAND "${CMAKE_COMMAND}" "-DSYMBOLS_PATH=${SYMBOLS_PATH}" "-DDUMP_SYMS_EXECUTABLE=${DUMP_SYMS_EXECUTABLE}" "-DMOVE_SYMBOLS_SCRIPT=${CMAKE_CURRENT_SOURCE_DIR}/../../../cmake/move_sym_file.cmake" -P "${CMAKE_CURRENT_SOURCE_DIR}/dump_dll_syms.cmake" DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/dump_dll_syms.cmake" WORKING_DIRECTORY "${QT_DIR}/bin" VERBATIM ) # Qt depends on the rest of them. ADD_DEPENDENCIES(dump_dll_symbols Qt) ENDIF() scantailor-RELEASE_0_9_12_2/packaging/windows/build_deps/dump_dll_syms.cmake000066400000000000000000000006371271170121200270720ustar00rootroot00000000000000# We are operating on files from the current directory. FILE(GLOB DLL_FILES "*.dll" "*.DLL") FOREACH(dll_file ${DLL_FILES}) MESSAGE(STATUS "Dumping symbols from ${dll_file}") EXECUTE_PROCESS( COMMAND "${DUMP_SYMS_EXECUTABLE}" "${dll_file}" OUTPUT_FILE "${SYMBOLS_PATH}/temp.sym" ) EXECUTE_PROCESS( COMMAND "${CMAKE_COMMAND}" "-DSYMBOLS_PATH=${SYMBOLS_PATH}" -P "${MOVE_SYMBOLS_SCRIPT}" ) ENDFOREACH() scantailor-RELEASE_0_9_12_2/packaging/windows/build_deps/generate_qt_build_script.cmake000066400000000000000000000013461271170121200312560ustar00rootroot00000000000000FILE( WRITE "${TARGET_FILE}" "configure -platform ${PLATFORM} -debug-and-release -shared -fast" " -no-gif -system-zlib -system-libpng -no-libmng -no-libtiff -system-libjpeg" " -no-openssl -no-qt3support -no-style-plastique -no-style-cleanlooks" " -no-style-motif -no-style-cde -qt-style-windowsxp -qt-style-windowsvista" " -no-phonon -no-webkit -no-script -no-scripttools -no-declarative" " -nomake examples -nomake demos -nomake docs" " ${MAYBE_SKIP_TOOLS}" " -opensource -confirm-license -no-ltcg" " -I \"${JPEG_INCLUDE_DIR}\" -I \"${ZLIB_INCLUDE_DIR}\"" " -I \"${PNG_INCLUDE_DIR}\" -L \"${JPEG_LINK_DIR}\" -L \"${ZLIB_LINK_DIR}\"" " -L \"${PNG_LINK_DIR}\"" " -D _BIND_TO_CURRENT_VCLIBS_VERSION=1" "\n" "${MAKE_COMMAND}\n" ) scantailor-RELEASE_0_9_12_2/packaging/windows/patch_libtiff/000077500000000000000000000000001271170121200236735ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/packaging/windows/patch_libtiff/CMakeLists.txt000066400000000000000000000016231271170121200264350ustar00rootroot00000000000000CMAKE_MINIMUM_REQUIRED(VERSION 2.6.0) PROJECT(patch_libtiff) GET_FILENAME_COMPONENT(outer_dir "${PROJECT_SOURCE_DIR}/../../../.." ABSOLUTE) FILE(GLOB tiff_dirs "${outer_dir}/tiff-[0-9]*.[0-9]*.[0-9]*") FIND_PATH( TIFF_DIR libtiff/tiff.h PATHS ${tiff_dirs} DOC "Path to top-level tiff source directory." ) FIND_PROGRAM( PATCH_EXE patch PATHS "[HKEY_LOCAL_MACHINE\\SOFTWARE\\GnuWin32;InstallPath]/bin" DOC "Full path to patch.exe" ) IF(NOT TIFF_DIR) MESSAGE(FATAL_ERROR "TIFF source directory not found. You may specify it manually.") ELSEIF(NOT PATCH_EXE) MESSAGE(FATAL_ERROR "The patch utility wasn't found.") ENDIF(NOT TIFF_DIR) CONFIGURE_FILE( apply_individual_patches.cmake.in "${CMAKE_BINARY_DIR}/apply_individual_patches.cmake" @ONLY ) ADD_CUSTOM_TARGET( apply_patches ALL COMMAND "${CMAKE_COMMAND}" -P "${CMAKE_BINARY_DIR}/apply_individual_patches.cmake" WORKING_DIRECTORY "${TIFF_DIR}" ) scantailor-RELEASE_0_9_12_2/packaging/windows/patch_libtiff/apply_individual_patches.cmake.in000066400000000000000000000004001271170121200323400ustar00rootroot00000000000000FILE(STRINGS "@TIFF_DIR@/debian/patches/series" patch_list) FOREACH(patch_file ${patch_list}) EXECUTE_PROCESS( COMMAND "@PATCH_EXE@" -p1 --binary -i "@TIFF_DIR@/debian/patches/${patch_file}" WORKING_DIRECTORY "@TIFF_DIR@" ) ENDFOREACH(patch_file) scantailor-RELEASE_0_9_12_2/packaging/windows/readme.en.txt000066400000000000000000000250201271170121200234730ustar00rootroot00000000000000This document describes building the Windows version of Scan Tailor. Both Visual C++ (part of Visual Studio) and MinGW compilers are supported. Theoretically, any version of Visual C++ starting from Visual C++ 2003 should work. In practice, Visual C++ 2008 Express Edition is known to work and no others were tested. As for MinGW, the version shipped with Qt should work fine for 32-bit builds, while the 64-bit edition of TDM-GCC was used for 64-bit builds. The official 32-bit builds are made with Visual C++ with the main reason being the built-in crash reporter only supporting Visual C++ under Windows. For unofficial builds the crash reporter is useless and should not be enabled, because only the person who built the particular version will have symbolic data used to transform the crash report to a human readable form. For 64-bit builds only MinGW is currently supported, so these go without the crash reporter. If in doubt over which compiler to use, go with Visual C++ 2008 Express Edition. That's the one used most heavily by the author, so you are least likely to run into problems when building. Initially this document was only covering 32-bit MinGW. The bits related to Visual C++ and 64-bit MinGW were added later and marked with [VC++] and [MinGW64] respectively. When we talk about MinGW not mentioning if it's a 32-bit or a 64-bit version, it applies to both. Downloading Prerequisites First, download the following software. Unless stated otherwise, take the latest stable version. 1. CMake >= 2.8.4 Homepage: http://www.cmake.org Warning! Don't install CMake into "C:\Program Files (x86)\...", which is the default installation path on 64-bit versions of Windows. Some versions of MinGW have problems with parentheses in paths. 2. jpeg library Homepage: http://www.ijg.org/ The file we need will be named jpegsrc.v7.tar.gz or similarly. 3. zlib Homepage: http://www.zlib.net/ We need a file named like zlib-x.x.x.tar.gz, where x.x.x represents the version number. 4. libpng Homepage: http://www.libpng.org/pub/png/libpng.html We need a file named like libpng-x.x.x.tar.gz, where x.x.x represents the version number. 5. libtiff Homepage: http://www.remotesensing.org/libtiff/ Because libtiff is updated rarely, but vulnerabilities in it are found often, it's better to patch it right away. In that case, take it from here: http://packages.debian.org/source/sid/tiff There you will find both the original libtiff and a set of patches for it. The process of patching libtiff is described later in this document. If you aren't going to distribute your Scan Tailor build and aren't going to open files from untrusted sources, then you don't really need patching it. 6. Qt 4.x.x (tested with 4.7.4) Homepage: http://qt.nokia.com/ From there: Download -> LGPL / Free -> Download Qt SDK for Windows Tou may turn off the "MinGW" option in the installer if you are going to use Visual Studio or a 64-bit version of MinGW. 7. Boost (tested with 1.47.0) Homepage: http://boost.org/ You can download it in any file format, provided you know how to unpack it. 8. NSIS 2.x (tested with 2.42) Homepage: http://nsis.sourceforge.net/ 9. [VC++] You would need the Visual C++ itself, which you can download from here: http://www.microsoft.com/Express/vc/ Don't go for the latest version - go specifically with 2008 Express Edition. 10.[MinGW64] Download and install the 64-bit edition of TDM-GCC from http://tdm-gcc.tdragon.net/ If you've got another version of MinGW already installed, make sure it's not in PATH. Instructions 1. Create a build directory. Its full path should have no spaces. I suggest C:\build 2. Unpack jpeg, libpng, libtiff, zlib, boost, boost jam, and scantailor itself to the build directory. You should get a directory structure like this: C:\build | boost_1_47_0 | jpeg-8c | libpng-1.2.31 | scantailor-0.9.0 | tiff-3.8.2 | zlib-1.2.3 If you took a Qt version without an installer, unpack it here as well. If you don't know how to unpack .tar.gz files, I suggest this tool: http://www.7-zip.org/ 3. Create two more subdirectories there: | scantailor-build | scantailor-deps-build 4. If you took Qt with installer, install it now, telling it to download and install MinGW as well. Otherwise, download the MinGW installer from http://mingw.sourceforge.net/ and install MinGW. 5. Install CMake. 6. [VC++] Just skip this step. Now we need to make sure CMake sees the MinGW bin directory in PATH. There are two alternative ways to achieve that: 1. Go to Control Panel (-> Performance and Maintenance) -> System -> Advanced -> Environment Variables and add something like ";C:\MinGW\bin" (without quotes) to the end of the Path variable. 2. You may just launch CMake from the Qt Command Prompt, which takes care about adjusting the PATH variable. Unfortunately, in this case you will have to write the full path to the CMake executable, like: C:\Program Files\CMake 2.8\bin\cmake-gui.exe Using just cmake-gui.exe won't work even if you've chosen to add CMake to PATH when installing it. That's because Qt Command Prompt Overwrites the PATH variable completely. Of course you can edit the qtvars.bat file and put the CMake directory to PATH there. 6. Launch CMake and specify the following: Source directory: C:\build\scantailor-0.9.0\packaging\windows\build-deps Binary directory: C:\build\scantailor-deps-build Click "Configure". Select the project type "MinGW Makefiles". If any paths were not found, enter them manually, then click "Configure" again. If everything went right, the "Generate" button will become clickable. Click it. Sometimes it's necessary to click "Configure" more than once before "Generate" becomes clickable. [VC++] Select your version of Visual C++ (Visual Studio) instead of "MinGW Makefiles" 7. We will be building Scan Tailor's dependencies here. This step is the longest one (may take a few hours), but fortunately it only has to be done once. When building newer versions of Scan Tailor, you won't need to redo this step. [VC++] Go to C:\build\scantailor-deps-build and open file "Scan Tailor Dependencies.sln". It will open in Visual Studio. Select The desired build type (Release, Debug, MinSizeRel, RelWithDebInfo) and do Build -> Build Solution. If you don't know which build type to choose, go with Release. Make sure the building process finishes without errors. Warnings may be ignored. [MinGW] Launch the Qt Command Promt from the Start menu. Enter the following there: C: cd C:\build\scantailor-deps-build mingw32-make 2> log.txt The "2> log.txt" part will write errors to a file rather than to the command prompt window. That's useful for figuring out what went wrong. When this step completes, check the log.txt file to make sure nothing went wrong. You would have errors at the end of that file in that case. Warnings may be ignored. 8. Launch CMake again and specify following: Source directory: C:\build\scantailor-0.9.0 Binary directory: C:\build\scantailor-build Click "Configure". Select the project type "MinGW Makefiles". If any paths were not found, enter them manually, then click "Configure" again. If everything went right, the "Generate" button will become clickable. Click it. Sometimes it's necessary to click "Configure" more than once before "Generate" becomes clickable. [VC++] Select your version of Visual C++ (Visual Studio) instead of "MinGW Makefiles" 9. Now we are going to build Scan Tailor itself. On subsequent build of the same (possiblity modified) version, you can start right from this step. For building a different version, start from step 8. [VC++] Go to C:\build\scantailor-build and open file "Scan Tailor.sln". It will open in Visual Studio. Select the same build type as on step 7, then do Build -> Build Solution. [MinGW] Back to the Qt Command Promt, give the following commands: C: cd C:\build\scantailor-build mingw32-make 2> log.txt If everything went right, the installer named scantailor-VERSION-install.exe will appear in the current directory. The VERSION part of the name will be replaced by the actual version, taken from a file called "VERSION" in the root of the source directory. Patching libtiff These instructions assume you've got Debian's patches for libtiff from: http://packages.debian.org/source/sid/tiff There you will find both the original libtiff sources (filename like tiff_3.9.4.orig.tar.gz) and a patch set for it (filename like tiff_3.9.4-5.debian.tar.gz). Download both and follow the instructions: 1. Get the command line patch utility from here: http://gnuwin32.sourceforge.net/packages/patch.htm Better use the version with the installer. In that case CMake will find the location of patch.exe by itself. 2. Extract the original libtiff sources into C:\build to get a directory structure like this: C:\build | tiff-3.9.4 +-- build | config | contrib | ... Then extract the patchset inside the tiff directory, to get the "debian" directory on the same level as "build", "config" and "contrib". 3. Create another subdirectory under C:\build Call it "tiff-patch-dir". 4. Launch CMake and specify the following: Source directory: C:\build\scantailor-0.9.0\packaging\windows\patch_libtiff Binary directory: C:\build\tiff-patch-dir Click "Configure". Select the project type "MinGW Makefiles". If any paths were not found, enter them manually, then click "Configure" again. If everything went right, the "Generate" button will become clickable. Click it. Sometimes it's necessary to click "Configure" more than once before "Generate" becomes clickable. [VC++] Select your version of Visual C++ (Visual Studio) instead of "MinGW Makefiles" 5. [VC++] Go to C:\build\tiff-patch-dir and open file "patch_libtiff.sln". It will open in Visual Studio. From there, do Build -> Build Solution. [MinGW] Do the following from the Qt Command Prompt: C: cd C:\build cd tiff-patch-dir mingw32-make If no errors were reported, you have successfully patched your libtiff. If you ever need to patch it again, first revert it to the original version, the one from the .tar.gz file and delete the "debian" subdirectory. scantailor-RELEASE_0_9_12_2/packaging/windows/readme.ru.txt000066400000000000000000000375501271170121200235320ustar00rootroot00000000000000This file is in UTF-8 encoding. Этот документ опиÑывает процеÑÑ Ñборки Scan Tailor под Windows. ПоддерживаютÑÑ ÐºÐ¾Ð¼Ð¿Ð¸Ð»Ñторы Visual C++ (входит в ÑоÑтав Visual Studio) и MinGW. ТеоретичеÑки, Ð»ÑŽÐ±Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ Visual C++ Ð½Ð°Ñ‡Ð¸Ð½Ð°Ñ Ñ Visual C++ 2003 должна работать. ПрактичеÑки, Visual C++ 2008 Express Edition (беÑплатнаÑ) точно работает, а никакие другие верÑии не теÑтировалиÑÑŒ. Что каÑаетÑÑ MinGW, то Ð´Ð»Ñ 32-битных Ñборок теÑтировалаÑÑŒ та верÑиÑ, что идет в комлекте Ñ Qt, а Ð´Ð»Ñ 64-битных - TDM-GCC в 64-битной конфигурации. Оффициальные 32-битные Ñборки ÑобираютÑÑ Visual C++, так как вÑтроенный обработчик падений поддерживает только Visual C++ под Windows. Ð”Ð»Ñ Ð½ÐµÐ¾Ñ„Ñ„Ð¸Ñ†Ð¸Ð°Ð»ÑŒÐ½Ñ‹Ñ… Ñборок обработчик падений беÑполезен, и включать его не надо, потому как только тот, кто Ñобрал данную Ñборку имеет Ñимвольные данные, необходимые Ð´Ð»Ñ Ñ‚Ð¾Ð³Ð¾, чтобы перевеÑти отчет о падении в читабельную форму. Ð”Ð»Ñ 64-битных Ñборок на данный момент поддерживаетÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ MinGW, и как ÑледÑтвие Ñта верÑÐ¸Ñ Ð¸Ð´ÐµÑ‚ без обработчика падений. ЕÑли не знаете, какой компилÑтор иÑпользовать - иÑпользуйте Visual C++ 2008 Express Edition, так как именно его автор иÑпользует больше вÑего, и ÑоответÑтвенно Ñ Ð½Ð¸Ð¼ меньше вÑего шанÑов получить проблемы при Ñборки. Изначально Ñтот документ пиÑалÑÑ Ð² раÑчете только на 32-битный MinGW. ДополнениÑ, каÑающиеÑÑ Visual C++ и 64-битного MinGW были добавлены позже и помечены как [VC++] и [MinGW64] ÑоответÑтвенно. Когда речь идет о MinGW без ÑƒÐºÐ°Ð·Ð°Ð½Ð¸Ñ Ð±Ð¸Ñ‚Ð½Ð¾Ñти, Ñказанное отноÑитÑÑ ÐºÐ°Ðº к 32-битной, так и к 64-битной верÑии. Скачиваем необходимый Ñофт Первым делом, нам понадобитÑÑ Ð½Ð¸Ð¶ÐµÑледующий Ñофт. ЕÑли не указано обратного, вÑегда берите поÑледние Ñтабильные верÑии. 1. CMake >= 2.8.4 Сайт: http://www.cmake.org Внимание! Ðе уÑтанавливайте CMake в "C:\Program Files (x86)\...", что будет значением по умолчанию на 64Ñ…-битных верÑиÑÑ… Windows. Ðекоторые верÑии MinGW имеют проблемы Ñо Ñкобками в путÑÑ…. 2. jpeg library Сайт: http://www.ijg.org/ Ðам нужен файл jpegsrc.v7.tar.gz или Ñ Ð¿Ð¾Ñ…Ð¾Ð¶Ð¸Ð¼ именем. 3. zlib Сайт: http://www.zlib.net/ Ðам нужен файл вида zlib-x.x.x.tar.gz, где x.x.x - номер верÑии. 4. libpng Сайт: http://www.libpng.org/pub/png/libpng.html Ðам нужен файл вида libpng-x.x.x.tar.gz, где x.x.x - номер верÑии. 5. libtiff Сайт: http://www.remotesensing.org/libtiff/ Из-за того, что libtiff обновлÑетÑÑ Ñ€ÐµÐ´ÐºÐ¾, а дыры в нем находÑÑ‚ чаÑто, лучше вÑего будет его Ñразу же пропатчить. Ð’ таком Ñлучае брать его нужно отÑюда: http://packages.debian.org/source/sid/tiff Там и Ñам libtiff и набор патчей Ð´Ð»Ñ Ð½ÐµÐ³Ð¾. ПроцеÑÑ Ð½Ð°Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¿Ð°Ñ‚Ñ‡ÐµÐ¹ опиÑан далее в Ñтом документе. ЕÑли вы не ÑобираетеÑÑŒ раÑпроÑтранÑть ваши Ñборки Scan Tailor'а и не ÑобираетеÑÑŒ открывать им файлы из Ñомнительных иÑточников, тогда можете и не патчить libtiff. 6. Qt 4.x.x (протеÑтировано Ñ Qt 4.7.4) Cайт: http://qt.nokia.com/ Ðа Ñайте: Download -> LGPL / Free -> Download Qt SDK for Windows Опцию "MinGW" в инÑталлÑторе можно отключить, еÑли ÑобираетеÑÑŒ иÑпользовать Visual Studio или Ñобирать Ñ 64-битным MinGW. 7. Boost (протеÑтировано Ñ 1.47.0) Сайт: http://boost.org/ Качайте boost в любом формате, при уÑловии что вы знаете, как Ñтот формат раÑпаковывать. 8. NSIS 2.x (протеÑтировано Ñ 2.42) Сайт: http://nsis.sourceforge.net/ 9. [VC++] ПонадобитÑÑ Ñам Visual C++, который можно Ñкачать отÑюда: http://www.microsoft.com/Express/vc/ Ðе нужно брать Ñамую поÑледнюю верÑию - ищите именно верÑию 2008 Express Edition. 10.[MinGW64] Скачайте и уÑтановите 64-битную верÑию TDM-GCC отÑюда: http://tdm-gcc.tdragon.net/ ЕÑли у Ð²Ð°Ñ ÑƒÐ¶Ðµ Ñтоит Ð´Ñ€ÑƒÐ³Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ MinGW, убедитеÑÑŒ что ее нет в PATH, иначе возможны конфликты. ИнÑтрукции 1. Создать директорию Ñборки. Ð’ ее полном пути не должно быть пробелов. ПредлагаетÑÑ Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ C:\build 2. РаÑпаковать jpeg-{верÑиÑ}, libpng, libtiff, zlib, boost, boost jam, и Ñам scantailor в директорию Ñборки. Ð’ результате должна получитьÑÑ Ð¿Ñ€Ð¸Ð¼ÐµÑ€Ð½Ð¾ Ñ‚Ð°ÐºÐ°Ñ Ñтруктура директорий: C:\build | boost_1_38_0 | jpeg-8Ñ | libpng-1.2.31 | scantailor-0.9.0 | tiff-3.8.2 | zlib-1.2.3 ЕÑли брали верÑию Qt без инÑталлÑтора, раÑпаковываем ее Ñюда же. ЕÑли не знаете, чем раÑпаковывать .tar.gz файлы, попробуйте вот Ñтим: http://www.7-zip.org/ 3. Создать там еще пару директорий: | scantailor-build | scantailor-deps-build 4. ЕÑли брали Qt Ñ Ð¸Ð½ÑталлÑтором, уÑтанавливаем ee, указав в инÑталлÑторе чтобы Ñкачал и уÑтоновил MinGW. ЕÑли без инÑталлÑтора, то Ñкачиваем инÑталлÑтор MinGW Ñ http://mingw.sourceforge.net/ 5. УÑтановить CMake. 6. [VC++] ПропуÑтите Ñтот шаг. Теперь нужно Ñделать так, чтобы CMake видела директорию bin пакета MinGW в PATH. ЕÑть два альтернативных ÑпоÑоба, как Ñтого добитьÑÑ: 1. Идем в Панель ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ (-> ПроизводительноÑть и ОбÑлуживание) -> СиÑтема -> Дополнительно -> Переменные Ñреды, и добавлÑем в конец переменной Path что-то вроде: ";C:\MinGW\bin" (без кавычек). 2. Можно проÑто запуÑкать CMake из Qt Command Prompt, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ñама добавит MinGW в PATH. К Ñожалению, в Ñтом Ñлучае придетÑÑ ÑƒÐºÐ°Ð·Ñ‹Ð²Ð°Ñ‚ÑŒ полный путь к CMake, вот так: C:\Program Files\CMake 2.8\bin\cmake-gui.exe ПроÑто cmake-gui.exe не запуÑтитÑÑ, даже еÑли вы при инÑталлÑции CMake указали добавить ее в PATH. Причина в том, что Qt Command Prompt Ñобирает переменную PATH Ñ Ð½ÑƒÐ»Ñ, Ð¸Ð³Ð½Ð¾Ñ€Ð¸Ñ€ÑƒÑ ÐµÐµ изначальное значение. Впрочем никто не мешает вам подредактировать файл qtvars.bat и добавить там директорию CMake в PATH. 6. ЗапуÑтить CMake и указать Ñледующее: Ð”Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ Ð¸Ñходников: C:\build\scantailor-0.9.0\packaging\windows\build-deps Ð”Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ Ñборки: C:\build\scantailor-deps-build Жмем "Configure". Выбираем тип проекта "MinGW Makefiles". ЕÑли какие-то пути не были найдены, указываем их вручную и жмем "Configure" опÑть. ЕÑли вÑе прошло нормально, кнопка "Generate" Ñтанет активной. Жмем на нее. Имейте в виду, что иногда нужно нажимать "Configure" неÑколько раз, прежде чем кнопка "Generate" Ñтанет активной. [VC++] Выбираем Ñвою верÑию Visual C++ (Visual Studio) вмеÑто "MinGW Makefiles" 7. Ðа Ñтом шаге мы Ñоберем завиÑимоÑти Scan Tailor'а. Этот шаг Ñамый длинный (может занÑть неÑколько чаÑов), но к ÑчаÑтью его нужно Ñделать только один раз, то еÑть вам не придетÑÑ Ð¿ÐµÑ€ÐµÐ´ÐµÐ»Ñ‹Ð²Ð°Ñ‚ÑŒ Ñтот шаг Ð´Ð»Ñ Ñборки других верÑий Scan Tailor'а. [VC++] Идем в C:\build\scantailor-deps-build и открываем файл "Scan Tailor Dependencies.sln". Он откроетÑÑ Ð² Visual Studio. Выбераем желаемый тип Ñборки (Release, Debug, MinSizeRel, RelWithDebInfo) и делаем Build -> Build Solution. ЕÑли не знаете, какой тип Ñборки выбрать, выбирайте Release. [MinGW] ЗапуÑтить Qt Command Prompt из меню ПуÑк. Туда вводим Ñледующее: C: cd C:\build\scantailor-deps-build mingw32-make 2> log.txt "2> log.txt" нужно затем, чтобы ошибки пиÑалиÑÑŒ бы в файл, а не на Ñкран. Этот файл поможет определить, что именно пошло не так. Когда Ñтот шаг завершитÑÑ, глÑньте в файл log.txt чтобы убедитьÑÑ, что вÑе прошло как надо. ЕÑли нет - то в конце файла будут ошибки (error). ÐŸÑ€ÐµÐ´ÑƒÐ¿Ñ€ÐµÐ¶Ð´ÐµÐ½Ð¸Ñ (warning) можно игнорировать. 8. ОпÑть запуÑкаем CMake и указываем Ñледующее: Ð”Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ Ð¸Ñходников: C:\build\scantailor-0.9.0 Ð”Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ Ñборки: C:\build\scantailor-build Жмем "Configure". Выбираем тип проекта "MinGW Makefiles". ЕÑли какие-то пути не были найдены, указываем их вручную и жмем "Configure" опÑть. ЕÑли вÑе прошло нормально, кнопка "Generate" Ñтанет активной. Жмем на нее. Имейте в виду, что иногда нужно нажимать "Configure" неÑколько раз, прежде чем кнопка "Generate" Ñтанет активной. [VC++] Выбираем Ñвою верÑию Visual C++ (Visual Studio) вмеÑто "MinGW Makefiles" 9. Теперь Ñоберем Ñам Scan Tailor. При повторной Ñборки той же верÑии (возможно измененной), начинать можно Ñразу Ñ Ñтот шага (он же и поÑледний). Ð”Ð»Ñ Ñборки другой верÑии, начинаем Ñ ÑˆÐ°Ð³Ð° 8. [VC++] Идем в C:\build\scantailor-build и открываем файл "Scan Tailor.sln". Он откроетÑÑ Ð² Visual Studio. Выбераем тот же тип Ñборки, что и на шаге 7, поÑле чего делаем Build -> Build Solution. [MinGW] ВозвращаемÑÑ Ð² Qt Command Prompt и даем там Ñледующие команды: C: cd C:\build\scantailor-build mingw32-make 2> log.txt ЕÑли вÑе прошло как надо, в текущей директории поÑвитÑÑ Ð³Ð¾Ñ‚Ð¾Ð²Ñ‹Ð¹ файл инÑталлÑтора, под именем scantailor-VERSION-install.exe, где вмеÑто VERSION будет верÑÐ¸Ñ Ñборки, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ð±ÐµÑ€ÐµÑ‚ÑÑ Ð¸Ð· одноименного файла в корне дерева иÑходников. Патчим libtiff Эти инÑтрукции предполагают, что вы взÑли Debian'овÑкие патчи к libtiff: http://packages.debian.org/source/sid/tiff Там вы найдете и оригинальные иÑходники libtiff (Ð¸Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð° типа tiff_3.9.4.orig.tar.gz) и набор патчей Ð´Ð»Ñ Ð½ÐµÐ³Ð¾ (Ð¸Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð° типа tiff_3.9.4-5.debian.tar.gz). Скачайте оба и Ñледуйте инÑтрукциÑм: 1. Скачать и уÑтановить утилиту коммандной Ñтроки Patch: http://gnuwin32.sourceforge.net/packages/patch.htm Лучше берите верÑию Ñ Ð¸Ð½ÑталлÑтором. Ð’ Ñтом Ñлучае CMake Ñможет ÑамоÑтоÑтельно найти путь к patch.exe 2. РаÑпаковать оригинальные иÑходники libtiff в C:\build, чтобы получилаÑÑŒ Ñ‚Ð°ÐºÐ°Ñ Ñтруктура директорий: C:\build | tiff-3.9.4 +-- build | config | contrib | ... Ðабор патчей раÑпаковываем внутрь директории "tiff-*.*.*", чтобы получить директорию "debian" на одном уровне Ñ "build", "config" и "contrib". 3. Создать еще одну поддиректорию в C:\build Ðазовем ее "tiff-patch-dir". 4. ЗапуÑтить CMake и указать Ñледующее: Ð”Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ Ð¸Ñходников: C:\build\scantailor-0.9.0\packaging\windows\patch_libtiff Ð”Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ Ñборки: C:\build\tiff-patch-dir Жмем "Configure". Выбираем тип проекта "MinGW Makefiles". ЕÑли какие-то пути не были найдены, указываем их вручную и жмем "Configure" опÑть. ЕÑли вÑе прошло нормально, кнопка "Generate" Ñтанет активной. Жмем на нее. Имейте в виду, что иногда нужно нажимать "Configure" неÑколько раз, прежде чем кнопка "Generate" Ñтанет активной. [VC++] Выбираем Ñвою верÑию Visual C++ (Visual Studio) вмеÑто "MinGW Makefiles" 5. Из Qt Command Prompt Ñделать Ñледующее: C: cd C:\build cd tiff-patch-dir mingw32-make ЕÑли ошибок не было, значит вы уÑпешно пропатчили libtiff. ЕÑли когда-либо вам понадобитÑÑ Ð¿Ñ€Ð¾Ð¿Ð°Ñ‚Ñ‡Ð¸Ñ‚ÑŒ его Ñнова, Ñначала придетÑÑ Ð¿Ñ€Ð¸Ð²ÐµÑти его в иÑходное ÑоÑтоÑние, то еÑть заново раÑпаковать его из .tar.gz файла и ÑнеÑти директорию "debian". scantailor-RELEASE_0_9_12_2/packaging/windows/registerExtension.nsh000066400000000000000000000040041271170121200253260ustar00rootroot00000000000000; From http://nsis.sourceforge.net/File_Association ; Added a parameter to set icon for association -Brendan Kidwell !define registerExtension "!insertmacro registerExtension" !define unregisterExtension "!insertmacro unregisterExtension" !macro registerExtension executable extension description icon Push "${executable}" ; "full path to my.exe" Push "${extension}" ; ".mkv" Push "${description}" ; "MKV File" Push "${icon}" ; "name.ico" Call registerExtension !macroend ; back up old value of .opt Function registerExtension !define Index "Line${__LINE__}" pop $R3 ; icon path pop $R0 ; ext name pop $R1 pop $R2 push $1 push $0 ReadRegStr $1 HKCR $R1 "" StrCmp $1 "" "${Index}-NoBackup" StrCmp $1 "OptionsFile" "${Index}-NoBackup" WriteRegStr HKCR $R1 "backup_val" $1 "${Index}-NoBackup:" WriteRegStr HKCR $R1 "" $R0 ReadRegStr $0 HKCR $R0 "" StrCmp $0 "" 0 "${Index}-Skip" WriteRegStr HKCR $R0 "" $R0 WriteRegStr HKCR "$R0\shell" "" "open" WriteRegStr HKCR "$R0\DefaultIcon" "" "$R3,0" "${Index}-Skip:" WriteRegStr HKCR "$R0\shell\open\command" "" '"$R2" "%1"' WriteRegStr HKCR "$R0\shell\edit" "" "Edit $R0" WriteRegStr HKCR "$R0\shell\edit\command" "" '"$R2" "%1"' pop $0 pop $1 !undef Index FunctionEnd !macro unregisterExtension extension description Push "${extension}" ; ".mkv" Push "${description}" ; "MKV File" Call un.unregisterExtension !macroend Function un.unregisterExtension pop $R1 ; description pop $R0 ; extension !define Index "Line${__LINE__}" ReadRegStr $1 HKCR $R0 "" StrCmp $1 $R1 0 "${Index}-NoOwn" ; only do this if we own it ReadRegStr $1 HKCR $R0 "backup_val" StrCmp $1 "" 0 "${Index}-Restore" ; if backup="" then delete the whole key DeleteRegKey HKCR $R0 Goto "${Index}-NoOwn" "${Index}-Restore:" WriteRegStr HKCR $R0 "" $1 DeleteRegValue HKCR $R0 "backup_val" DeleteRegKey HKCR $R1 ;Delete key with association name settings "${Index}-NoOwn:" !undef Index FunctionEndscantailor-RELEASE_0_9_12_2/packaging/windows/scantailor.nsi.in000066400000000000000000000077451271170121200243710ustar00rootroot00000000000000!define VERSION "@VERSION@" !define VERSION_QUAD "@VERSION_QUAD@" !define LICENSE_FILE "@LICENSE_FILE@" !define INSTALLER_FILENAME "@INSTALLER_FILENAME@" !define STAGING_DIR "@STAGING_DIR@" !define UNINST_REGKEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\Scan Tailor" # The window identifier of the main Scan Tailor window. !define WNDCLASS "ScanTailor_Window" !include "@REGISTER_EXTENSION_NSH@" !include "x64.nsh" !include "MUI2.nsh" !include "LogicLib.nsh" BrandingText " " # To remove the mention of NullSoft. #LoadLanguageFile "${NSISDIR}\Contrib\Language files\Russian.nlf" Name "Scan Tailor version ${VERSION}" OutFile "${INSTALLER_FILENAME}" !if "@SIZEOF_VOID_PTR@" == "8" InstallDir "$PROGRAMFILES64\Scan Tailor" !else InstallDir "$PROGRAMFILES\Scan Tailor" !endif InstallDirRegKey HKLM "${UNINST_REGKEY}" "InstallLocation" RequestExecutionLevel admin XPStyle on AllowSkipFiles off SetCompressor /SOLID /FINAL lzma !if "${VERSION_QUAD}" != "" VIAddVersionKey "ProductName" "Scan Tailor" VIAddVersionKey "ProductVersion" "${VERSION}" VIAddVersionKey "Comments" "Interactive post-processing tool for scanned pages." VIAddVersionKey "CompanyName" "Joseph Artsimovich" VIAddVersionKey "LegalTrademarks" "" VIAddVersionKey "LegalCopyright" "� Joseph Artsimovich et al." VIAddVersionKey "FileDescription" "Post-processing tool for scanned pages." VIAddVersionKey "FileVersion" "${VERSION}" VIProductVersion "${VERSION_QUAD}" !endif !define MUI_ABORTWARNING # Installer Pages !insertmacro MUI_PAGE_LICENSE "${LICENSE_FILE}" !insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES !insertmacro MUI_LANGUAGE "English" !if "@SIZEOF_VOID_PTR@" == "8" Function .onInit ${IfNot} ${RunningX64} MessageBox MB_ICONSTOP|MB_OK "A 64-bit version of Scan Tailor won't work on a 32-bit version of Windows." Abort ${EndIf} FunctionEnd !endif Section SetOutPath $INSTDIR File /r "${STAGING_DIR}\*" WriteUninstaller $INSTDIR\Uninstaller.exe # Write uninstall registry records. WriteRegStr HKLM "${UNINST_REGKEY}" "DisplayName" "Scan Tailor" WriteRegStr HKLM "${UNINST_REGKEY}" "UninstallString" "$INSTDIR\Uninstaller.exe" # Create menu shortcuts. SetShellVarContext all CreateDirectory "$SMPROGRAMS\Scan Tailor" CreateShortcut "$SMPROGRAMS\Scan Tailor\Scan Tailor.lnk" "$INSTDIR\scantailor.exe" CreateShortcut "$SMPROGRAMS\Scan Tailor\Uninstall.lnk" "$INSTDIR\Uninstaller.exe" # Register the ".ScanTailor" file extension. ${registerExtension} "$INSTDIR\scantailor.exe" ".ScanTailor" \ "Scan Tailor Project" "$INSTDIR\scantailor.exe,0" SectionEnd Function un.onInit FindWindow $0 "${WNDCLASS}" "" StrCmp $0 0 continueInstall MessageBox MB_ICONSTOP|MB_OK "The application you are trying to remove is running. Close it and try again." Abort continueInstall: FunctionEnd Section "Uninstall" # Unregister the ".ScanTailor" file extension. ${unregisterExtension} ".ScanTailor" "Scan Tailor Project" # Remove program files. Delete "$INSTDIR\scantailor.exe" Delete "$INSTDIR\scantailor-cli.exe" Delete "$INSTDIR\mingwm10.dll" Delete "$INSTDIR\libgcc_*.dll" Delete "$INSTDIR\CrashReporter.exe" Delete "$INSTDIR\Qt*.dll" Delete "$INSTDIR\libz.dll" Delete "$INSTDIR\zdll.dll" Delete "$INSTDIR\libpng.dll" Delete "$INSTDIR\libjpeg.dll" Delete "$INSTDIR\libtiff.dll" Delete "$INSTDIR\Uninstaller.exe" RMDir /r "$INSTDIR\Microsoft.VC80.CRT" RMDir /r "$INSTDIR\Microsoft.VC90.CRT" RMDir /r "$INSTDIR\Microsoft.VC100.CRT" # Remove translations. Delete "$INSTDIR\translations\*.qm" RMDir "$INSTDIR\translations" # Remove imageformats. Delete "$INSTDIR\imageformats\*.dll" RMDir "$INSTDIR\imageformats" # Remove the installation directory. RMDir "$INSTDIR" # Remove the uninstall record from the registry. DeleteRegKey HKLM "${UNINST_REGKEY}" # Remove menu entries SetShellVarContext all RMDir /r "$SMPROGRAMS\Scan Tailor" SectionEnd scantailor-RELEASE_0_9_12_2/resources/000077500000000000000000000000001271170121200174715ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/resources/GPLv3.html000066400000000000000000001107471271170121200212640ustar00rootroot00000000000000 GNU General Public License v3.0 - GNU Project - Free Software Foundation (FSF)

GNU GENERAL PUBLIC LICENSE

Version 3, 29 June 2007

Copyright © 2007 Free Software Foundation, Inc. <http://fsf.org/>

Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.

Preamble

The GNU General Public License is a free, copyleft license for software and other kinds of works.

The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too.

When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things.

To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others.

For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.

Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it.

For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions.

Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users.

Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free.

The precise terms and conditions for copying, distribution and modification follow.

TERMS AND CONDITIONS

0. Definitions.

“This License” refers to version 3 of the GNU General Public License.

“Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor masks.

“The Program” refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”. “Licensees” and “recipients” may be individuals or organizations.

To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work or a work “based on” the earlier work.

A “covered work” means either the unmodified Program or a work based on the Program.

To “propagate” a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well.

To “convey” a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying.

An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion.

1. Source Code.

The “source code” for a work means the preferred form of the work for making modifications to it. “Object code” means any non-source form of a work.

A “Standard Interface” means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language.

The “System Libraries” of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Component”, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it.

The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work.

The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source.

The Corresponding Source for a work in source code form is that same work.

2. Basic Permissions.

All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law.

You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you.

Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary.

3. Protecting Users' Legal Rights From Anti-Circumvention Law.

No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures.

When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures.

4. Conveying Verbatim Copies.

You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program.

You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee.

5. Conveying Modified Source Versions.

You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions:

  • a) The work must carry prominent notices stating that you modified it, and giving a relevant date.
  • b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all notices”.
  • c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it.
  • d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so.

A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate.

6. Conveying Non-Source Forms.

You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways:

  • a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange.
  • b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge.
  • c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b.
  • d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements.
  • e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d.

A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work.

A “User Product” is either (1) a “consumer product”, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used” refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product.

“Installation Information” for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made.

If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM).

The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network.

Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying.

7. Additional Terms.

“Additional permissions” are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions.

When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission.

Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms:

  • a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or
  • b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or
  • c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or
  • d) Limiting the use for publicity purposes of names of licensors or authors of the material; or
  • e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or
  • f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors.

All other non-permissive additional terms are considered “further restrictions” within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying.

If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms.

Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way.

8. Termination.

You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11).

However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.

Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.

Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10.

9. Acceptance Not Required for Having Copies.

You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so.

10. Automatic Licensing of Downstream Recipients.

Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License.

An “entity transaction” is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts.

You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it.

11. Patents.

A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor version”.

A contributor's “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control” includes the right to grant patent sublicenses in a manner consistent with the requirements of this License.

Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version.

In the following three paragraphs, a “patent license” is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant” such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party.

If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid.

If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it.

A patent license is “discriminatory” if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007.

Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law.

12. No Surrender of Others' Freedom.

If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program.

13. Use with the GNU Affero General Public License.

Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such.

14. Revised Versions of this License.

The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.

Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation.

If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program.

Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version.

15. Disclaimer of Warranty.

THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

16. Limitation of Liability.

IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

17. Interpretation of Sections 15 and 16.

If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee.

END OF TERMS AND CONDITIONS

How to Apply These Terms to Your New Programs

If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.

To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    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.

    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.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

Also add information on how to contact you by electronic and paper mail.

If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:

    <program>  Copyright (C) <year>  <name of author>
    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    This is free software, and you are welcome to redistribute it
    under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an “about box”.

You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer” for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see <http://www.gnu.org/licenses/>.

The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read <http://www.gnu.org/philosophy/why-not-lgpl.html>.

scantailor-RELEASE_0_9_12_2/resources/appicon.svg000066400000000000000000000622541271170121200216540ustar00rootroot00000000000000 scantailor-RELEASE_0_9_12_2/resources/icons/000077500000000000000000000000001271170121200206045ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/resources/icons/COPYING000066400000000000000000000042361271170121200216440ustar00rootroot00000000000000The icons in this folder and in sub-folders are all licensed under GPL version 3 or above. The origins and original licenses for each icon are given below. Origin: Created by Scan Tailor developers or contributors Website: http://scantailor.sourceforge.net/ Original license: GNU GPL version 3 or above Files: ../appicon.svg aqua-sphere.png aque-sphere.svg keep-in-view.png layout-type-auto.png left_page_plus_offcut.png left_page_plus_offcut_selected.png left_page_thumb.png right_page_plus_offcut.png right_page_plus_offcut_selected.png right_page_thumb.png single_page_uncut.png single_page_uncut_selected.png two_pages.png two_pages_selected.png Origin: The GNU Image Manipulation Project (GIMP) Website: http://gimp.org/ Original license: GNU GPL version 2 or above Files: stock-center-24.png stock-gravity-east-24.png stock-gravity-north-24.png stock-gravity-north-east-24.png stock-gravity-north-west-24.png stock-gravity-south-24.png stock-gravity-south-east-24.png stock-gravity-south-west-24.png stock-gravity-west-24.png stock-vchain-24.png stock-vchain-broken-24.png Origin: F-Spot Website: http://f-spot.org/ Original license: GNU GPL version 2 or above Files: object-rotate-left.png object-rotate-right.png Origin: Tango Desktop Project Website: http://tango.freedesktop.org/ Original license: Public Domain Files (some modified): big-down-arrow.png big-left-arrow.png big-right-arrow.png big-up-arrow.png despeckle-cautious.png despeckle-normal.png despeckle-aggressive.png insert-before-16.png insert-after-16.png insert-here-16.png user-trash.png plus-16.png minus-16.png trashed-small.png trashed-big.png untrash-big.png undo-22.png Origin: Amarok Website: http://amarok.kde.org/ Original license: GNU GPL version 2 or above Files (derived from default-theme-clean.svg): play-small.png play-small-hovered.png play-small-pressed.png stop-big.png stop-big-hovered.png stop-big-pressed.png Origin: Crystal GT icon set by Everaldo Coelho Website: http://www.everaldo.com/ Original license: GNU LGPL version 2.1 Files: file-16.png Origin: Nuvola icon set by David Vignoni Website: http://icon-king.com/projects/nuvola Original license: GNU LGPL version 2.1 Files: folder-16.png scantailor-RELEASE_0_9_12_2/resources/icons/appicon-about.png000066400000000000000000000167341271170121200240660ustar00rootroot00000000000000‰PNG  IHDRd`ës×BsRGB®Îé–IDATx^í]yt›Õ•¿Ú-ÙòÛ‰í$NÈæìH`Òé0ÐR(P(9…´@§ôpšÐé,LOYf€s ´¥ KiI“ )ekM“4$Cö}qïû.Y²–ùýžô9²"ÛŸl9²{γ?}zßûÞ»÷ÝåÝ{ß“ab¾<ãH“;-&‹ßfq´™ Ö&_ÀÐÔ Tyü¾cíí]UWwm–/ !()I_äpxW§¦fä9i™V«5‹ 75¹_Ú¹óÔO ¥“¤35ÇärÅdrŠÝn“”«Øl&±X|âÞv¢sjUû ?žñ‰´¡´w£ð¿7\\øß†Ò‰âi©iÀç¼U¤5!#飑q"¹6‘Ie"Ÿ¢ ºuÞ!oL™ R˜&Rœ#âħC$ýršñß‚ÿ(é&”ÃŶ9ŽùÅù¹¹E’™™ \£6‘ÔÒܲuÿ³É(›Í!õõ&1Eº=éÄÓ€O|¯RƤÖ¦ÏÉôû%Íç;ŠÅÏÙ(¸Î $5Œ‡.üñ„ €4 ,uD\¸ÚÒañà¿÷:QÇ!qíÁ³^\w¢¸ð\JˆÍ:x¶]OÃ=[®H®' ›“Æ‹,ÄË9(²uÿŠA{ðn/áÅ;0GÎÞkÁ};êY0ÖIC žñ[qˆÅ{Œh׈ïRÐsŠHß§¢žÏ;QÉ‚þ¥ðu,øœ‚:$€ÁÞÏ\@¿¥-;[2‹/ãì‹%=+:¥¹¹RÒÒ,’‘‘k¨®qÏ5â7›-bµZÄ &™<±H©©ÒîrIˆ6¢Ý@©Ûí–Z·G|¸‚è±ßÅtú½ÝôzÄÔÝ-–n¿Ø€ö”`ÐhÓ-hé‚k3Šׯ`Pu ÿ£ ‚õ œþƒÔ[,Òn6K5ú|×f»].Êœâææ9öŽŽžÉq>y¥/kÈÌÒ¹¹bÉÉ{^žxp/¥¸XÆNš$EER]] Idwd‚Íb6Äá°JWW'¤“Ý´’̓@²_ÌA¿ØSL2qÂXð]št‚(-­mâõx@³˜€Œn ÛÎq¹Üâq¼øì'L1Œ’ý(Ý>¿´y@( ›ŸYÄïûð>Ú"ð{uE>±‚$úbD›˜™ªí&…OuÔ &‚ÑȺ~Ôí¿Ç%]˜K_ø½´¶¶¢_.¼Ü Ô…ÏFÈÛØ(Þ†1¶´ˆ÷ý¼‡gÌxwm“£S0Ï .ÇhÁ„„Xg¬6›ø0~?þ¬V°“U|¨kãwN§¤Ž#“ Ä™‘ÉÛ-xÇGÄÁw¼×ÞÞ.’ŸŸüy¤­­ jÁ&Ð%’••!ÍxÆl6>/li2³¨?L2kúdPÍ!­hÄ &G·1Ø6ikïw—W(ç,"È‹ñ….w¨Ü…—…ïõQ} u ”u»pr! oẻ½¨ ŽBû^¶ Ê–‚ïQŸý  e5›À]ª¾€&ˆR„’×Ý!~7$’Ý'ÍA«\uÕUjМu¸†ö `¸ê~€"HOOO"Ð&¬ããä  ¥& ÌYͶ2€x>£µÇ÷ùð¹g›Ó¦MSß“0¬Ÿ••±”¦Úåp‚”••©¾@à™,Œ“˰š­­rª´º3· 3ðh„¥Ý*%à™3g ²_ªªkU£>?f !ß-m­¸ODaÞs&–Äóáœeäò‚Ñhw'5KP<kÌH7¸ ê 3b°À²BÛ²X!™9@h/D¡„him—î.7ô[ˆ^w@ÜÐ*F¿¸Ð´ ï 81[Y"¡©©©é18žÐäñ(ÂhÏð3‘oÁìg"ãfáw߬KbÁär ÃB"Ãz’’’ÕÅ>‰Ù7„ßuu¹ÀÕjÂñ•æòÑB‘C™1°€BP8+ Çå+äiD‡B´†H^qìHs ØÐjR#d¤;G´Ãþ ÍÂPiiÔpô”ºÇÛáw„ dg¸Ü.éhkØŽ764IC}@ë1Ü!"(Öú‚lÈt–X@„pV« ‡ D"‘D¸aøLI $$¿×ðCi@ÂDOˆ¾úEÑÊw–ïß|˜Ñf>{„Ó‘âRøÚ@z $Ñãr†5Ó –†íAéáMò­Ñ(³°2M´P}¥¸RøŽÞQLÂEX˜WFÝà”Sc”‰“ ôìYjU¬¬ÑÑk†ÑÐ{®[æ(qk„ɨhb€yÅ ÆìM6kö&³ßwØ¢¸›fo(×ï0(H: OW4ÈÑÕ²xá$¬Ž­j½q¢¬^Š 3”«(@šàê˜+÷D](sçΫY®úcq¶ÁŠzzè:æ:„e¨àƒcñð±Jåa|åȱy鵿¨fý~£ìØ].ÇNT(·Ê?vý ¢×0¼é¦›dçÎCÞ9Ïs¶“ÐtèÍÕ¢Õþ•CÍ ·[+-,ºÆiõÒŸ4àÌÿ—‡×Jum‹dËi2[¥¡¡^¹/èŽØ´í´jž>$–gý)—ÍrïªKd|!c=ñCØ_ÜïƒtUìÞ½»W /¼Pn¸áøífª¾|òÉ'òÈ#ô¸FX9nô§Ÿ~ZfÍš%µµµªÎÛo¿Ý‹°œéÛ·oWN½ I"KØ÷¦-6Ì50{µµ]ƒYÞۉΧ˞•ˆ*ù;ΛŨ˜S¶ïè–òª&ùñ}/•­ò‡wwÁgdSÞÒÖ¶.©©m•Æ&×  ¢9ûC©yq§OŸ.Ï<óŒ\}õÕ½Y¾|¹òÄ®^½ºçþš5käöÛoïù¼bÅ ùÉO~¢òè£Êo¼¡¾Û²eK\¡ÏŒï ù¡Ï}YmX‹¸á"³›¨Cô’8¢ÞÞƒ§åÕõ›•ëyâø™S:NöòÉ×®™/Yv¹þ+³d÷32sZ¾L™˜-Mpè}øña8ípµáKS~´Á‚¥¾uëVÕüu×]'¯¾úªòÒÆ‚¥K—öº=a‚³1`Á‚òúë¯ËË/¿,ßýîweÛ¶mquŸ:„ñ Íä5 ‚¨„tã+ä¬Õ xyÐÇÍ-.ÅòùcÒå‡w/Ç‹¨‡B.ô£PdîÌ"u‚˜Ç­7,¯‚HŒ<¾øÚ©³eöô<½¯=§žK!1ž}öÙ~¥cñÀÊ•+çÝwß}Ê£­™³µáõRsùxFéoÄÃøY- ai¹ùÉ%Kë…MÝ'<¹^¶í<¡üù6 ÖfÓGÐTx7°¶.œ¯g…|ç‡ëáÆ?(ÒÛÖÈT§^;s挼òÊ+rìØ19}:¤ÇbA¤;]oî¼óN¡¸ÛWº^ wÐ»(“ (T¢Ìd0!ˆrþꪚ&XPåR[ß Wz–Üýí+õ<Ö«ÎìÒ±ÂB¨¬F®Ê ÓhÀ§(R(RIº:äÅ$Ö®]+¥¥¥º[¥Ã¢6Õt¦Èâ?=½q,Ô;\Ýj4ûXs=ä2øLüˆè¶ϬþÞRÌrFÖ(æôh„¨q°v¢¡B‘JK«¢¢Br‘„h ’Žèé\‡0ðÉçÕÔ Tº QáÓ‰PFûÒ¡Œ©©œ* 88 1Á5É`AïÚéÈ‘#*"ÈXw²¡• Š LtPÔÁ…^;ë_yQ¶~> ®®A^YûqÒÆÈŒ=@ÝA‚0ÜœlèKW)‚0)-4¿¡uÎT+ÜÇšt¡ëb÷þSI£^“™ ;*SZEŒ'«'—tÇRê~.Î1&8è›k‚5Æ—z”¿`ÍÚ䀵SYY©dv(*uX|ZñŒžf/'†?ªó‡(U>ùùÂSÆ÷¬Yh{Ÿ<Ó(o½·KÎÀ‰HGâù=¶UU•š@$Å–žg†s \³ô â{;øÇHkE/‹À"òûΚÈ\ämx÷ÿäŸú­<÷üj,»öFªO(eF=³9^Dñ…þž£©KB°D®ã}W¢ê“[éß3E»ßCœ¡²Ôãòô–•ÕH¬•¼¼üž•/-.6h°É ¿Ý&Ÿî9.MÍmò•åseùå3äý{A¤rù»VeH&  á2AŽ&¯êÓP˜Ò™L`þ=ÄçÄCØ)¬Ô••cñ¢yST²u7d!s_‡Ú¡A‹üWõƒî÷ÎÝrך×åØÉf¬ä rõ²‰òw—MÔ-f¢$ˆvÈ 2Më8‰ø‹_ü¢×8^{í5µžI4P‹£Óž4‚ð 7ñÇ¡ÔWÞ¼LRá^ègo Rø©­ëÍîzñÆ÷¡s8 '<”®”‚ÜøRŠ NQr ¡•²)"wíÚ%·Ür‹üéOêÕmz±™•øÁ$<}(Z_ñÅ=~\¸ØŠOˆ•K“ᬫü‰gß–'¼M/äÈñ:©k½xd±j7xeÕm Àq!˜^ˆ4DèÝ¥ØÓB$¸è~÷»ß©m4“ïºë®˜Uׯ_/7n”Ç{Lo·¬9Qà×Vî!åáqIݱ ÔbÙé:èJ_`à‰ì÷ñÖ#òÎ;eáüˆØ#⚺V¸âíH:#‹æOúÆyò¹± ¡ HÈ’œì4¹ùºé8*äJ‰‡ì£ò2‡âJ‹ÆiÌ,¬U«V Ëùíès”•ÕCê4£9*O¨¿Nfe†Všf$ªñt%ü­÷öÂJòÉ{H36:\P2 uGäÀ¡r€ªmX„XIƒt`åÃÜ®––f¹ñ¦ÊÔÉ<âB;Z#>Ô0[¦ºº£›ƒz,,rJ²ŽÎhä\ö/Ú3ÒCHŸÎ±Ð!Ð!^n¼v|¼íâåÕrÉE%r튙ŠJ§äž=|FçûbW#‡T+ÓVÓÚ }$¹K¢ûΉë§0 Š=­ Z¡Ùq@™vG<8Zõ­/Ë·-9C›t®»z¶8\.“KrÅØûäú¤"OЧõêòÜ©.pÉ!ʼn$õ#”#´n V—‡¨å®+’xLßÈ«¶Ãú•Y)³K‹{ˆ0܈á$Ò²µE×HH÷ékÜœ@4·£[žÂ¥î+C>” gTEʶáFd"ÚWÛÀ0@íX=mýQŒÃ'G2(_[ïu†½‡ ;Šåò÷æ@³ƒjñ˜¾#cÀÙ¼y³Z¸‘ô33q a2ûN‘ª²ð¡Ø™¥ˆ#/-4‚¤Ò$› =·Ñie%s@‘ï6bqÊü. @1P__/3fÌ)݋ٺM8i:±dx}Fšì¸(͆~LSêAœñèp¤[Ün¿Rì£ ,lÑÅñÒ‹/"C²MmYÓë¿JÖ8Éɱ®\¤ÛŠÅ +h_Þ#²Ì& âDVÖ8–u43I÷íúD¶ÃýAo-»aÆ?êjgF&¸;®£ D6ÈlƒÙlí¶ÙìÈ“ÊA|#ù[¾âÅfa^šrßLž+D ÷Þ{¯ºÅ¸øO<¡âå#iõ+«víÚ7æGö[šSV¬¸tÂÒÅ s‹ ó ²ÒÅØµ0Ýžb‚}Ší)–ü̬´´¬Œ4žTóì÷ÈóÞc_Ÿ}N;û½©©=¸ÿPYõŽGŸyôéw~†NŸÓ½ôÒK„úãx&dÆsÒð°2Š-žkuÏ=÷$mbñÅ B°¦¦Æ…5HÝSO=õ矾—Õ¡‡ `Ú´\ç×W|¥´tfIQŠÝvÕbš—žžR„¬¼lɶ۬iø «Ûã6æd91¹ƒFˆ!üŒ?Ðåõøð«=8†ÞÕàryŽ#Èuž‚m›þvdãºu›û5õ/^¼²øºÛ80þŽ;îX…¾²°°ðR¬Ö³°õÀ¨õðà OìyàT2ÃpbúìäxÚ0Oj!6ÏÀ·¶~«½T ‰â0ün}Ž+!‰sp“ü‘î=ПÉõ’©S§.¨ÚY cÅÈTûöí;Y g æ-[¶ì⢢¢+QwásÏ=7çàÁƒÄwë­·Þ5q œ%ám 8)Ét4ð£;fµPÚŽeA+,Ò m(͈áWcMq&v=&EÕ¯~õ«hnЮŽd$Nú[Çî• õ!5‰Ä ‡`.B>ê¯q(õÔÿuŠÜ @î÷AÈ3CîPHðŽðö¬¦ ŠÆbçÛ˜Ñ3"swq}&î–þznèANb†ß ‚LC¹†òíÁsC;Ž5ACm±B̬1GƒÞ µ¢fí@xè9‚ç‹PÿB”ë <£;º×ÉÌ7×q6Þ@#<߃÷cv_Io.fûÏ ¦n„ß>Yg¶Þ®à™á™ …S9™Dü8Ê&”IzÛù\×C†FLÜF(ç Ê32p]ŠPm7L^ôJ<¿_tmب Gü…i8?@Ù‹ráçÙzþKX=AX4ï£~¯­ÂPÖ; ‚à ¡ã‚WÃ"*òDåŸw%¾ÃÜãzíè¯| †Ð kª ¦é´èá€sþbqBÃ¥q•mÑD½.ê¹kð¿æ*úsóÅ£½ú[á™ü¯±ÎÙ‘D™7ˆ¾€gžŠñÜÓ¸÷Ä ÚûÌ?ò%Œ È ”X§õçsº`¾r×ÐÙÔ¶{ %úH#z"O¡èoêóQ“§ÝSñÞßÇp¿þþ—C@ÇËxöû1ž§åõèÚýÌ=Š!U{蹯,öéùpæ† B«i°Àdãã(…Q P'‘K¾€0Ö…‘Swà;žÛ¢~9¥¿Ÿ3׃Ð5¨ôG”H Ž×ÌøøÂ ¸@c¾>3.úZ_ЗNqök= ýzï P|E ÁÐé÷о®&sô®WÅ]éáÙú?øß×®KÍ> ËDÇÛIØo¡¼‰òZXœôL'þ°xiUˆd"iY›ŽûÔ/< ¥'7 ƒàÚƒ¢K‹EóÿðPÐáóÕ‡ïët²'ÃH{x:Dñµåõ0§Ä÷ÓÃС‘ÐäŸÑ 0KYs=BÝÂUvÿ[lGÂHâèC"Y=Ž×êªZ…Zœ™MÑp7npáFy_¦«µ/* È~Qrßo¤¥CÅM· uÉ’0!H¤ä&W}Žéòû0—Ð…B½AbüæsŒ¤ýrô€¦¯¶Hã] Õg•ôAö0#„Ù Œ£ë; ~øÿ¨½?þ#î+¯IEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/aqua-sphere.png000066400000000000000000000014131271170121200235240ustar00rootroot00000000000000‰PNG  IHDRÉV%sBIT|dˆ pHYs……EYžBtEXtSoftwarewww.inkscape.org›î<ˆIDAT(‘…ÌIkTQàSuï}ïu·M¢ÁN'£¨ãD@܈ ]8Á…{ƒà¯Ü*nAÔˆD7Ú(ç5‰IºM«éáu¿áÞr¡]œZõ‰þNaäòI±­#â¢ÍZ%…ÔšYbÿ©ÌµÙgÇÏÿ}KK¸¸íz¯K/nÙìÝ·gÅB¹¬A½cjºŽñ»Ÿñú}<ÁºãÄÌ“ƒå?¸¸}¼×%_ŸÚ_ìÙ1ÒƒåòàQã÷æ”Y9<3¹¿¬@\ûÂàšŽžÞUy|™–Åýè[Ý虫´/8¨ûwNÜL°³ådY¸”l6  q çÄ1 `ŒØÇü‚ƒç§ÿÅ•ª€Ø€1M¤‡Áj àã´…ïñ²°9„- b "=¬Az=‘„ѯý‹diƒXÿÞÒzÍ”…ÜZ"€ˆÓ?ÄÎ à á²&<%–µ¬öÊØ#°&€À%;¸X ‰@, BO5™“S>Cg&Ï09† Ägiè4~Õ†6ˆ¨í=3Ý9w›'U–ý.… ›áw)˜<ƒ`cA²èÐþæU-âšCÚ’OÝ»¢­ª÷ð—Ì«Š6…£&oÈïÔð» L‡‚ÉjV€c¸„á"†KY’ôþ©š=÷JuöõåÂ…›Î¹L`2C•h¥CR‚m1Ò&#©3âfÕ+W®ÖªgoI£QUft”äûwÝ®•“úÃÆPAÒ®œ =ŽkŒè#þ§ÍÊËÙê»Ó—›ÕwœÈ1¦J¸tIõ—JžLMuë0,Ú4$‡ãlPÞºBÒzSŽÃÇo]šÌÀÚÐzÊårÕòîÝ­ŸUè3?Õ÷dRIEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/aqua-sphere.svg000066400000000000000000000157461271170121200235550ustar00rootroot00000000000000 image/svg+xml scantailor-RELEASE_0_9_12_2/resources/icons/big-down-arrow.png000066400000000000000000000022431271170121200241510ustar00rootroot00000000000000‰PNG  IHDR szzôsBIT|dˆZIDATX…í—KlTUÇçÜ{§-ˆPÀ4j¤¦J…&1 #â#M”èÆQnи%î4AcbbÐÄ­˜@â£<Ú‚ò0B©<¡´´ú Ç½çœÏÅÌ´Ó2-#a×üsgæœ9ÿß÷ǽîÇ=§Ó¢u¾ø^€ˆ`¹í“LYÃzÒ·ëçÇøtÍ×8g±b°Î`ÅàœÁ:‹+ø-²!ßÔ×…ý)Sÿ#À Û‰LšÈeˆl†È¦‰l†Ð¦‰lHÆ$Y6û]$î˜:nG•›-¥nµ|Û÷Îâ„ÃÝ=ÉUUŠd™o‹³8ÿ3@ÎA©|›»›"‚—Ë6/†€"—ªÄ]ÈZ9D®@KGƒwlE¶á¢u~6 E @ !Ÿi@P‚Ò‚ˆa0ºŽ(‹Þ %%Öï^ô&Ç^ÐTgnÙB·ž¢>XX³ôóå‹W•CÈf*"X±  ‡VBhè »Ïâ{H$JXóú:Ä "¿|^ëíø¤XŠÅÏì}¹rñû«Ÿ¨¬NœèhÀØ#!Ž+!)ÛÏt›q.ˆâ@œ x©jÇOþ™n=¬þàFûf1¯¢k Ýg?ÜѸåDOo­œ:‡mA[zÂ6ºRç0*…hü@á'^ †¾{æÙÊZ®vw¸Sç_ô'Ú·‹ùŒ pô+¢È¤k·þüYß=E*&W"Úr=s™~Ó‰À tÖ8¡sŸ5^μºbeºœ†C»Ä™WÆ»)¹ š6Ñ50xsÙw{¾È̘<‡„_B_xÏ×xþp¦~"[l%4=ø3˲ko}*2æ¦:.åàרvÈ]™:o ³÷æõ¥s«Ÿ :’­8e3õ5žŸ½jO¡pîèVóžÄ~ÎÃcøÔS `(ãQJäTÒuVö«G:k“É䔦#“g÷„oõ] U`–Ï^0ò±i¼ ŒÖÐ 6…ã~OOèX}íŒ}çÒ~9ØvÔµPf4Àxï:XXÆÂìòPùQf6g˜—£ÈZˆõbRз˜  uçO¨÷ã^Ä¿ë5/v”mÛIEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/big-left-arrow.png000066400000000000000000000022601271170121200241330ustar00rootroot00000000000000‰PNG  IHDR szzôsBIT|dˆgIDATX…í–mhÕUÇ?çœÿ»ÎÌŠ¤"ôY³Ý©eV›ÙƒJ SM{‘Øæ^Æ ¤^¨AAÐÌ7EMÊ0ðEJ–èE‰êeê|ÚuÞ=Þ»{ïyèÅÿ¿Û½×­ö@ïö…¿?çÏùý¾ç÷tLa “ÄâFU_Ý้ð-¢;¼ ï•NØÈ„<ÝÈôš&ïÀâûV¼õú oG<åO˜€7Þ UMÌMâZ³tÓüE÷.ó•TvãŒ@u£÷ØtïÖ¶7V¿³ðáÊ%~{×I¬Ó“"0æDÔúY3g´õÅæ2å+:ºN’1i Àüš‘Ѥ·»rÎC[6Ôíˆôeâœïþc5ÎYpj,p|·ã"mbƒÞþšꪟ‹nˆ\êi'‘ºŠuk Æi¤4¿ö ÖYël(jc ï¶¼94®ÔlÌwÂ^»lãœE•Oø]'Hfúp¸œaë ±¿¾ÅX±jc³h›Íéå÷¼:ê!G$PÓè=^âûß­«Û1cöóDÛåÉštø×á° ƒÅà¤Æ¢q.ÐÃä¬38,Ξ›Dü-wÌ,ß³®vû4éCÛå#A®…@+ ' ä¥'¿,Fì×Ñ&ÿ«ŠòÊçׯlŒ\ê;Kg÷Y!RZBœ KÊô“Ìô2¤“h“Ƈp é|%amXpŒ8­ Xk«…ƒ™}C×Ñ&Εƒ™ú3qú³×ÉØTÐ œkV;¬qíðˆÖ©±¥`îySq޳»>Þ¿só³K_*SÒç̵¥%>ÒC-Ò R „xRààÖ‚0a†ç ›MåPÍŒ‚IØÚŠ9¶KoïíKlÝ÷ÃÞ¤oÊÜ£u¤í Ùn2 "”Eyå‰@|ç‹Ö”@z©RT­ïST #bÛc¾î¿’©ýþ§}‰+W¯ê'®ç¶²Y¡ÑÀò$Ê(_¢|‰ôCÇ9çAê‚Ú)ˆxAÔGjC ÈS-ºmÞ–³‡[o$ºî^ZõJéo݉§ÿDå9YYÑŽ°‚4ÚauXÆØÅŽF ‡ G‰÷vèUº¾mOO_¢vÕSk#C¿r~𗀄ÔÁ{4ÿ›™|Œi»lï5†N|h¶e7vnþb ¥aͪúÈíååœî?„P&—ãã»uСdó$“÷móìçBR S$Ч>ןvžîÙôå7-ƒqçªÊë™æß ¦I`H9ÕybµF{M¸¾]üŒ» /§Î¬ˆ¨²²G<£JT)GcG¸³ï¸˜D6$WІ£Õ€ Ø<Ñ€wîgÝ>p‰åÎý¬«;^µº¶>’áðGp˜ÐM—½§†›p³t2Aêb̶–.¸1½ãö“CÞŘÝYtòtžLîÙT÷ñ6õrøñõÜu5…)ŒcF¦Œ˜IEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/big-right-arrow.png000066400000000000000000000023031271170121200243140ustar00rootroot00000000000000‰PNG  IHDR szzôsBIT|dˆzIDATX…í–hUeÇ?ï{ÎqÞdê¦Î3S Š~—.ç(*- ËúCŠ2#( B7#  ¢Èþ‘D#,„夅e¬i³6Ür*æ¯ÐÍ™S7›Ú¶»»{~¼ïÛ÷œywÝæœà_{à¹ÏË=çy¾ßó}žó¾mÐhE¶™Ql=w½uäõ$O{çÆÂb{% ZÃhâ-r凯}kÇ“­0µnzÞl½íÜoø×ZçºR0¿pñн1'–pöå¿Ë„JÀS.G›«¸gj¾óú¼÷ofe,(¶gÜ0Z$ƒ8G›*9b¸|û…rF [^°ÌZÔß=OA‘mtö™iˆ}úÖwh,ÃS.ÆhÆdOdLl›ËÖ$ë›=¾A-ݺ5 ,^‹e9H$B¢fJaóç©ñµ‹Ö ƒ&;k4SF?HyMi²úèÎ=I̯ýŒÖÞؽS3ì>± )¬.B¤‘¹,…Öm Ú(Z:NÓž¼À£Í:*gláÏU›ÿÊ_.Ÿ¬þÜ=ÞL¯3 ŒÂW¾ö´‡¯]|•Zx( —¸ßÂÅd#ç;Nr®­ŽÝÇ7áYqñê¼w²sFäm›¹ÜYÚïh­´‡@"¥ÄVН”aÿÊtx—hK¶ðÛÑJ¡•Ishþ»;ò¦óòÜ%±m%S||Våªà™«ðµKÂkÃ’6Žãhƒ FÆÅÕq<•Àƒ´ gR?c FC =<í¢M €)ýR áµÓÔV`9ËX¶@Úi ¤é¤Þ ƒÀhƒŽÀ…#Àîÿy±ÉlÙ±®³µýâêÊUÁŠ«è6ÒH+Œ¶À £”Þi )`ƒ-b<<éYü¸6ß—mèLtt¾Y½ZmL_DúB¦ö{)çŽÀ»T!ƒÑá“Ü\¦Ýü<õ'낊š²¶ÖF³àÐfUKêðSé$2 Èt^|`EP¤‚)ù…Lõ»ìÌ—ø‹V©šcb¹7÷i*÷îö©­;õG°ðL gúá$_Þ{jX»å“.u·â%+°‡ïÖìû˜Ëgû/¥É†Ó ;lTËÚ[ˆ§×%c÷Í$`]õE08¡§¯À),¶+dY–@‹»F>NÌGIé÷ß.®©ýF­ü°fºw;2 èЄl#ï"ØUD†fÝÄý¹³¸t¶Ó”ü´>qîXré±Ô®0?r•¶îsI¾B™´B€-†0-g!û÷ï výþkKã>ýÊÉru"ÜO‹W|1õDÀ^á½Ñ Œò¡¬¢Ü=pdïÁúþ¢¦Ãü—A4Š.ô|,÷çcÒ¾dA‘}!/wœßtþlÉ¡õê½xœ€Ë-ŒHû½÷¥@¦ERF³Ðõª677-ß³Z}E÷‹T´Aë—ýÌ)ÉŠX‰IEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/big-up-arrow.png000066400000000000000000000022511271170121200236250ustar00rootroot00000000000000‰PNG  IHDR szzôsBIT|dˆ`IDATX…í–[lU†¿5{OO{jCÑXåD©$JZM¡¥1Tâ%¥DLDÑD¢1ÁKO ÆŒ$ÆÂÁàƒQ á QÒêƒÄ i¤´´¶z¡íé93{ù0ç”rz5>°’•=—=ëÿÖÚkÏ Ü´ë´…;È)ßb«n4Žw½ö™=¾ä)¯õ_ÿÏ*6û¯Ì¼}þÚWŸ~›[ò Þ-¯µ+þ3€Š-¶rJÁ­»ª—=ï .²úáçò­õ?_²)v÷¿°ìMfù&çÐSlˆ_¸rŠãg>#0 ª–¬.ðlðUé6¦LÀLtbéâ&´uk–¿8-·ÒÐò-NCZ»ÿ`Áô‡ÄšX~[{SùâénC:Ѹ­€äöÚË=1gÖs¼†Ö〠"8 ùþÜ!-Xœs×´ùeÍs쮉ŠÃ+PQkß*ž]òÂʲ5¹'šŽ0êÃi˜ö€dÐOGo#Kï{Ò?Ûtêþ;JMõúÓ?P^k/š:c÷3Uã¿¶ÕÓh5<îè¤?ÕCÙ‚•þoçNVÍ\ÌÑÆ:×<^ü1— bkìÞX,þéúU›â-=§éìÿkÌ`^ú…ÖÞÓ<º´&_=—meæu”ncŠºàèó«ÞȤ ]¿_s_ûìj¿©:~nþ› •%+ }µGJwŸ4ÀÚµ˜˜³_VW®/*š:]N·Ÿ@U#QQÄcÏ‚g1J@•E©?{Ù3æšâ¹ÌËíµ `Ä(|Ì~ø`ñ²ê%5±“­ß £àN5!Îs¨¤H¸naxÏħ-]g©\Xm[Û›gݶèŠßX玤5Œli­Y7£hÞÞ5;ó!öã1žáØ™} ¸$=©ºm8 Q@¨*5ÅÛPŽÕ •ý‡?îïè\Wÿ~x0[Ïf_p*Ÿ´tœgûÞ ¡àTñ¶¿ôbAéJ^ 7¸Œø`ð`PPðŒ°{ß;ˆ’Â#$‚Ëä‹‘P·+ÏK_ÆØ$ž‚q\ì?GB;±9’Ž—wN¢„ahï±~¢ @ „$FZ‚aÙöÃRå›”KçIhÆ$½ÞQ†‚:EœàyaÊ&.÷]ãÅ c)MжalTfÏ€¤+ ª¸PPoÔ†¿1€¾ Ï€g$‚°‚—®€s‚Hô>ÉéO éúðm ¼ÔU©I¨Q#:cÇß €¢‚ ‘ˆ0à2Â2iý Ø4AzG™j(„¸èV˜¾¦W_Ì?ˆ¶T0Rà± àq¬äP5û54°Kgde›2&ÓâÉl±ÑÌË‚°CD3ÂC¿¦nÈP˜ŒhÃÿ'Ó´™f¤g<’9¿iÿoûúâã”|~ÕIEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/despeckle-aggressive.png.png000066400000000000000000000032221271170121200261700ustar00rootroot00000000000000‰PNG  IHDR szzôsBIT|dˆIIDATX…µÕ}l]uÇñ÷9÷ÜÇöÞÛvm×­víº•‡µ…Ù±±=°̈‰“±dVS6jâ1èbDÑ F  Ø2Ê@cÚ1”ÛD]]ÙDÙCWº§’>ÜÇÞ¶÷áÜÇóèÞâ”tvë7ùæäwrrÎë÷9¿s~õÜ}Ø)³¼dÂ}˜hf7ªñíGºˆ0G%žß!ý¤æ†[m¹çkö\&oà˜vöøë#ºª7=ÚEf.Ò•óK·ÜÙf?rà»(ªFIE´°¾qaàÃþVÐ3±p`¦Wޱ¸Êxð©‹ÈÑ1ê×;îÙsåµsÞMÇ|¦¡¤9øX39*k›ð”Vz=-÷ÏÀR8¸·Ùü 4|¡õî[]F· н•¶°ÿòÒ7Ïš/Ì)à­>‚Ÿi2„ ÿùÕM[­©PÙ襋–3|ùƒÒ»nÖºŸ#t={¯Ñã§“¡‘sNÒJ¯CS2ÈÁTUÙœáïþÖþüß;0;;¯Ä϶„éNþ|;5‹¥¿åön33N:ÂV!b+™`AÃz%2ò‹‘N.¹cÃsøåƒ–¬ëK7ߺ°È^Ä=¯†ú­@¯´_¹p8–PU³}üaN½¯°4# Ç‹¼Þʆ•_À[±Œ¯öðÆž£Ô9‹X`ˆ d j÷Ct¶iº“=|Ë0ÄgÞt£^¹è6‚}aöï~ )aÒÞ²¯(âóùp„ÃbZ×[€?Ï0mǰQ48"ÕÆûš}äÄ8knVÖ/¡´ªŠ÷~þ8ÐGÍm¦¾êaLÃøÎº6º¯àÄ«xºÚ Õçp.ÓÁSR‚ìõò·Dë|Áܶ¯]X¼¢ŽXdás¯§³òÐA¯ƒ‡·¡\À#0´jÝÀY‡È¸ ¶üx ëw¶cæz!÷>ŠnE1 öf'}ÇÎä4îÝÔF캾Ÿµ t VœŸÞZÌögŸlvÓˆ£eǰãèº9‘°­!O«¡ÎQ }ÃÚ/ã¿f@Ѱõ<Ѹéþíó««@ £SJ&•ÄîPѳ~-A,Åæ^‡(.1†ÏüjÂÌ¥6¬mãüL÷Ÿq‡û5\*šGR3T5F %ÄnHŒ‡AÐrv‡—X MëW=V.9ËOöàök`àW3qCUAÓdtuAHbh9²©4JVÇÔ3yë™y›l¢G¨_ý¸§ÈÝðÎñ—ùâ5 Ó‰HVÓ줒 2¤AJÊ‹‰‚¸<^’1 „îŠf&†uR·r·«¤zó ½x±·ç¬ü+#‡uCð’†B":ŽER(*qõáòT#GR˜Ú(e5k‰úþÉØà~Þô€£úÖo¶ŠZñÈñ¶Î pÇZN45ENµ¡ä’(JŽdé8ࣦ »Û¸ñs…îêÆ®l2…Õ–6&}§Ôl:B`Ì|£ë(_;Â0 Z¾Õÿé)€:ã^OÁZÐҾﱹy Oê&F<Á‰Ó9üô‹ô¥TÌ©hó­O)å®ÿywòm)h‘ÿ.°B€‘ï)ÄÔQÉ÷Œk`º² ÄüXàãŸt!bjÖS‰\1³k­ÂÕ]˜@!âëß4ÁÛ9“)IEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/despeckle-cautious.png.png000066400000000000000000000014051271170121200256660ustar00rootroot00000000000000‰PNG  IHDRóÿasBIT|dˆtEXtSoftwarewww.inkscape.org›î<—IDAT8•’Kh”g†Ÿï›™èL›ÛHB“H¼Õ¨+&‹ Ò‚‹JKv‚—…‹Ñ‚"ü ]R„ŽÆ…`Ku¡ é1-´i41Š1šqFI“ÉeÌ?óOþÿûŽ+Ec!ø¬ÏûpÎËQG¾`s8ĢƲ÷Ô ù‘÷@‡4߯mŽ­Ú°¢¼1¤¹zt›Úú^`qET³´*ÂÇ-±XHsöô~¥SIÕ™JªÚVè|Rt}#Ô×”]¬ê«éŽKˆð뙋·óîLɧ©Ê•­7ÄãM/€º…JDèÚ§NÄWrü“¶ÏUsK;=§»½éÆrìT“ÆÈ%Î'ÿ_A*©vi­¾ýtsG´®®¿þ›§W²þìvj×U‘í÷îßýCŒõ¾3FŽ%ÉÎìÑšdS}k9—$j&]¶toeQ…P5~PνÁ_ýþß âÿ` )àæëRIµ(rŽ;͵«:.iýB `ÆÀd±A϶àÍÕÉ Èã‘wj2[Ô¯VI8RòwÓU{°±¤#e`g!“ó^á³¹ëÌŒ#^ÚÔþU…VJÂo5RÁÏÙLZ[«Pþ">þœ‡Wš¥X˜¡P˜FDÐáJÒOz”ü¦ßÌ'yæÏM¤Ów™qcLLŒ’Ë¥™ÌepÝ)D„Xõ6 EÍЃþ’18o #‡úz)ú¬Æu=ŠÅ<Öèp •ñäݘܺy%oLðe‘ÌëߤûŒêYýQ{Ûò­/ÿ'e±5èÈ2 ÷CC}ãÖòY‘‡áwÒ€1t =øëvå’†?¨jÓ££÷üááŸk¼ëÖr áÈÄ;Ÿ8ŸTR-SŠ>¥àšµœL8òxþÜK~C(„ÚIEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/despeckle-normal.png.png000066400000000000000000000023251271170121200253240ustar00rootroot00000000000000‰PNG  IHDRÄ´l;sBIT|dˆtEXtSoftwarewww.inkscape.org›î<gIDAT8¥“}hÕUÆ?ç÷zïýÝÝ;·¶¹y§m¾ÍérXbC{“b%F‚4ÈòjP©D!Í\…•˜SJ!5QRhŠ`fŠºM§ÓÍ—ME×r/mso÷ÞÝ—ßïôGWÉZ ø…‡sàp>çáá9bÝËŠX¤±\ ‘*¤³ai•½‚ûEÞÍSøñ¬¹ÁÀ“¥¯x=¾Ë*ËÔ÷ VU=XX<Û _;H¼÷ Sc)‚u•eª~_`ÇŽÇœDE5l;‹eyÈ5Ö,ÙºR¼°ã#óØWKÄ=?¢ÛÏÕˆøògceM¦¿¥†‚¢™ÆÕò­”L9äÝ»cÉŠîök•Gö3¤˜€™ö -GH›îš·p¹;;w\T$xø^ÁBJ @e™:_Óµ¯Ó'zݾ ‹O=‡Ëðræ­=ôüÔl [l*µí7ïÚñ­¢Sç΋õ=0eÑŒÉD~?EͳÛàp'…#ª­È×6—‹…›Ëï.o`k¹˜)¡®p긌IS Dom óÎ’Ö­°R¹øç Rh9¹¹UM\ß\.æßUÛV‰½RèsRÓüaOcÌãìi£ŠÇ¸Ú×ŘOÆ“õLwJ1}=íÔþ¶?Ü×ÓÑ;eÁ yùÁ[V‰“F~¦|7!mdjw4D·%o½Ä3ZÅ4=膅Ëûš§ˆ?®]Çî Ç¢ƒ«G~¬‰aÁ·f¯ª¾o«òƒ”Yù"çëBqI4ÍÄ4=¦M3Ñ5/º5¸Ìçtõ/‘+ Gzc±è*`{°BF‡lY)¢¥e¯¡®H'„¦˜¦…aZèzÈ(n·UO³ˆþ‹úê}á«jãR:k‡5ʨ€¢ÒìF52R"ùÛ€¢ ¡™"¡(ÒŽ!¢'ñ»êyüéRÏóeËüR(+n·âß#vwµµ8†k< IÄHÛLì„®†Ð=éíéÇ‘ QT/—«£šBU°B v¾o>&¬»' „ )b±RJl'…H¨—[Áå+¤³­[/ábýñDÓ¹ê–xÜycXÇBeñ‡ÔGBáK­MÒðNAQ!·‰†{Ñ]é E$áÞ+xüù¸ü%ÔÚ?ñë—zzí’`…ŒÜB(Bd>ÉÚ³µÕQÓ*FÕ31<™„‡ˆ…¯’’1în•~ÉñÃ{¢§ëO¬Ü˜Xðöç(BŸB(I¨¬¤R_ÕÏÜhïèi¸Pw a¥ÏÅqâ¸}cé» ª‘‡acÿ®õ±“§šö-]/oíÀLÞµëvÝ„Þä7©”@Yï½ÊÍz"#;7S®lúÚi½Þl_<ßÞwÔÙ°ûÇ€A` ¹]wôXáü€')wq!£ƒ/‰m3çÌK‰EÚ8S]ëìrŽ®¯’Úº¸ D’ 7)¥=ìIFd.ÀXô"S¦?Äq›S Í|¹i5@ˆC@HÈÀþ¬Ãæ(Ì\ ŸIEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/file-16.png000066400000000000000000000013251271170121200224560ustar00rootroot00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<gIDATxÚböÿÿ3ÀÀ? óÇŸ ¿ýfxóî#ÓŸßÿÞþüŃ™…å Ãí[7ª3Àjˆ…[SM†ƒ üöã×?†¿îÞÌÀÃÍÉu÷ÁóýOž½ egg?€¬ €˜°FFF E ŒLÿ˜˜™$$E¬uEÔUåw²²°„üC8– €X°’gb66†û3¼zù’‹›ƒáÏï¿lŸ>~Xýïß¿" t?H-@auØL@WMbøõû?ÃËïîÜ}ÂÀÆÁÆðåˇ6˜:€ÂêíL@oAOWáçß ïÞèVV6†S'ŽÂÕv/ñïß þ2|øð h㆟?1°±² `a`ff†« ¬ü†Òç/¿þüùÃðïï`X°½ŒÞÿXY€lF„Z€Â0à7Ðæïß3)°g˜˜X˜€šØ€®út+Ô“@,0ößÁÌßþBc€ h;Øæ?ŒÿÁ&ÏÂŽf X4üùóî4F ­ þ b„¤ þô60Y003Â"ˆérȃ(-£&3Pbb{ƒj;„3 ˆ fÂÿLr˜BJ®ÿai÷?$Š‘½@,09nVDnbp˜ý… 0Mýùû—á0 Y€ 9ˆåã§O_Û:'ÿabbZòìßÿ@'ý»’3À61#0Z¾|úüf@€IÛ€6ŸÆ©IEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/folder-16.png000066400000000000000000000010251271170121200230070ustar00rootroot00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<§IDATxÚbd```šÝ,ø— ¤Ö¾×R·øcÈÃ@15ÿÿóã‰倘 ˆY ˜ˆ‘õHá÷—‹'–^’Ú{ ¹ €Àüûó"û(ÆÈ¦¾ß5h_âd@~½aøóýÚU ˆ@ïí‡@P¼kÆn#.  2Ä € ü~úõéÔ+þÿÿ¤}ó ˆ‚Ù o{±‚S,fŠ+@A xË¡?00áùD3ˆþ ûû¨î#Ìa€‚ðjñ'†ÿ@[þÿûÖv и+ Âä5àÄ>Bbh H(ýüÿ÷j;Ä`À®€€B‰FpXüÿq R@ ú6äïßß( ÄéŸ]qDˆ qú?°Ìß Pï€Á€%K-`¼’°¹ H­ ¬@lÄÎ@,D¤þï@|ˆ÷,c€hv¨aÄPî΀bü 4r@€œô2s˜^IEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/insert-after-16.png000066400000000000000000000007141271170121200241430ustar00rootroot00000000000000‰PNG  IHDRóÿasRGB®Îé†IDAT8O½S¿KÃ@~w—´–R#-T: ]Ôͱ£.vtséhÿ‹fÔÝÅIü\\\ADÚI¡ŠPj~•Vâåž—Ô+¡iC'_x¼w—÷}÷½w ÀŸµZ- ­ßïGnYÚ¶¹ã8èºnä¡…µ §©DÅb±8½•ºžT*•KÓ4AËÚÓ¹ fäó?P* aÅø†ÚÎýIÑxºiPƒ„3©W3ˆšœ2÷ë GöúnGÖ®?èU¯Ñ+4У¹Ä äA„ˆ\§we k@è0ê‚ ÷|>ô2ï$BphŠ€’O`´+Á›@´º|À"¡|T´A<ÎÕ1¹ÊÈ !2'ò$ÔÉŠH`®À RÊØFÎiWgA{¦‚¸ ÛÀÅ»ÜâÀ°-#çkh;Þm®üúœÚB¨a|ÜÄu‚7I°çW/´Ù¼8šÛ‚je À± Ö0 èõÌÉõÓE¾¼éÁÆ1© â*âÃM%ø÷ŸéLZºAª ršIEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/insert-before-16.png000066400000000000000000000007241271170121200243050ustar00rootroot00000000000000‰PNG  IHDRóÿasRGB®ÎéŽIDAT8O•SÏKAþffÕDtCA¨ðx©n=Ö%ÿƒ.ë¿ÐcÝ»BôtéÒµ"BOE„X憕ÍÎkvudqMê-Ë{û~|ï›7û†R©TªFcÓ|u"ñL¦‡Yû…Õ˽´}s |]°¤zå•J%úMº/ôÑÙ¦~·H¯ký÷Vþ„œä9¯`ê!Rñ$°/±êÔ¤¯ú;ðƨ´ï‚rV?1z;ŸIŽèºÀu%óÙ•ÍBqkÂ_w?äI ›ÁŒëʽÜÂZ©ÕåÖJ®+÷Âf0ÓÑÁÁT^–¯M|l€re»^zQ:ÖUb§Héå[[(¥—ïÆý lÃÝ2LÔsIEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/layout_type_auto.png000066400000000000000000000010671271170121200247240ustar00rootroot00000000000000‰PNG  IHDR szzôsRGB®ÎéñIDATXGíVͪAQ^ç¸Ô ˜01À˜ s"àðRg` ¼‘’¡™'P<‚‰üä'Å )µìsnNwwuö¶œ®îíî’d¯ïûöZk{)ȼq©oä6©—€år Íf …„Ãaðx< C±X„n· çóù¹¤= Z£Ñóù¼Ñ+ÂO(Â~¿/‚´þ™_‰UUÅjµŠóù/— îv;lµZ -qŠ¢`»Ý–Ƨt:‡À“É}>Ÿ%‚•g³™PÄSr¹œ-`½^çJT.—ÐëõlÇã1' ‹9+`¿ßÛžN'N€¦iB2wFÖ,Yƒrpn·[僧ÁF"‘Ÿ09BæBR·@XH¶áz½b"‘°zÀåráb±†:& V«q È,[Hn¾ÄR»l6G¬T*¹®ëÒ°dÓéÞ·êd2‰ƒÁ@šü¥ ÜI£Ñ(–J%4,ÊRL„Å3Šn1:ê„s¼"’²âG'{5õwÌÿ2°Ùl€ 'à÷ûÍïívKé¿ÏÊÝÍf³œó‰&%;’°ÙØðaÚø}8HY • •JqdétšDN.ÁjµÂL&ƒ^¯r¬×kJ%ÍR èÇýI*ÁŸp6Ñû÷ƒ78IEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/left_page_plus_offcut.png000066400000000000000000000003171271170121200256520ustar00rootroot00000000000000‰PNG  IHDR üí£sRGB®Îé pHYs  šœtIMEØ 5ß5Å(aIDATHKí–A 3úÿ—M BZí´eÇ=$ªÚòžˆÀžIåcåÚvÏÛ„ž¸rahRÑålT;MET À¶ˆŠ 8ÀQ4>µ¨üãU0}WF'*½ÙIEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/left_page_plus_offcut_selected.png000066400000000000000000000003201271170121200275140ustar00rootroot00000000000000‰PNG  IHDR szzôsRGB®Îé pHYs  šœtIMEØ 4s©£•bIDATXGíÓA €0 DÑŽx¤¹ÿ æNQAA»*íæw[š Mž’ª6áØjõÑj›Ð»Ûby€ýÏöÐIò[OÒu÷ Ð{04ÙYìÞFÀP€ (@ P€‚å pÍÑRä“q§IEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/left_page_thumb.png000066400000000000000000000002671271170121200244440ustar00rootroot00000000000000‰PNG  IHDR¹ êsRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEØ Ôú ½7IDAT8Ëc` `d``øÿÿ?>ŒŒ0QÅ-,ÈF¢ÉáwvSˆ×3ê–Q· ·P'7Rý*$.$òIEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/minus-16.png000066400000000000000000000003671271170121200226770ustar00rootroot00000000000000‰PNG  IHDRóÿabKGDùC» pHYs × ×B(›xtIMEÖ ;"\„IDAT8Ë푱 ƒP†¿iÜÀ:+è#­mF ˆ­sÄtˆÔ¶"DóîO!ʳ³3…ÿwûãæ&ÍÊH6暺¸¦§`˜áøþ?ñ¬S-ÛF䀨IEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/object-rotate-left.png000066400000000000000000000016171271170121200250110ustar00rootroot00000000000000‰PNG  IHDRÄ´l;bKGDÿÿÿ ½§“tIME×';h_y1IDAT8”]h[eÇïI–¤iR»ŽÒZ–v¦0ƬæB˜ÊaõcójÙ)f¢#Þ© 8ÁAÙDPdø5‘Ì Ç@)b/êðk ¦Ë†L³í‚BKK3›¹ØsÎûx‘sòe²ô‡ÃyÞçÿ{ÿïËëØd̦ð÷ÄxÂìz Ô>±6¶iÇU”aû‚á7Ç>*Ø,“K‡y4,´8ÿÎÁ[¥Ë_ˆ½ö‡èòšˆu«–%™›2,OãïK^ ÜóÒèóÙð–¾íÈÚoÈÒ÷ˆUBt•ƒ‘8†ˆ®òî¾”¼|×î}/ŽφYû}ís½©S¶Ï™<ØñÊȳgºùkY_쀒Î`u¥+üáèsDX™Cn^¯®©èô'Qá»Á¨ J6èöàüa&z û·nG~Ÿ­ºQ ½»Yž>U^ùñ¼¶ŠKáÚŽ†í ^‡r{° …žH¥#²zÑ•Z÷Ò³“«¯îýÇ^_ymŸIžåÏšJãAÛ‚'‘_¶+àÁ½,MŸ¼mÿ½zâ¾Oí·[êê¢õ;ö6£k+º¼ îH©HŒ•‹_ bëm ÖŽRþXëµ¢/ˆU\7¿8g6ΊŠº;X4×sæzCoò,ꎎ÷œÌáï‹×\fÀº0ÃØ»ó`T$vñW^¸·­c›báÛ±÷ ¨ÒuôÕO@œÝ>ŒÄ$2J>E*™åB+°ÌrE*Ÿ‰!‘8Æ®4ˆT&ÂKŒ]i$'Ÿ‰qsƒ‰d–Ú96¼o2ËÅ¥Û<žÏ #Ñ8Fâhå]ЈÆHE¢qò™a®9øÈ9~‚n­à Ý@ïgx2g"NqQtaFì¯'EfÄ).JÎDNïgú^Wð®µÙuˆ}ÀàûªÀDÊ«â$g"§Æ™F0èöG]ýÿ\78v ÞÏdÎDÊóßIÎDÞJaºÐ‘Í8ö~ ` •ñ«æÇñðýýœÿe™CÏ|ÃOT^#°›ÒÂ{ÖšÀ^ø_]vÐ5½À¿.T\€S—vó´7¯ùšz<ÇÒRQÿ†:Taã/î IEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/object-rotate-right.png000066400000000000000000000016531271170121200251740ustar00rootroot00000000000000‰PNG  IHDRÄ´l;bKGDÿÿÿ ½§“tIME×';h_yMIDAT8•_lSeÀßííZ·6)QWÈöÀ!5>€‰ƒ„³|Á½¬%s#Ñd´ëú?âINr¿óó»çžs¾ï* ;âyßzœ:ØúšMsèålZ¹n>Nµçû}˜<‰DÃZN²+"¹Õ¢Ú™Gb>º++·¾•鮯Fܳ“ƒø/<µö`„]V°oŸ_ßÕœàô <Ï¡6t‘{x©O©ÌÜôé]³ï4kUÛ\W3 é’ˆbßù%¶¿=Ñì{þåc“CM'Ÿ,`gk«•AæoƒŸØúÆ…׿m§¢!öÔ¥”f"¢k;®ïZY$õ7$ dy6ÿêÅ)”ÞB盟yþü°ÿœÚ¡@ê‚5WÓÑ#¾S¥SáômNmÜsHkë?æVîVdî÷<°íE2‹iþù²û|f°2¶ºy%Òs‰û=Í÷Ì¥…·æ¯žN«-»‹•Å)\þ ˆôÖŠm.Šä®üsã;Qžv°syÍ, =µ±Ì§ÿ7¸ç÷sÉùf®upn¥»±-Ë]+F¯e„ü)ËÀíÍ?Ø9*íFh¹Ì7øª.`ç™[è>qmÇ#e`;¡{,^\›ïòÇñ Ð`*Œ0½ÝcqÔÊöíÏA¬jGå@ ¼Žx:‰¶ƒbopœŸëÖ88Îo‹Yúb£íˆ§­kDʼZ×âé(ƒBíæi€ pí»ÂÍ;IÆFýˆ·-0 bçË!6Z`ñvõ3—æ•à87ê0Q@Ðø€MÀ3g÷3`„+9+v<"æbÇ#b%gÅ!—ûyµàßRˆ¯*±¸/Ð lÚÎz çá3"™±’3b„Oûx­à×Zˆs×ʺfÆÀV óÌ^BFÉLÿ"F9»ŸÂþ¦ÊŒkM…8ÉÏx©:uî»_hã›h‚C#×ø0+4Ø.¡5Øšj…/RýÛp]! Ø€U¢ÅÿaÃÛ­¢DŽ )Àªîb€¥yòùIEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/play-small-hovered.png000066400000000000000000000027641271170121200250300ustar00rootroot00000000000000‰PNG  IHDRàw=øsRGB®Îé®IDATHÇ–ÍoWÅϽwæ—ø#±CZ'Qb»4Ž¡qóT b¥jÕMIÓ@£"!6Hl@ð°dÅÇ©A DÄU‹*›DÎ"Ôi,š`'ökÄ~gæÞçañ¾‰h(#ÝÍÌsFçœ{žþÇõÆéSÞùgÍxlÔ°‚,\a<™NýÍÙ•'aÈGÝ|ë+oõEñ °/c6¨j˜Øã×DÀ9‘Y_×òüÇo¿ý«Å%8}úôRJ¿v›ªJoo/Û·wSÛÒ@ÑhP_^fñþ"Î9DàCçÝWÏþö쟟Hðú©“c¦¼£¦!Ë2žÞµ‹O=÷mmm‚šcD5!"TeÅôÌ óóóTU…Åñòïwnâ¿^ãä+)êSL®g{ ËîÝýxU%¥DJ‰XUeEU•ˆ«+kLOO[½^¼:q¯ž;wîpúK§öUEBަ&HY–ܸqƒ‹/²±±I^ËɳŒ,d„ð.P•%{÷î%ÆDJúš3ÓcjJooó óÌÏÿ“FÑÀ;OÍõ(=UU¡ª,,,páÂþþ·Ö Ã{GL‘;zQSÌôXPÕ§U•ö­í¨«««¤¤¬o]§«³“<Ï1kêüˆH¤™žÉÉIfggyáð Ôj5Ä9L­…¥¨ê® fbj8Ôšæ™5%yøð!Yžsåʲ,#ÆHY–²€ª2wû6†‡‘V™8L 5sXë,Š‚¼–£f˜¦Šš¡IñÁcÍ2"¥„÷žªªØ½g7‡¢»«‹µõõæ Eñ¨·VÆuÏ-//óLÿ3MT‰)‘b¤ŠUë€9¼÷„8~ü8ƒCƒhJlln>>éÞ{–——›%a\wãℹ¹9«Õ¶ šˆ1’R¼*Ë–ÙMYúúz9sæMFFbf”UIJñqGÕò-Üž›3qÂxä¼ ß«×—eiq‘mÛ:©ªŠà="ŽFQ âhkkãàÈAF’å”UEYVTeÕ*;ÏÒÒ"Ëõeɲ AþàœwvÑ{Ïää5ZeVPÍM:»:y饌Œ¤Š%ëkk4 Ê¢¹§( bUÀääµ–”vÑ;wÇÏÌÌÄg‡†þ¥ª_,Ë"33ÛÑ»CbŒ˜1E††ɳœ#±jJWV%eÙ\UUá¼cjjÊæçdÇðíñññipëÖ­[û;UíÅúÒâ};ûDS"V‘”´©s•¨”MÌ SSÓ6õþû€ˆ÷îûï¾{é§ëàƒn_Øß­fŸ­/ÕY˜_¾Oîdk{;eYP–1VM㫈™R«ÕXßxÈ{y»wîb Þ»]¾<ñ'ŽÌ±±±¦”¾nf5MJÿž~”mÛo6`åÁ ³·fíÎ?îˆó)¼÷?™˜˜øæÇý±±±“)¥o©ê‹òB°Z­ÖœÉEAŒQ¼oçÜ_½÷?˜˜˜8÷ýUœ8q¢ÃL?cúš™½ªª½ÿùÜ9·("òÁÿ܉\¿téòúGáüwD„/HËIEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/play-small-pressed.png000066400000000000000000000027701271170121200250360ustar00rootroot00000000000000‰PNG  IHDRàw=øsRGB®Îé²IDATHÇ–Ío]WÅûœsï{qÝØÏnÄIíÐ(±@ã$Eª„m:)ŒP 1Ab‚?€!ÄÇ‚3È 4•!:``%4Rì:iqˆc7~vˆí8~ïÞsÎÞ žh(gx>Ö–Ö^kí#ü55=5¼ŸP³ó˜2ìyAÖyω\Jšß¿qíÆÖÓ0äã6OŸ9=Òív¿|³ãf†šÙÞ+Á‰ " r øu³,rå¯ïµ?±ÀôôôTõ—À˜ª¢ª8p€çž¢¹¯€n§ÃÆýû¬Ý[Ã9‡sàŽ8ùêë7þôÔ“S'_SµwT5EÁ‘Ç99y’æ¾}‚ª’sFsb]óþâ"+++ÄqÎ%çäõ¿Ý˜ÿ㘜:y>¥|Q³ºááa›šž’ññ1¼ˆ€f%k&§LŒ‘ºŽÔ±F¶·²°°`÷7î‹óN½soÌÏ/¼à¦g¦^Œuº`fû‡‡‡íìËg娱c4Êï¡(pμÇy‡óéÑBŠ A8xè lom[g·ãÌøü¡C‡.´ÛíMê¥SÒív¿«¦¯•eÉËgÏÊñã'ðÁã½§( –ï,3Ô"ïÎ{œÈ†’Ñl ÉG÷>"åô¬©Ú‹Ç>õ{¿pÿˆšþ<§´obâ/}æûö5ñÎá} Q6˜››ckk“þgúj"‚HÞ T•f£íµ5œsÇc0^Õœ‡BLMNÑßßOO}‚óž² F»½Î½{kŒ1;;‹s3Ȫä¤ä¬t»]Nœ˜àæâMrÎÃ"îU§šßÌY§¯¯q‚÷Á{B(15T•ªª¸~ý:/^¤ÓéR6JÊ¢ ( Bxï‰uÍÑ£GÉYQÍoúVkðû)§ÉÉ)ªºB5S– š&!ʲàêÕ«¤”¨ëš”›››,..‚Ak¨)¥žÂR¤(J–n/áœ{6¨ÚaUå™þ>Ì”­­-RÊìôïÐlQ–%f†™õ< ŠsŽ#W®\áæÍ›œ9{†²,'`=¬žIm4(&f†à0Až€=z´CY–ÌÍÍQ)%B„(Š‚¢,È9óá25=¹§)zXf(æ‚À¶˜ Vu—FÙx’9¦Õ‘ñÁc{9”sîq#ãGÇ™e¨ÕâáÎj† Tu1C`;`v Üç6Ö7xaì…^h&åLJ‘˜Á‡žÑB „À¹s瘘˜ çÄn§ƒê}Þ±±¾âÀ¸æ¹„ƒ¥¥%k6›äœHé1x¤®+Bè.Fxë­¯033šRÇš”")erN4Mþ¾´dô|x)rAï­¯oH{­ÍÀà 1Æžc£Û­pÎÓl6™ž™æôéÓEÁîî.uŒÔuM¬ã^ØyÚí6ëR‚üÖ‰“;†½íƒçòå+ˆ@¬#U]SUN‡ÁÖ çϿΧgfˆuÍÎÇt:]ªªKUUT{EDàòå+øà{Û‰,û¤¡V럦úŪª 5³‘‘ç%¥ f䜘˜8A RN=Úb$¦šº®©«š:FœæçluuEDdׇðíÛ·o/z€­­­öªÚ+ëëmÄ9<(9'RL䬤½ÞĘˆ±&Ö‰ºªPSB(XXX°…ùy@Ä9÷ƒåå»?{×ÛÛÿ0000¤¦ŸÝXß`ueUŽÒ×××£ FRL¤Øk¾ªÒh4x´»Ë»~—ååe Ä{ÿã•••ï4"gCŠ×'þ:ñøyòI7_?þz«ÕúèWPNIIª ÚùJ0"# räT£VûéøÅ|*ÁØØØçcŒ¿SJ¤”ؾ}[·öÓèÚ@k}Ÿï>ÆŒ1³Æš¯NümâÏÏ%88zàí”ô\JÉåyÎË/½ÄƒèÚ°AH)B Æ€ˆàKÏ•«W™™™Á{1&#_øðï“ú/‚ƒ‡¼ªø»¢éßÚ¯‡’Ý»‡p.CbŒÄ !PyOQz¼/–—W¸råŠ..,Šu6YcÞœ¼|ÀŒÝå‹ê´&íéïï×7Þ=­b„³NÓ‚|aaQæææèííÃ{ëÄA«U`Œ¥««‹±Ãc;vŒ<ÏY[[£ôž²,ñ¥ï„ennŽÅ…EÉóA~kŒ5³Šž±Î2þ—‹ˆÐ ³‚¢(X_J_³÷Þû2‡á}ÉêãǬ¯¯S-Š¢ ( *ïññ‹Xg=c¹k>|šÍ¾¥”Ò‹¢•%U}qÛ‹Bx6š###í) UUá}{ÅeY¶ÿÀ{Œ5LN^Ö™™;""ëÖ¹ïܸqsÚ,--ÿ£·wó¦”ôèÂü.0î0Ç9‘ÈxrbD™[Õ"[-$€mxÞ÷\9X¯wÆæÍVëù2@´´@°ÂPØiAkÈeRñ€jt&åR€ÁÈ¿&ROåXNO†Ó…4û3¹âÿòóÙôkgŒ‰ªµO¶›=TrK ‹²wŽIEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/right_page_plus_offcut.png000066400000000000000000000003161271170121200260340ustar00rootroot00000000000000‰PNG  IHDR üí£sRGB®Îé pHYs  šœtIMEØ 1´$Žì`IDATHKí’Á C3üÿ_¶ ‰h$I—yŠœo3+®Dd¹ñÝÀ¹f®FÒhgÒ´Å2Z±šêø3ˆ:DDD @SDD0ED @SôQºé nÍr+JùÈóIEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/right_page_plus_offcut_selected.png000066400000000000000000000003201271170121200276770ustar00rootroot00000000000000‰PNG  IHDR szzôsRGB®Îé pHYs  šœtIMEØ 6*ù"dbIDATXGí“» À D¹ˆ‘¼ÿ·S Ðä"ï:[¶åßÓû©L’TìÅ=‡]±Ÿ+UФ7Pûº»6—sEÄÁŒkªíáÔï€@@@@@@Aú¤7Ð}†x%7IØIEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/right_page_thumb.png000066400000000000000000000002711271170121200246220ustar00rootroot00000000000000‰PNG  IHDR¹ êsRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEØ 8³¾I 9IDAT8Ëc` `d``øÿÿ?>ŒŒ0QÅ-,$8›‘MîFLÁã¯Q·Œºe¨º…:¹‘:[$*$9ïNšIEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/single_page_uncut.png000066400000000000000000000003021271170121200250000ustar00rootroot00000000000000‰PNG  IHDR üí£sRGB®Îé pHYs  šœtIMEØ 28Ô hTIDATHKíÖ1 À0 Á\ðÿ¿¬ÖÍæ ú_¡ÌÌÓ|o³ùß»>`m$9Üi3Ïõ &B"‰P L‘D(€¦H"ÀS„DõÛ´>à 9+ Ë6IEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/single_page_uncut_selected.png000066400000000000000000000002741271170121200266600ustar00rootroot00000000000000‰PNG  IHDR szzôsRGB®Îé pHYs  šœtIMEØ 8HÎ5NIDATXGíÓÁ CQ1ŽÄþt'H¼êÕôàgh€RÕ0Ö4ÎÞ£íÖm™ùd1’޾ñýp (@ P€ ö°h“?%8ÝçIEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/stock-center-24.png000066400000000000000000000013641271170121200241420ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDÿÿÿ ½§“ pHYs  šœtIMEÖ"ÊúJQ5tEXtComment(c) 2004 Jakub Steiner Created with The GIMPÙ‹o@IDATHÇÍ•ÏnÓ@Æ3k8!TUBH\°”TJÜcó=€H¹Ñ>>pAB†×hàd©Ç¨‡ò©¹‘h$_¹…ô@ÿ,¯Sã&©A•`¤‘=öøûvf¿YjšÆÏ×6¶?­mlÆ_?|®ûÖzí  Ðsñí½VР½Ù¤4z·Jõ#¦#†éˆ¬ÉmWÀ~rÀgKKM’ä%ðªÆN·Û}÷ÇÌI’Ø:–$‰]…ãÝDôeøñª\ƒªbÔpÿÞz½MöÃxËã•+T jL~u¾`f¬Æ[s ZA£xi«{£¢¨Œz5xÆ F1F«ÀÖÍÉ  ñ€A;hÒÞlln\“âÜä×<.Üü&–gÝ'.ŽÓ“ Ð9NOK1L€ù5î'´‚FÓð²~tä‡qg˜ŽY?2n_æƘùjU4Eóé¼EÆ}k!ž¬ÍUä-ùy‹´è¿#½º7RUã4ëGš-—iAÂx<æÁú#Dr‰(*‚hîzuÒxÀÌy!»rNOOß¾¸©ù“Éä­[,:¤r/Žô®kÓ]·ZɵΧÀ¥ósà§«‚eX—pYŠ `ü0þV:¾–¦Ø–zo×ÅÃYieçÀð£¬s÷ì̽¿pÓ2ð²ÿÁ¢Ò/ü0ž¸ ÅMüw^ä\kÍ*•¦€X;Û¦£=A¦#¬í:Ðj¾­ý?¨Ú㧯wDtØÉúÑûE`ÿÄ~.çì#¤]Â?IEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/stock-gravity-east-24.png000066400000000000000000000010441271170121200252740ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDÿÿÿ ½§“ÙIDATxÚÕ•OnÓP‡¿÷DZÛ-…%;Xå^pÖ Úp–"U=\ `Á²wD(Î"iÓÄétóŒç5Ô!1ÒH–ý<ßüÆãøßMí1Žv×7€ì3É7. ï€ýÓ,sU»'""Y–U—¢©ÉºOð Þ=—¥Ù:¨Ôskm·Óé¨6 0™L>'À#_)Œ1QÝn·€8Ž+È9ðÎAš t* Cðd Ъ֦ I’äÌW"aµZmJ¯uCUçÊWÎw(ùج×k‹óù| 0vù-eYþR¡ÿæˆã˜,˼Ϫ„6¾AQŒÇcï ý~ãÀp8$MÓ-uu«N’$ù´+c¡( ¦Ó)yž“¦éoUÖàð 8ôtÓÀüøp_Àð (€ Ñ¦h?¾¶”À ¸ôühÿ|oS"ܰZûÊTÁO]æcà¢-`—=^ÀOàG¥v4‘çy5*Ä#Zk–Ëe«Q§ÀC—œŽkûàÔZûº×ëÅqlÚ(àÚúÖRµî;r .‚ ˜‡a(ûZ™Ö•íÀZ{EÑÕl6+÷½Û `”RŠi·tÃtvHÓ`IEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/stock-gravity-north-24.png000066400000000000000000000010401271170121200254660ustar00rootroot00000000000000‰PNG  IHDR!-bKGDÿÿÿ ½§“ÕIDATxÚÕ•¿NÛPÆ×ql¥Ž‘"„H¥ªK'$–nl)¼@$fØÚ©k7ž [Þ‚GHž"^BC24ƒMY6¾,ÇèÆ²ÁÐ#Yºò=ç|çÏwÎ…ÿ]TÉ?Ëqåyžr]—8ŽY,$I‚ÖºÒ‘ã8xž§]×ÕÓé4«ÒëúµŸeY—NÇo·ÛvU6úµt³Ù<²mÛWJ)ÉX.—Ìf³k=H’¤ Ì%ðdê4€‹º¤iº©L¿SàPü­5Ymàðø Y¿Â0Ä÷}†Ã!½^àp¬$Ò XÀ=ð( kb>Ð>߆áK­çó¹ yÄ×ÀWà‹èwÅÞÞD]8/:Ï‚ 0A~JIZb§¶éYÐQ•2¦ä Øßù]«œ×€{ÛdÐ(cH.“ɤŠAßLö˜M-“i\žöm~aÌÈ b 6ee¬)J& €¿5A„Àø¤AÊŒµU]–±8ŸºY‘ûUÆÚsU£“IôOU µÞû=Ø@o¸«»ßn0¢(b4Éð&ÒI59œÉîiìúd–é˜[¶%œ¯Ýš»ä¥lžœSYÍ+9WÊ3Á¯eæ]ÞX IEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/stock-gravity-north-east-24.png000066400000000000000000000012261271170121200264260ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDÿÿÿ ½§“KIDATxÚÕ•±nA†¿]Ûw‘s6qP,Ÿ¼]dEP`ë‚ U€Ž&/@Iƒh) (4.è"ä H} Ä3ØŽpœæ|V$'Þ¥È]tgb»AŒ´Zíhæÿggfwà‘¢“ ½˜ËÄv=Éèih0ïz\riÑI`¬µF1_:Îü®]`èl2ƺÝîLÀÕj•N§+Àd¶;™Læñ¬‘câÇ…W$ ° ì†Qä)¥¤X,²ººŠã8AÀÁÁ¾ï­5Àû)ç\rÀҔݮŸ]À˜v»éïW¢àe‚à$ÊÝp8Ä“º†Ã!À&`]”>9¡Ÿ·ã…;—ÑhD¿ß?s–òòÌRJ],÷òùü–R*•$Çqì™ ´Ö¦P(×jµ/¶m?QJMt. vX6M9 ƽ^ï–Öúe¢“vÌS|ßßÔZŒÀ;Bˆ?ÒÁ\wOqð0M»J)ã霙`3 \ñjeeåC©Tzô·š\T쥀o—Ëå¯ݵµµï­VëD)õ:ô9lS¾íÔÛ<â¯ò¹eY÷<Ï»Úl6—÷÷÷šÍæ²çyë¶mßn/b¯ûppLÚ_ôx¨”zΆoRʶã8?\×T*•±ëº§ŽãœH)BŸà-Pq~Ç“n’–uàP³,k©^¯ÿÖÎFCض £Ì…C¦®‰'N²þ3#)å±ÖzÇqra3UH»ŽãÜh·Û—ºÝî9Ïóîä@.VÒ.d¦ï !BCà”ò­iš\×U*•ï®ëŽêõúKMÓn¥Œ¿V½ëhV/B‘×€ŠÚÉðÐ ÃÐšÍæ±ŽÛh4ôb±¸lWTxŽå`ÑÈœ;þT;Ñ¢(: Cà0ý³]ž`†ÿ½õõ”sicLiIEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/stock-gravity-south-24.png000066400000000000000000000007231271170121200255050ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDùC»ˆIDATxÚµ•ËJ1†¿ÌL‹õ¶¯ø‚ Å…k_@¡kû"ø$ñÁºí¢K[e&”Qg¡Ó¸h"é8I`úC pNÎ%çÏØ0„‡o ý ôZ4UȽT}`ˆšêRH)M’ DÔµír5ˆãØlÏ€ºo;ívÛl·–WužDÖj®_lŸ—Ó4e2™Ç1Ýn`t\g*ô }””r¥ê,ËTš¦j4™Ê_€;àØwÕ·8žŠI¤”eÁ¯´äJÝHìx¶“ŒÇcüè×Ú¯£Ï>ªùIUö:•®ìYǨ2•ŠŒÊ²Œ$IÈó|m›ËF–0~ãN€øò"ÿª® ‚º+·z&aQ)° œ§ZŽ}´j¡«~Þ€@‰zvôGÒòü³ð |ð³Nëm¡ó…³à5†_Í'2J¥¶ZkIEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/stock-gravity-south-east-24.png000066400000000000000000000011001271170121200264250ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDÿÿÿ ½§“õIDATxÚÕ–A‹Ó@Ç3ɦ¢IM-È µ=xOíiŠ^,îM?€¨gñ(Š—=xì—¶÷+ø-zHm¡=H! Ú¸aƃ Œ1Ù¶ax„LæýÿïýçÍ›Àÿ>¬ŠyÈË ( x\“RZ;âX™]ØÐZk hà©ã8~¿ß·ÍEƒÁ@4ðu¶V'Àuà J 4 -Ëz6[“ÉÄžN§Âqœ‘x3÷É×÷¿, <)#ñ}ÿÅx<¾Ñn·ï&ØQ ÁCàVÙþ Àž—‘´Z­w€N’Ä;.!x Üìâ&kàðx)„ Ûíëõú½Ö¥””]!ue¥@œ¯L’ßû~9ez.¥ŒlÛ>Þä$¥ÎRÖ"ÀuÝT)uøxQä®ën%(Ó(Ši­Ï’$a³ÙüñÍ|÷<0 ÷#R ¥ÔY,—Ë¿Ò4eµZåìŸRJôz½m¾_â8NêH$€CàЖRÚÍf“N§ƒëºÄqÌ|>×aþTJ}Û·ª>m`g+´có Ù…Šz[·Þw‘HÌf³Z@‹ÅÂì•—ÏI‰ ûTlv¢ ‘—éw\)ö•-C` |ÍZŽ*W3³k(•ß3;§"BY˜{d?Õ?û«ø^ ÜS¸ðbIEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/stock-gravity-south-west-24.png000066400000000000000000000011261271170121200264630ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDùC» IDATxÚÕ–ÏŠÓPÆ÷&cËxÓÚ)Z•ì…Yu!ή€êj^@| Ð+EèB|‡îÝŠ>G’é€iâдñÆ…7%¶IZ«8.ß9ßùsϹÿ]¬¿èKrùP–€¥!·*0y¹ hà!p°«À'@jô)Ðv«V«Y½^Oü‰”@š¦ifóØ+#‘8g°Ÿ !Žûý¾3íÑh$êõúAïy^fspLÙVz² ¾›°Ýnƒ½f³yTâüpËd±R^»€à~vÇq ¤­Vë¤ÄùKà^UEòβLJœ™’.z ·¹?9À÷}\×x¼<àd#)Ë!Æù[ࣔҷmû\Jóus°¥ÔºL]¥Ô×N§óM)•l2h qgål:.4Žc€Ál6;ìt:»ŽãXËM­”0 ß§§§Øöª‰çy¸®û&‚aÎ¥”‘Ö:݈ Š"€wBˆÃuØ ®j­¿h­/€ïh­žÏ¤”íF£q©Ûí ¥Q1™L&h­“$Iæ¹ÅW(Esð»ú¸böW9Á6’ jßÈÒù¾ÿ'oÃ5೸•e÷|ËÒd:î×K¬k¸aõª†Í0>g@Xd¼ìµ·(O\‹Š'S¬»rÑç¿õ?ù«øfìlË\äBIEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/stock-gravity-west-24.png000066400000000000000000000010711271170121200253220ustar00rootroot00000000000000‰PNG  IHDRàw=øbKGDùC»îIDATxÚÕ•½ŽÓ@…?gW`CØdˆ¤ )L‘>=%ûKIÅCð€¨è¡B‚' $uÒ¸‘"!BâÄVÌÆŽ=ÜA³ÆÞ$«4{¥‘fÎØ÷Ìý‡«.V V‘}¨}žˆBœ·€Ã}Z£”R*Š"MòPH*Æ7²¬m”VŠö³ÙLoï7åîD\¶ŽëÛTŠâ‘¦©Þ:À5à ði0hüp”û#AÙýcàã|>§V«i¼q Ф¼ó}ßT޾h]ŸCäÉd¢ƒ¬|ßWZLü‚õ´,ûþ#0•o#Æþe_©‹Æã1õz}§<ϲ,Ÿ}ViµÛmF£FÛ¶±m› ˆã˜8ŽÍL Žc–Ë¥>Úâ‘b¥þv‡N§ƒçy4›MÂ0$ CZ­Ö&C^gR/jSxëºîsÏó¨V«$I¢ñg@¬r½JðXnCðøåºî‹ápˆã8€oÀ Hs‰(ÿ d…†Ï€ÏÀ¼Ûí¾ê÷û_‰ò©¸Â$8×…7Y°–×~Ò^¯÷FðDîÖ9‚Ââ:'Óé”Åbñ’DHÞ_€Ûbþj×Yq U¨‹å%ð¸g´‚#à.pGRÑÚe¢Y@h‰R€ŸÀw Zv™ig¸ÌnÈ9w¬¯ÌLÞ»üA„ÿ@|ËIEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/stock-vchain-24.png000066400000000000000000000005731271170121200241330ustar00rootroot00000000000000‰PNG  IHDR (×.bKGDêêê: pHYs  ÒÝ~ütIMEÖËóé IDAT(Ï­’±JA†¿H^ÃÊÂÎÂH‰˜¤°:<Î-¶A| AìB Íz\yÕY0FŒpv)R¥•¼ÂÙ¬«çæâù»a¾™ùwv`]ªÂ?ô’¼uôP5Þ¯ ¥´Ù~_15`!¤”\ß\Y¨Ûé1Ø(²ð[ꈡer »ûÛ¬RjÀt6çâü’Zm¥Ôz~yÊU›X8³wöêÙbñ™ýYÊxÕ|KH¦³¹MÂÏ€V¨£ÇïNÉñQ;WÝ8l$¹qžç±½µI£}ÂdòŽïŸ{úx{­¬g㥠ó*wqÓíôl"MÓŸÛ „°ò|Wz1 Êé hTPuØxt—IEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/stock-vchain-broken-24.png000066400000000000000000000006461271170121200254120ustar00rootroot00000000000000‰PNG  IHDR (×.bKGDêêê: pHYs  ÒÝ~ütIMEÖµB•·3IDAT(Ï•“1KÃ@Ç‘| '7‡FJQ(c§`¨²ŠB¡ôSâV ùM¯¥c ¤¥kÅ qëЩ«ô+Ä%‰IÎ*>¸á½÷{ÿ»÷îN¨Yæ% Íê‹AOQ*cÛv’ít\fó©Û¶i4ë Ôn9ÌæSvòúí–£äc”VÛ ýd¤i…p+äº]–«5×W7hZ×íJÐÙóË$SùV•Ê™®"¿ æ*•œ/ÛÁa‘Íæ“»ûÛÿ@.€åj$âX_ ¾•NOªa¼*e]ÚNxô3C|šŒC@OC£H)±Hi”9¸aìïíR©ž³X¼cšÛ»ûx{ýû©Ä]ý µ[Ž å+ͺôTÔ¾P³L<Ï˨A8` }O }OúR_ž¦d¶øXiIEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/stop-big-hovered.png000066400000000000000000000306751271170121200245030ustar00rootroot00000000000000‰PNG  IHDRç ÙPsRGB®Îé IDATxÚí}y”]Åyç¯êÞ·÷{½Jjíb ä ,dmlgrâd&3IÙ,ä`Ídâœ(“ŒsâœÄ³åäxÎq&3!Ç XÈ`ƒ –“Éø8™$†xœØØ€B`„$£´tKêýõ[î­š?nUݯêÞ×­ÛjXžÎ;ê~Ë}¯ëûÕ÷ý¾µÞ¢·µkײ7^)Dû]ì* Jö ‰>Hôª—‘ø4€ F˜ÇÏqàŒdì¨Çp —+îÿâ¿xê­ºVì­ðGìØ±#?61ö0 Z„â:)«‚@,dn—jÊ÷ý#žÇ_fŒ?Ã9ÿÛ;w>õÞÀÛÖ­[o•2ü• ?Áj ä_ïïÀ9Ÿô<ïy{Ç{d×®]/½‚‹¬Þ7lØð¯ƒ ¸³Ýn@ˆ°÷RûŽÜ÷Žçýüßär¹ÿuÿý÷?ýæé¶mÛ¶kCþv«ÑüÅPˆÞ7ËÂúžw$ŸÏ}µR©þ÷{î¹çÌ; ˜›ðïhµZ;Z­æ:0ð7¯½e­|¾ðx±èî¾ûøÁ; ˜ày££ç~«Ñh~:Ãåo1.ó¹üòùü~àþ÷; HÚ{¾aÃúO7ÏA¸oñ[>_Ø›ÏçoçÎýüêö_ýùéÉúÿlµÛ—ãmtc Åâw‹…â§î»ï¾—Þ– رcÇŠ‘±‘]õÉ©J‰·í{¼])WÈår¿qï½÷6ß ˆ\½õŸ­×¿†A ïܹ|þd¹Tþ7÷ßÿ_½¥A°}ûö+&''þ¢ÙjýÔ;bO'¥Ré/Š…â÷ÝwßÔ[wß}÷§Æ'Æ¿Aåu%a…<ªÕjµŠÅ<ÏGÎ÷áù<χçyÃAÐF„ÑÏí6¦êuLNLbl| " _߃Ÿ{­\.ß¶sçÎï¾%@ð¹Ï}.w䨑‡''&E^dãŸÏå°pÑ",@µVE¥\A.Ÿ‡”€Dôùñ׉¥` ``‹k4š˜ššÂØØ†††pîìYŒ±v¥\úÜ—¿üн©Ap÷Ýw_6UŸúëf£yõÅúö  `pp1úûûQ­v)!Ëè."AKD¿k™G`pÿt©ƒ;,zc8c RHŒŒŽbäÜ9¼ú꫟¸hkX.—ÿçyݵkWãM‚­wo½±>^ÿÛ úæûÚÕjV¬X‰¥K— X*B¡„®."1kÁ«ßR€à.3+Â׺Œ›gÁ8ãgŒsŒáø«¯âÕãÇÑl¶æ}- …ü¾|¾pë®]»Fß4 زmˇ§&&BvÍçu—¯XŽË.[…žž^H!jáK)ƒ@ £ càœƒsœG?Ç"v·‘ŠB@ªÿgL€Yš!Bô¿”ÃÃÃ8xè Ι_ï!—;ÒÝÝý¾{ï½÷ÕK[·n½}|b|'¤,Ì“mÄŠ•+±zõ•(•JBB a ^(A¾ï#—ó‘óóðüHð%6`4OP`RBQózÆ"+!dtý0B@B-Àƒ+ Á½hgÏœÃË_ÆðÐÐüÆ\n¨P)üó¿ôàó—,îºkóoMNÖ?/¥ôæã›]~ÙeX½z5 Å„j‡†FˆBˆH]æ‹( Èçó`JX8¤TLA½Of( ˆ®Eˆ£GƒÀú|)c@p¥)8~÷<Ž‘‘¼ôãš'0xïê*ý«xè;—6oÙüëSSÿò¯ÙÓۃ뮻ÝÝ5[ø2„ù|¥R¥R)RÛR@„‚CJù¤ùc i”êQb"á+>!¥•Ö ˆ‰£„þn` žÇÁg6Nœ8‰}ûö¡Ù¸ð  ç¬^©u}àË;¿üÔ%‚Í[6¢>Yß%…ð.PÝaíÚµX¹r…±Ë®ðK¥2jµ*8ó"á@õ­··D Ë%”®·À ôã"“£A@ø…Âö8Dô˜&ŠÚtD;Ö³ÀÀ= 8p‡ÿÄ€iîÁ-•J7?øàƒ?~ÃApçw~hjjò[ò9ÀÂ…‹pÃëÏ冑ð…Fø•JÕj ç† <²ßŽÐ%ˆÚwÔ¼Ãã;Ía„/b­s ©.Y"º ‘Ršs®ªÏãÃÓO=…z½~A%ÿdWWuÝÎ;O¿a ¸kÛ]ë§&¦žðr!_`Íšk±úª+ÕbÆ÷0(‹èíí…ǽ˜]ϘÙàT„JþÒ? ˆwŠ9@D6µK‘B&õ¢Ø3‘†Dÿ+sˆ#HHpÎáqOñ†È<ˆPâÙ½Ïâµ×^»0÷1_8T,×_ˆû8gõýÉO~rÅäÄÄwÃ0ìžë5Š…"6n܈eË—" DB„" áq Ð]«Å»\k˜öݵݵ"~*ÈÃXüzÆ"¿Ÿ)_Ÿ‘È vµÛf¼„øýÑÕ(Ä!ë½ZçÆæ‰˜ &îq,[¶ …|ÃCéq‹ó¹…aØàg¥”÷ Ë× [¶lñéjµZ+æ €îžÜzëûQ©”•ú×»?@WµŠ… ‚{žÚ•*x£#yŒÅ‘=-4r€È§·„Ø`Ðÿˆa½F†¾~L; ÌÒ£ \ý.M,AJ‰Pi/q õ¼æ$}}½X¸pNœ89çE‹—¯\±øù}ÏóuÁu×_÷Õ©©©ÎÝþ/ÄÍ7‚{‘Š×€X´pªÕ.ãš™]Æ@E‡q¹þ› @„¨ÞÌã‹@_ŠÑ-?˜|šVˆT= àt š1fö´ªT‰;0ò¾r¹„%Kãä‰S‚`NkÚnëÞ³aý‹{÷î{ñ¢ƒ`Û¶mwýþ\SAK—,Áú›Öƒ1†P©ÿPÈåòX²x |ßW‹ÄU˜6ÚEf·ê G4ãtÑc“¡«_Ï3²Ö`KKïx  Uùœs5Ô1*Ô8 Å@¢ ¡PA(¦\J’üʰbùrœB³9'7’Aø³ï{ßû¿òÌ3ÏŒ_4lß¾}ÕèèÈ7C!æÔèqÙªU¸áÆ"ÖF¬?CT«U ."‹È ËÖfÀÄîµYP‹Ì• ÐÁÚÔp9y°® ’ 2¦†3,Ú‰ ÚÂäÍÖÜ€±dN"r+¥2š4D€òýV¬XŽáá3h4²ç‹„”Å ~÷fá™@°zõê'Z­öœxÀÒeK°î†"Ûo ¿¯}}ýB˜ãt—3Gýõδ UßÌIÇI z=NÉ¢<å œL(k)áhš˜h*;^7®#dúfæº Ïó±téœõE)eæðò5kÖ``ÁD…C V«˜´Ó¶ ÉŸÚÁ\%ct!‡"o\€E¾»ÙÉúõ‹It\ÁD£÷{œÃó<“色ê³8)(QñÎ @9gžyR”V†ÅIèu >Û¼ "2DF¯¡«®¾  ÌEôOMMýùi‚Í›7ÿêÔÔä–ìDpn¸á†*XȰtéRãÁ âĬ<…ì‘×ÛîžùK¿ëÄ·üvBö,W§h!¦ÌILó ©Téd¹” "æ#±‘ ñg‰$y C297%mƒƒ‹qìèQ„‰b×¼ç=ï¹ïÞ½“sÒFã¤Nºœç{nZ¿^%T"ÀÀ°dÉÒˆµË™ë–ÁŽñ;<ÁRÍ$ÀEX6Û¼7 "˜Ÿ=UÂUª×Ôg[F½«M)'Ú«ÝlXƒ11\¿Ž;akõžH#ĦAk•ˆBÎa`Âéù|7Þønd•G(D¡ÕjþñœÌÁæ;7ÿ»f«™¹3xíÚµ( &†«H Ü@‹åÿ«E¤4š!Ö <ŽRmÁÍ¢r#`8ªW/2çÄô0Uü¡ë= N=Ï•I‰®­¹ 7Å"Æ\¨÷[EŠÙ2¡n „¸ÉAJa 4i¨b+ƒ‹aÉ’ìýºÓÆG?õ©OõeAcºñ›Ya×]ëÆ—_NB!º»»ÑÕU‰¼c—hY¡Y;{ÇËÆã _pE¹E¼S9‹«‚ãâϘ0-8²C9 Ó׌xSä”sìzf¼ $@F+×ÂåqÆ3±Y‹M‘Pu”z-Cñ„믻¾çe’K„¥ÉúäeÁÖ­[ÿE³Ù¼2+âÖ­[ ]åÑ-„Ò ³RFM¢øFXÄÛa\]ÔÉ™g“DfzšKÕ¶þ™ÇD."ƒ‘ÐÌëÁMe6Q'€hS0b47ZFƒV› SÈb26'T{EχA)£¸ŠT•VÅbk֬ɮ êÓwlß¾½vÞ ˜žžþý¬²rÕJôô©Œ™ŒBƒ‹­|Zuv #I˜¨Õ03ªuÛ4訛æ–Ý×?#6 ÐÚF£bÿ2§¦‡ÙÞÕZZÀ<&’<c$ûILÕbÌæ"Œ«—B!på•W¢\)g 'wMÖ'?›Z¡ä>p÷Ýw/ZŸÙ%¼æjåÖ„"D¹\FµZE†‰$ £”\=¦§vJ÷ô©Ó¢uÊ®JR¦sóBµ{™J¦¨ô­IÝ"¨ÇII-13%[ªÐTÂÓ¸¦ÐÏùVýa(„©y2Ò~Ò|nµZE¥\Ž—B<Š p‚ƒ1©)TÞƒ3DÂç‘6Ð ã¾k׬ÁÓO?“IFzãvŸ™“““¿+E¶’ñeË—¡R© TQ¨\4h <%ÇîfÞL¥&hBh¶šS‹,L(í!°Ê»× ÆUÃä=ö5âò0÷3’1fj$©]ÔŸ S‰Ì¢BŒë×]J¥&98—„ˆ¸‡a¤ …ºê‘ˆ°ÁÀD”X CÎ8„Á9ƒ%–¯XŽ_|õút†šƒö²Í›7ø¡‡ú›ÍA«Õúå¹hM… Q«v£X,ª9£ASRÂE#uÜä 8Må2ʈ/mí^i* í¢Ò¸„<ùÄÆ’öȸ7–šÂU§^Ñ¨ŠØäIÓ§@Ý?nE$)? 13B<© Ñš)2 Qñ-\}uöÏvÐþ9ÁÖ­[oi·[ƒY¨çâÅ‹ÑÝÝ­:ƒ¢ríEƒ Uf0¶÷àÔmƒûÚ™Ëa­þ i Ì­ï‹5€z¬M$¹ 1 ´¯1.híô}`@mC'µm gÜZǘD†¡0Ú-ÊÆJ¬X¹ù|.“ ×h4Þ¿cǯ#é_ËŠ¬+®¼Â´g ¢«« ù\ÁŽõS-`sÚq}æ$c­Þ³ºÎ®7r’ös: G,é ­OÍ@lVŒ60<©Xv*˜éãnm$g< 95±N"“v´”jÝ]åqŽ+W¯ÎVx†•3g†në‚f³õÓYBÅb LµŒ2ú] b˜q£µ~¤ÈÊ "®ß)Ô°rñ2"h 2)Tj&¤”ÁELŒÖ³ÀŸs %¹Ë,Øi뙵6 Ë—/ËElÁ© ¸}Ëí×A{QT­Zu™ÊºE_®PÈ£R©Äñrš€c$Ûëfø¬ü{lB¸Sž“¦¤ó¿ÞÅ6Ü]‹Ž»Øú]Úï—Â67p†]¤á UP,N/sÓÌC?vÙ Ú@â)„DWWúû²uÿ7­ © M¹Uf„ÔòåËLs†½½}º÷ÓÎÕ-`EfÙ|«vÙ•þ®wÜUõöî´Ÿ?¸$RZM*ÒÖI3|/“7àÌŠQvFÑ®gt*”ˆ™”B¨¶;aª˜W­º,“Ü‚ è½ë®»Ö'@ŠðCYÐÔÓÓƒZwwdŸ”9¨Õj¦T .ša—»•8nß?Íû§ :)4—œ¹¶Ûô:@¡žBÚõ„ÕªFzM›{ú÷²‚dÌNeÛI%[hnÀH¶QkEÝ=­]S!$–.[©œ¿»x[Ífóê,pZºlI<'år ù\Þª¦³ï¸{V!†ë:ÛÀ9›©Œ*] ¤r[¥Ç ¯"&‚,՟ꦂ”~¥Ö81¦=îÄY´5LuAë¶xõóù<ú3©ñ n¶@°yëæëÂ0¬f±fWI!P«ÖL-s›¶˜ãëQÍ`~ÂÖ àÜë(ü40Pâ/¬~Á™Ù|ZŒ McH›  Øä‘‚‰ªwZI´•P‘öÌ$æxP“UDÜ8+„À¢ÁÁLä°Õn]c l‡?ŸE•pÆ#û/â>»×NœÀ‰'UÏ ­h°1t=ªÕLC‡³;;™$idB0:nú¸Í ¤Õ+RÌOÜàšü Ü)n1Ún` VÀÈõ˜ã)éé,RJ ôg«< ‚ û[?qµÂMÔä<î àûžiÿ–jˆÄ‰“¯áÀ˜ššJ×n!©÷·ÿ`w¹Á!ÈTàF éΗšH41ÄZ„z¢Ò!¨rVSÅjYE0’ìrPÓàŒÑ³L‚òè”¶¾¾ÞÈÕÌ G´ñA‚vdÊMª…–ïÌÀÐhLãðáÃxõµ¨f>ÅÐ4‘•D`v߯Uw@wb2jèæfŽH$É`ºw安q]E]No×Qºmj” Ïf¨·¤WêïáÇÂ… ²–ž½Û€ h·—e±'ݵî‰"¾Ä*KâÜÙŒ‘‘3@*-hD×÷3':8cÃE*(\{îÚ|í¸fa¦Xäì\Äl «’TVQ/NÏ%·c¨3ñh)ÑÕ•mVHˆ¥Jï’R¥óÏãÎC¥R‰B™>® H6,B Æ+GŽ`lt”$tXÂî3Øž‚DZÀGv%kéic™ð"\S­ê>b†ð3m]ávË<§Îé\²b I^€ÄgHT»j™‚Fí è¿ÊweAOW¥ œ3„aœFµ&ÅÌ…A€á3g0::ŠZ­†Zw·Š- Q`\d‡ë·Ïî>Êâ'g0T†1Z÷óñHâ Á$³Ÿ²kg¶ó.1ŒÈ›U˸à UóhlnB{ÂÚ,5r̦†!”RÚ A&éjÇ|>‡,òdLöúY…•Ëù?ƒuú]¦.ë„ôÄDå 1>>Žññ1‹(æóùˆù¾¹3ÆÌÿw‚ ³æ~^Ô(LeîB$ŠH“Ÿõúœ0¤ùïç2¾qȺ¡/½“u¥”h4˜žžNìv½C;!¡ÑœgU³ÙþÆß²+hQ@mêEª•>g&iµJCÂ4ÔMÇåè×sgx…þ*ìMpÆ|†™Qàs†Lçµ´Ûmg$lÂlÎgä1ÙÂ,„ÓíAFȳXA(:&ÁŠÝwœŽBRá3j×ôÉ¿$9†Éî,û_{_í ëÄ3Y÷%c™æYCØ,6Š%Ū¶;“kÆùŽ»=©Þãä‹Éæ¥~O$„>?J9ý³R#O3\΀鴨j«ÙΨ)yÝgç²¼)JÛåOÉEí°;dúk$yš9F!j® ‹+ÎË&Ò‚ô;¸Ó®IŸ3él&= /J:nfÕɬ¥•Z ù†0Ì6É„yì”Ï;›åMÍf3Ú$j&÷I{†Ÿ$g ™Ç¥´M”*JÆÜÜ œ"ž˜¶‰ljäº}³íx÷y ר×3«E:ÄÒŠ'ý|«ˆEv²±0ãqmŠ¡‘ñdy'|€e:0abrÒ:30ñžt±"Óm®¬#ÇYâeRζ¬jwǼ ñ3Æ’æh oÔí³þOKì]÷"5Ÿ@ÛqåÊR„œ‚—dI;SÍa ^Ï|ºîqŸùìå,trä܈…<=<"žI/ºÕÂì¤w})Õ„W És“n>ÂÉѤCÎ8ô”tžÐ iµÔÌFÓ"¤£ÅôlFZ 'f2²6H6½Æ{ߪxœ—ò¥ç²y-5…;Уf\}§w¾}d-I¿:õ| áÖx'²ôÀ“Ëê‘<Ç€º~ñŒ"nF×¥½N€Ç£Î0$=:Î6Ž^Md!~>•4§èçì ¤ññLÃÍáûþð;weœ·³”%MNLÚãtˆÔA5SÔiÅÚ‘l+O´Œ‹^DŠ‹–¶{Óêóî˜=³ˆ ? tVš`‰¾íŽJË Hjó¥ã$¦ÌeE4¥­‚h•òÔäT¦ò2Ï÷†Uc˜óýLäprjÂúpkE¡‘)íÜ”ùHÀ9ŠŽ´z!½XÅ-¸H#z,íØ› p2n®S|ÀœÏKÓZW)CÝ'-;i8Q¢ I ‰¨œ/2yqW³çØX6MósG |?w$ „†‡†íÁϤyT¸µxpòò°óðÔF»¨Þ+¤ìhk;Ùb7ò—¦):»wHˆ™€4w‘¥Ùlû`-fÖHR=Hø€tø1ºÔ Òp¥»—O>•©¼Œqö>Ë»Oœ8/ â1ô±ú„$ªv«r%}œ2h½Sê×Þáf˜dа© ·~žéΓÓPÓL ¼a¶7ë@*„1¤ŸBÚn®1‰Nᣃ¸U{ÿôt“È"Gîóïx^‘©©IÔ§êªÁ’4P¦ð·‘31èÁ1 TeJ x¾G½S Æ‚% 0g ¤#lîÌ;¦&€Å³RÀmS`DÉ"9r—jFq;V…’öÆÉ! sô÷Éz /cLxðž0 Èçó…hLÒyãhxø ™ÆiókžQq Á »XUB w‡é–¢¾m¡ÄÃ$Óv*g6û7³™3]TÆç¼#Ï a¶06-öj»±zÔ‰¯ š¶‡alóxêêÐéáL¤0_(œøÊW¾2a@°k×®Ñ|>Ÿ)htêôI5)4^`}$íÖMšfƒ#å¬b8-ãFœ%Xxª HI¥Ûü˜_p‹@¦Oêep–¾Õ  z€GÌq¤C…u@·¸‰u"þ½3=•áĉl‡læsy0Îo¡Px6KÛ±£Ç"Ȩ'~šãÉ“&Á(9!jÐÕq¡(ØC!]/ÁžÖiâ¹Íä’f&í}<åH7œLM„颣 ¬ÒÖÖx=¤i„tSOr>ïì™sQÿgùù¾ÿ· är¹=YSÊ'OÆ‘“ ÚK &Á0ba«½ÄÝ_gÍ LÚùN]ÌÉÑ÷8?2˜(:/01@ ÉkIDATg›uz*³:µ@4žÖ1ÿ±Û(ÜI)€ÃhbÒŽ;š™”Ëå/'@ÐÛÛ»›s/ÈbW^yå'fx´m&zê/OÌíã³´ jRM´³7ÅÎ'úÙR´Ây 9íÞ)ð”Tÿ.¨†‰SÙÂ^ªâ ÐY »Ñf™Á Çgâ…bñ•/}éKc ÜsÏ=õb©øRD½öÚkh6[ d2ufœœQÀha•€;fAg;AÚ'¦Í †lZ¡³ðïÁ“ÝÅ´–’A¸Ýд!„&@¤f7ÓÓXNŸ:V+S] ùÂß[¹ëÉBáëY %„ÄÑ#Gàé™»Ü3ÇÅ[ EXch©ÐM–‚(fe °^³X=ëêH»–}"K²û*>*—©Ý-Òç"X^ƒ¥ŒF$95‘³èvÏã8|ø2ΰ“^ÑûÓŽ €ÄŸqÎ2Úü / ÂLç^gm §š˜Ñ¯DUê±7z)„Ž8=Y¶`gÓ:egŒÎgç[§­ÛwÚ år1¨ÍߥæJ'Gârªâ3™´ÙeÇñã¯fÒÅRñø#>ò㎠ؽ{÷ÙR¹¼/ ¬¦§§qôèÑèÌuHD'mu2K£î 1L˜iNMkγúúSO<‡õÖêàÔs™3œ3UÃÀ 'K5tRÏÈ60Z@H› P-À¹GÝŠÎkxáÅU ”Š¥¿LÔO$R(þ92Þöïß5vÐ"e<,%‰‘Yvû¸R“çN… ³ÂôÖ ªp€à‚}þœ‰C²­d:ó8Á'P3†„k̆ö,),.À™:ˆCi©©)üäð+™äÄ=/¨T*4kEäÚµkùªU+ϵÚíî,ð¾÷¾ËW,GC ÝŽŽl BrŠ 'DÐó‹tЃ“£d¢çšÍf”'O•çìÀç'šq(‰Š"Ö±¬SÒBHåÏE uÂÞH†Ä…ÞÉjþp­Z‹z*uÉ{¨H/§F —ïç|xžßËÁó=ø¾‡§žz /¿|0ªÕê“ßØó‰º‚/®½öÚ¿lŽÜ•åžÝ»Ë–/ç^ó÷¼èæÊ.$ãQý¡,ºC0€GS/8¢õ‰ÿç y,X°Àrñô`+= š&TŒçheþìƒ(g¬[£Ï;!l Ñ䜄xÒ(€ Òóàð½û©êä iŽá‰ÎmŒþþ‰ñ :x(s_F¥\ü|ª†è€˜ÏxžŸ©vyrb/¾ðB|Üœ:!Ìó<³£ÜVnÝ!ó5D-Xü|¼àôõ!è(ôi%dÇEHEÓ×%i7›1šz`†>×QD;Xñ}H•Ö z°¤ÞÍÔüYPï—$h¦5?¤5ç÷àYUP ?üá37•Jå£=ôð_œ7vîÜyºR©<žuzöóÏ?‰ÉIóe=ÏS¶Œ[®ŸHåZ 6\Ž Ù‹v=ZÖatv 'êת»~,:s(:ÈK„úšñÐÈx€§º¦µƒ)×!ÓÈSÏD6Qíðˆ0x<>½õÈ‘£8yêTæÉæ•JùO;r…ŽU'åÜoq–Í] …ÀSO=¥TWôÅ ¸G"eq¸Ôâl@°âD(zÔ¾Œ5C¤%„:$Ba*a(!ë»1sÒ”pÒ…k ãâJiN)‘3@O&¥Þ€‘çGfÀ÷}ß‚ À3O?•·£X,ïÛ÷|Gt<ëhÿ¾ýÃëÖ­»¥Ñh^‘©$}bµj }}½Š8™¢s«‚Žvµbðj´ R*Žé|li75ÄÉIÒÿ TnbÖ!hš[Øç$‘i‡z‰Ðè°*Põ/HˆÈèïîµ4H!#À=x¾Ï‹~ö}O?õ4NŸÊ ‚îîî?|â‰'¾×±àtæjÔܯ{ž÷b†™ZØŸüÁ“èïïFÝy¼Ñh ÕnaÅŠå¦%v7)Q„k¶‘õUûN1«’ÖóÍT—Ò2BÚ•?X4niâJÆ©r}ÌžÃÒÀ8üœÖ> oûï15UŸƒ¨}ïѯ=öïg{ݬ Ø»wokÃÆ ɉɟÍú%Ξ=‹ZµŠ¾¾^«ƒHÊèp&Uä4lÄ“ Ã$œ&IÈå H¤pÕ]¤–I bÜ1ö)§#q¬˜ALx; ²ýÊx?üápâÄÉÌð|¯Ùß?ð¡½{÷Ž^0`ßsûž¼~Ýõ›šÍæÂ¬_æøñãèëïGOw·=¡+Ö<_I=[½+Êú«SO´§ïŸI+V$²·˜$Ú¦ ‚Ö©jiæÎîwMŒK]DDÐW¤cï³ÏâÀ—1—[oOïŸ}õ«_ýÚyæ|/ºaÆÇ'§&·K™íôT8vü/Y‚J¥l…zMDW¤ ƒfThÒjÇ6æôäÛ;Hœ‘ˆ™GáÆ&Á^|bªŽû§ Ÿ‚BØ‘?8•S¾ï úùàËñì³{ç€R©xòå—_þððð°œWìÝ»÷Ìëo¬Öëõ[²~)©j—-_ŽR©h%ô,j·M›®ÖÒûªw|ò ,3éݣ¥]˜‰J8>X5“$zç[\ Óî'A¦¨LL‘?e(Ž9Šïïûç5Ø;øa,ìîïùÅ'¾ýÄyg—2íêö¿ðw×ý³ë~¥Õje6 ¡qìèQ,]º¥r ñqo¦Æ,2h·l­€”ó"IÇchìÂVÂ/¬Jf7bL¢u’¦’ÒNE!×J¾ˆ¿›kÿuÀ÷¢ ïçTxØ7øÇïþ㜽ý}Ÿô‘GïÏIJ~ÈM7ÝôÍfcúSa(òYß!^yå'X¸pjÕª!Œ3Õ®Î-ò'-û·Ã'À a ‚Ð*«\ͪìá¸[c ^&“B®ð©÷ ¿¯ïG6Ÿ{žÒL€ã¥¿„ïÿIÌõVë®ýpÏ×÷|<3‰Ìú†çž{nü¦›6ü¸^¯oÂæÙ !ðÊ+?Ao_/zz{ŒªçŒQkP­àD )É3©^R˨M…3bxóe±Gg¦®Šrw>lÛ¯Õ¿ïGÌß÷#õ¯ãÜãxnïÞ9s( #=Ý=7íÛ·¯qÑA€(QôÒ»ß}CÏôôôƹ¼_J‰#GŽ"ŸÏcá¢öééJHº™”j{’¹ñÞ@òœH 9KI¾LD+‡AÝ=ÙAø‚ÈrEþTHÕx^xòO⥗^š3ãaï‚ÞùðWþñœÞ ¸}ä#¿ô­‘‘±Ÿ»k¬ZµoÞß÷†qb%ÊæEiã0Œ ,@›Dy\Úm•{Óê³ÄX·̰[8Þ``w[‘&SCÒxœJ÷¼(‰¦³‚žçal| ÿðßÁØèزÁÀï>ºûÑÏÏõÞ…sï‘¥K—þB³Ù\<×kŒŽŽá•W^Á¢EƒêøMUÇ-T‹˜ ?#qª3ç›Ä¬p±S 2ÓÝä’“ÕAÏ’‰SRð}•Iõ¢J h÷Gwß÷ñ“ïàño?Žééé úûþäѯ=ú‡"Ç Ú·iÓ¦\³ÙØ?99uÕ…Šaݺ°æÚ5Êõä`hò3)½2©#RëOM‹yìþL:U%í(^ lfì Ti8çqÝ¥*i6[xæ™§qèÐá ]zôôôìþÆ7¾qÛ…^g^µnÞ¼¹wdtäÅúT}ðB¯ÕÝÓ÷lx .4¦Aú 4'/“cãÉÌ©HžýÏNžs`«è¸ €ž*¬…U©‹B=ÓõtøÐa<ýôÓ™›ER]ÁÞÞÿ·gÏž[çC~ó6­wË–-ËN~¶Ñh ÌÇõ®¸ü ¬_ÿn UÐišò³´l\bL.c©ª4Õ+˜áA}]kžâÞ@OiXøÑkÎÁ“O~ÃgÎÌËZw×jÏ®ç{>®]»kÖ¬A±P45|n•p`…g‰Ê¶ÓTÕ¿õ:«}[ƒ!˜®§$ÓºWs†‘‘<¿o?Ž92çàOJNàñüÌ|`ÞAŸøÄ'ªããã?˜˜˜X3_×ô<Žk®Yƒk×®A¹\ŽË¶ ¬ó‚­”­´U·k×!;¯†u¬-õ*Èð ­þÍ@ ÂΜ9‹}ÏíñcÇæoaÐ×Û÷ðž={î˜o™]”áí›6mʵƒöwÆFG7Îë—e—]~.¿ür,Y¼Œ1RŸèVñHÛuƒLº{ƒ6©€‘Žcý³.içªþ/:ûñØÑ£8xð N:5¿BbLö÷|á±Çûí‹!¯‹:Áÿ#ùå=çÎüò¼ž“£nÅbW®¾—_~9úúú-aw8ñþóÒóÖY<—,‹#%pêÔ):tGÍ<\ú<Ýð`Á‚ŸÙ½{÷.–œ.ú1ÿøÇóÌ™3œµ2)Ë­\.aÙ²åX¼x -BWW ñ¦×Y0“GØ£#£8=t'^;‰'O Ýj_´µ+•Jg»»»i÷îÝß½˜2z]Îò¸ýöÛjllôo¦¦êK^ϫ֪X¸`º{z¢“X«5Tk]Èç én ã>2L×§16>†É‰IŒŽalt§NB³Ùz=þôôõ|¯«Üõa=\êMرcGþرcyîÜÙŸ“oÈÍ÷}ø9…B…B9߇çûh·Zh·´Z-uoªžÂ×ÿÆ=ôõöýׯýëŸ{½>óu?Õç¶Û>öoÏù|³ÙêÂ;7ëV©”ww÷|üá‡þÞëù¹oÈÑNÛ·o¯ =8:6ú Bþv~.—kôõö}áÑGýìñùoèù^·ß~ûÍcãc_™œ˜¼ìí(|Æ {zz¿ÓÓÓsÛ®]»N½aßãRXŒ~ü£ŸûF£ööQý•ãµZí×vïÞýÍ7Œ—Ê¢lٲů×럘ûz½Ñ÷¶û'kµ®?ؽû±û/•ïtÉ÷¸víZ~í»®Ý19>ù;õz}Ñ[EøÕjõPWµë¿ì~d÷—/9³t)/ÜÇ>ö±»ëÓõO¯…Ä›Ž@ú9¿U«výS±XøO<òè?\²ÜäͰ˜wÜqÇÊF£ñõzýçëõ ¯Y¸¸d‰®®®C…BawµZýo»víš¼ä ê›mwÝqÇÇ®o4Z¿Yo4>XŸª/»4„ïûíJ¥r ŸÏ}«««ú'o$Ó[€Àq1ûÚa{s»Ýþ™ætóúéééA!…w±?7çû­R©x,Ÿ/þÀÏûß”¡ÜóØcµß¬ëø¦{Û´iSÎ÷Ù-­VøA!Ä AÐ^Ù‚íV»'‚‚¤}k³. ¹\n:—Ëär¹Sžïò=ÿ)Îùã_ûÚ×ö¾•Öí-‚ÙnwÞyçòf³y™dròBãŠrŸŸâà>ŽçXîèC=4üvY—ÿ™_HçÈHIEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/stop-big-pressed.png000066400000000000000000000305221271170121200245030ustar00rootroot00000000000000‰PNG  IHDRç ÙPsRGB®Îé IDATxÚí}y×yßï½î¹vvfoì{à䉃 "é¸,8—ã’Y±TÇv"Çvì8±RŽs¹RJ•'Nª»Ûe)N%ŠGeK–E‡A` AHظ7€½gçè~/t÷{ß{ݳØ^,@ð˜ªÅ f¦{fúû½ïü}ßcøðÞØÑ£Gwù~c{}Rú]B¢Rv€”ÈÉ,ÀVà1³Ìá·8pC26ê0\ÎdòžþùÉí…ú0üˆãÇggfŸ¾ø.á‹OßÐóýH™Ù° ÅØ²ã¸#®ë¼Å…sþ''Ož<ó1ÞÇÛáÇ?%¥ÿ™FÃÆóH‰ì½þŽã,q‡¿îr÷[Œ±ÿ><<|écÜåïzäÈ‘¿âyÞ5§}ßë¸ß¾ “q¯gÝì72™Ì9qâÄ˃`ƒnGŽÙíKÿêÕÚ÷y¾ßñA¹°×Éf3_.Kÿî¹çž»ñ1Ö'üÏÕjµŸ«×냀ím=›Ë=WÈg~õÅO  ‚Û;xÎììÍŸ¯VkŸ÷<ðCæ„Ël6{6›Íþ‹S§NýŸA¿ñ#G}¾Z­þR£áoƇü–Íf_Íf³¿|êÔ©?þŽ;ú½+K•ÿP¯7và#vËrß)ä ?ñâ‹/^úH‚àøñãC·foýöòÒò3øßgÖbëËd2?ó /Ô>* `‡úBeeå—…ïðñ-ˆ&²Ù‰–BËOž8qâ?Ô 8vìØÎ¥¥Å?¨Õë{?{²ó˜/äÿ +üØ‹/¾¸ü¡ÁSO=õó ó_¾_¼§NX.‹R©Œr¹ŒB!Çqq3p\ŽãÂuxžÏkÀó|ø¾¯ÑÀr¥‚Å…EÌÍÏA qO‘ນ±–––Ïž6$‚Áþé2<¥ ÆÆ˜þƒ”³s³¸uó®_¿Ž……Å»v …Âÿu]÷‡‡‡«8~êðþåùå?ñ}¯s£Ï]*•°uë6ô÷oA¾P€”RJ!”Àƒ;H@OBJ©¿lv1¸Ò Œ3 ÆÀYðg` ó¸~ý=\¿v µÚÆ;ø¹\î|6›ûÔððð܇ŽúôòâÒÿô…hÝÈ/:84„;¶£½½¾ð!„ÔBBÊáêg œ38Ž Æ‡+ž1!D ÐV)àû"8¯ ´cã,¦gàœƒ3)%¦§gðÖÛ—qóæ­6u#mmíŸ|á…Þ»ïApøðáYX\ø¯RˆÜFœsŽ­[·áw¡¥Ð!%„ðCU ^„`ÜŒƒL&8Ž î0_3¡µdd%$„”‘@ @ÂÿKÀR x^ð¹€ @Ày¨8X¨!¸ÃÁÇÍ›7qùòeLOMmœÃ˜ÉLç[óþÌKg^¿oAðÄû~q©òE)¤³ßlçŽxðÁ‘ÏçÕê¤BBFêù\ÙlŒ#xŸÞ­n°àN†š"z:2B e$”?!‰, áq‚h¡ŽÃ‰vpÀ9Çìì,.¾yS×uZ[ ùÔ©—Ÿ¿ï@pàп·´°ôï!ïüœíxì±ÇÑÞÖf _&@ ‰l6‹BK …|(Lá -Ä1@’×Éc©ß'edV"?"¸Rªp‘qH‰H«fƒ;Úº>ø€¡ú}߇ð%ò…<:::à8„ï[«>°Ç‘p•ÕV¦@«3’CPù„ÐÙ¤à Lø ¢!´“)¤j§ œóÐÁ üÎ0Æàº|_àÜÙ³¸þÞ9ûù\îJ>_8x'áãºÕ÷“O>9´´¸øÏóÚÖÉðäÑ£…®~χë¸èééF[¹7 ÷thÆìœƒDñàL'{hHÇÃc£P˜ pŒ%†¢ü@t\øXê#trÉ€Åã m^¨Â …„ãp "—Íazzš¼7ÝÍóýNÉä÷LMNýf“ôÇÝÁ¡C‡ÜjuåD­VZ/Ú;ÚñÌÓO£ØZ„çùÊð}­¥z7mwœPøW±z$`D‚d¡B¡qÆ´lÔ nÄ@„’è=T_Ê0·ÀhŽ‘+?ÊC²ˆRÊØ¡ÆŠÂØ R wuu¢··cï)Ç6íÍ÷üÍ[·mÝ<11ñõ{‚þþ//Ý ·wŽ}Žã†ö߇ï{`6mêE¹\ T°ŒV«F°ÚôJæ \eó¢7PAsf T ‹Aÿ©ã ²ƒ°³†áëœs£Ö ºC¨Tâ!Š*Hæ2>ÏóÖuMÆã»vnsllüÍ»‚#GŽüøüÜÜ?Y/úû·àÐáC`ŒÁ÷êù>²™,¶léG&㪕Ã0NV;]ýä9fœ®hÄÓ½Qȯ^£€ÑÀ0ª ôœÊ$D¦Gê3Ú!4 Z#§‰|©°0‘Ëå048ˆ©ééõ¦ž™çùßóÈ#üÞèèèÂ]Á±cǶÍÍÍ~]¹®Fí;¶áÀA†Ï<ßh-µ¢os¯¡ny´ þZc‘ #ÁF+”SWS ×ÈófF:˜šÃ{0¾“þ> «›!0SþGô)"ô¤[&“ÁÐÐf¦gP­¦Ï'!ó ¿ñÝÓSÓÿ9 íímV¯7†ÖgB¶`ÿþA¦MÀGWWº»º•WÍ€¬nÄm6gŒø &Éö´?À-gQ™ âPFfE Zs¨œ@ÌA„Òê õù‘ΠYK#L“Žëb` ccãh4ëñz·nÛÚ=11ñG‚ÇŸøÅ¥¥å]º{ºqäÈa•ÕóÂH`Sï&´•Û!„¯=*lâ‘j»™-|F¢ÓÛ#&d•Ç2Ð,Ôo >A UN4OtÒ ‚éPj_,ÍPW™x×u188€ëׯÃóüôC£ñØöí;¾:>>~cÃ@püøñ¾™™™ÿ-„HmÚÚÚðÔѧÀ9S@ø}}½h-• ¤„‹¸°TXE àˆ^WÀà*¬3 C:å4’eJe†‚ZÕkmÀC_ƒ1ƒò"iG£]¦Ö¯…9†Ð‘ €V ³ôõöatôÚz²‹;455õ›‚ŽŽö¯®¬¬<’ºü™Íâ™gŸ›ÉÀ÷|eúúúÐZ*AJ:kZØÚãÚ¤ÚÀ^ý P¡ !#@Eµÿè=*Œ$%e„ï @Id&©™"~€JíQšŸ­i,LW' ƒÉBø[¤Z tuuâÚµëë0 Þ–íÛ·Ÿ½c>|øÙùùù&SfYPB©\Ri`ßèîîF[{;¤°.5qÀHìO+$i¤Lõäcš…Ç5Ήé‘qá΀/D³hÒIK³@."Ís@ÿå<L©µ€aæÆLz ÿèC=ô¯]»Ö¸#ttv|Óó½®´_`÷#»±uëV•ð|å¶2zz6A aØcfaÁÐŒ+Áò˜}¼þh@…˜úÞÌ$jæe $™™3P+•~WhÍ%”Y`Ri€(‚Py RHãZ°ÐñèÙ´ 7nÌ R©¤ŒD €ÉÉɯ­ø[•å忞ÞìÁþû!ü æîûr¹,úƒ$YͱH€[êžïZÙ|®£ÀÏmöÓédndút8GŒ;8t4%‡h”`•_¢gƒú…Ž" aÓZ¥z©JR—3lÞ¼#WGáûé’Ižç=¼k×®ß:;;¿æù~ªÚçÏ<ý´*øø¾`hh«VöDx€ Nœ;+Þ'6=Zùœ™¯›ÀBüX+ ‘úÕŽ$K$lÎHfÐК\ žZ0Åmd6h`Õ Bg:Yý“ÉdÐÞÞŽÑk×R@BºŽÃ{VÓM[¾÷ÜÿwkõZêÎà½{÷"ŸÏër°ï£¿¿®ë«OÇá0À•Õµå¬qE䌇ïá±Ç:I¤_ãœk-Á¹"{DÀ•‰aÑkŠ>Ã!ïÎǹî8 œs•ÈŠþÏ8ÑTÄáÑg’ï&EXG¾QUííëÖ-éûu+++?øÉO~²3µ&èêìúŠïù©˜Âmmå #òþ|ÏG[{ºººÇDz»‘íO§Þ?³¢+ÅË,¿ÂHqfš“¤j¡Ê/ÈPAÙõx4Ù£üUº¦Î«^õV!>¬H"yé,pí¼ c =Ý=x÷wHŽa Ú@Ê Ú''&¿¾fMpøðá¿P«Õv¥EÜþýû!CO×÷8çèëëƒ/„QŠ ܈ͩ™°‚"t®B?šîÕô.ºº‚¥£•E œsEþJÓ!_éÕ©5€Y¤ ž } ÎÁ™CÌVxLô>’¿Pß…8·œø@Œ1¢M…º¦…B»wïN¯ *•Ï;v¬¼fMÐÙÕù»^£1æC¶nÛ†]»vÁ÷=ö÷÷#—Íê{x±Œ¢‡áíÓÕª˜–´û‘GBn`¿ZŠ-(•J*%L³fÌH˜G^·$Ô}‘¦&§0;; @†Ìœí²Ž4O0Z©{X³€„Ê:r‡³È#B†} NH' z"¡ Ó¼™l†ð fQÀOŒÈ"Žz¯årÅ–H.ÁÁ³È p.8$óÉÁ™€°L _ø`‚A0¡œlžáxt÷nœ9“ŽgZ]Yù¿t[,--ý#!DªÂÒààŠÅ"ž\!Ñ×× Ÿi¯¦˜-‘ÕC’?ÕZ ªè"„4É¡ºy$âúB¿ŸB ʘM!BQÈ„Ð5òfK‘F"‘„:Gô>)%öí߇b±NÀyxΰòÈ8“`B;– \‚)2ŠàLH‡CøC[‡páÂTVVÖ,§z£1°ÿþO¿òÊ+ßXÕ'¨×ëߟÖÞ<òÈÃáÊ j¥rù\^WÐ’„O3w¤ØÃ­R.=Nq)Mܪ˜JIM@hä’°)Çö!HÝ‹@ƒ,‹¸j~–Íc”Fr‘G (†ëDX))Æ‹ßGV†~A¶‡Nɇç{?³ªcxøðá£õz­/¼kúÛ²y ÚÚÚÆ )€¾¾^M cfŠ„(¢3+q9|·ÉTÇ…FØÅäyA 0æê×`¢š!:—ˆÚÙ"v²ë´oÁ"²J«¦ú6œs#ZQ¹hÇ51ƒ ¦[å¤PšGøÛ¶o |®òª®T?~Üi ‚jµòÓi‘µë]ªùÂ>Š­Ed39"tÓú+È"(²(ÉPÓ•/“ZÎePD²ˆ¥U$5ô8ƒEU·GÁà/pB}£|Æ(½Í¸ÉˆRE(®M^x/¤„Ã9xàÁtõß+ÎÌL}vÔ¿K*&Üíÿòù6õô¨~@)üß.}2»@¯µHõPWåtÅ.:8Rý„¿kª_Cˆ·é' ôrbZd¬OÂZù2ŒÆ±’€^%‡¸aòL³@ÂFpK@û!'C†³CCƒH#3  î5>—‚Ç=þ°ç5zÓ jÇŽíÁ— Uk6—E±X ë÷¶Ý§$ RÈ¡ƒÉÃ,J¸NÌP… É}ÒŠÕ”´ŠÿÛÏ ÓÜ$û–Oa¾T "f@ȬHÐLõ? .ðŠ­­èîJWß«­Ô%‚@Öäß)!588¨B5):;;T¹”öØÉ!]¿'È€Í 2Õˆmçï%bš I@ÍÔ¸ýº´Ïëc´ü ü®A3 Äyà̤ÒÛ¦S ©€€Ð©Ý¾}{*¹yž×ñÄOŒÀó½gÓñ ÛQnkSvJJ ­­=0TÀ`–¬)­›üPX1R—_MðÉÏÙ÷´›HÄ|-h‘hF„•Wˆ´B33B3Z á´fRר6 ….X¥j!ý`†‚Š™400`ÐâÖrk4Ÿ ^¯?”Nƒƒ!Ÿ^†Üù²™ŒAŒ`4“Ƭâ+í#€ÅÙ'äÕ~\Sp$šsRIÔ>¦b{‘ ó˜$MbÛ'˜$ jñÖ¨ Œÿ‡ƒ0¢p‡32ÀcGžä·Óð׬“¥s!±Ø‰c‚Ñ´)ÑÓÝ“Îx¶}‡÷=¤Aà{ûÓ$zzzÒˆ•vÃ¥K—°¼¼Ó±l¡Á)Œw…u"CCÐÐÌž†'-ÍXù¶ B¶L*y¤#ƒxX(›j'37’@ !‹)nÌ܉šÒ¥Î…DWW'ÇI•8BÏ(4ö¤ÒÅV•*ÖÃóÊ<¿™70;7‹¶rÊmmÈf²ÄL¨8ÆØ¹É+î¶¹KHÍÀ”gaŸD³sÇ]ÔP4Ò¢¥3&ƒ%Àƒ±’‹ÿâTÒöèJD\ G9‡­­é4×ðŠ{öìiq}?Ý^Cíííd ”žò•ÄÃ×tnSC!17?ÅÅE´‹hokG>Ÿ·Ò!ú9³br«ü­æ˜ƒ0@ÐÌ”4«7D¤ÑÕr6-L%ÅdÔó ÛÌ ?H2ÒÎ.õë’¤Œc`u-—Ë!“Í Q_s73Ë3C.„èK‚––hÝVåÏã²'žpVc¨®¬`ªVCÆÍ P( X,"ŸÏ“œ„³Êªc1 ˜Æj¦¡ßZü P¾€ê0Nö9šƒBš½ê! Hwš6jQÛâš4¡µºb­CÙB¡p|g«+„LU‚RýôB%ÖŒhDVE‘¾W•• ªÕ¸--E5ÉÔ´·ÍÕÛDP·³õͲ‰áÊí)˜§,cF̃1‰%Ýþ? ª¶cšÉ¤ÛXH±ÅeŒçS “Iðˆe,EÜŒVf’7Ñ«¶R©`¥²‚·Þz ß|¥rmå6´´´Ü6lcжïš' b¾Áj÷ª 2IN©ÐU> ˜«,MðKÛ`ñk$cÓ šÃ‚^RƒÀ­®¢œÕ‘& ^ûFܤuAh*}aassó¸&¯sŽb±ˆB¡€\.§îã«¶ùêG,jöXKÐ&šFý2¾mðéï’hjÒb ¢KBâ<Ël6“JK1&;Ü´ÂÊd\4½>¬Ùÿ“6™ˆ‡‡qm|ßÇÂÂæÔp.— £L®ëªF×uMa4 %eâïò}žç‘¡Úæ7ÚÛÑô³îú-üò7›ò0ÆÝ”Ö ÷ãκRJ¬¬¬¨ÖmšP³šÁÖhÖŠÓŒ „9Rç¾»)ßï¦=Ê÷ý{ð#äú~M³8›ähO@[°öêŽûì1ïû^êëæ2†zšFBÁ£ åêÚCG=rUzy"Õ,y´–õ@C¯$Øs ìÿ7{.þ«n“Õ²¾§\óû›hçð»4¼´ÏDÅ• Õ4ÐT°æ¹<ÚËžGOÿxf\JÙüRÅT½„ ’$EZbãm:[µžæ¼©O^`3¸Õk”Ž¡Sq™Ä­tšÀ3É¢± Z’ÚŒú&¿G’—™u¢l6kÜk2TØ«½?6Ý„Ž®]Å0Ÿ·g™X¼ƒ&÷‰æp –Ñ)jŒ¥‹Ë6érÎn¦Q<µZÕ,z€¤µ$32¨¶äõ¶4Ò$c$¥ ÝÊNâ6)wÒ Äj+Þ~ݦ·%k&&À>å+JÛ¯RÆXKÍ  ¦ ÁB­VKå8ÌwžjÄť%£ÔB•»µþÍD Å‚ª=JRƒ$[Ú±U׋©òíhAÛ~S@«iªæ‰¢9X¬÷íwàVÉm+-!›™çx±N}>‹FæúëÙHãºËöV'döÖ-’ýŠGÃÉ[ꛉq“€ªYHfnG†‡…^„¡9¢ õ¤iדl}ð˜[MÖ”+ÐÌ!\ ÍÝ=δÈÍX¨PíV:inד˜Ã1¸Þ•JE…Äk¹qîxgΜ¹îæ³ù×ÒÀ¦^¯£^¯Ã &ØÌbmߥvïÔŒ å8Jâ@*ðr¨RûÖä1K°IibÛ0ÀÁ“lj5z,:àÌøíŒ5›b& 5íÐ"h¦OZû8Ñ/$㾂±¨oc~~>• p\gø©S§F9wihIK‹‹ªI‚¶Y)¶L‚¦í1Ç·®µzéqRZê‰!ÛíV/ÝØRmPEÛÃ7òÑûós»[8!”µ¦šëÙ†2®ù 3A®~ah ³Õž1nÔåcXZ\JE/sg9†™Œ{3_°hl%5FhfÚ>ÅÕ·«uaÁE] ETÑTj0{µ³D @¸D0  vrÈ¢mlu_€Ld¥ñ-%¶F¤\½«TÎKÙ–Kdð7˜^ˆ ,õžÍ73¢@ນ‘4šžš!\9=],VÚ”ÄÍ1èâÒä#Pu(a\ È$»l‹ÍÉh,æà`HÈÚÂ7€Ö4bHÈÙû%IÒÀ¢E!©F°Zè`FIþC4뉓®î‰É‰Tô2ÆÙk ŽÃ_Osôøø8­QJ{H]ý´{˜¶vS¯™^sˆtdÍÁ”Jp°T9g‰*~µ=€±B³‘Ñ6öL2Ô>é24ž]Ã`„É Ë_d œîo\Y©`qaiäè¸ÎKT¼˜F,--¢²¼LV‰¶±¶_ŸÜEéÙ¦Iˆ.ÝåÜqõk‡j&ì±vtŒ]l…s2JŽs2R1M€3I¤÷?JÚ‹Æï´M!]±­ûÂüÓÓÓik*‚ƒÿ™A6›ýC€‰4NÅÌÌ ½«·ýRç–æ^„zЃ0°­9ã ñ9KðÜY Ìšh8†¡sý™;¢C _k@ÿ$¦mh´#âÛóêÿÃè倡!$œ©ç+F“P¦¦¦S9…¹|~üìÙ³‹ ÃÃÃs¹\.UÒhbr\ÏæT§.Ñ$0ËaÙ¦æÎ¤VÊÕ0 ,î$%t8“8É”!>3ÁgH “"‘x¾@M¹M-ÀhxheUSÁ!”Æïâ\ûc)7ÙÌf²*5 –Y.—;—¦mtd¾ï‡“C£ùÁ$T”q“`¨?b“´2dK{!Kšnmˆeï[DÁ` š[{!ÄaÓðÏŽ4ŒŒ ²íí¢ZÀØyÕÞ¶wSÀ­çÖÍ›Aÿg ù¹®û'1d2™¯¦-)OĦˆòÀC4ÑKÃFAL‚qm@Ej˜Å<|cÍ $lz™ìÈÅÍD³ŒàjefÚÂìÎ!0Ò§'ÕF+HÃ,‰Ž((èîèz_Ií´´´üNêé÷IDAT ¿Ï¹ã¥±+ï¾ûŽiB'Ëó¦V¶P-à-+€Í©ãÓ>€`lI£ç,QxkŠXR®!žgHÊ'è\-e xA®‰¼­Ð@ɇ3–}!påÊ•Tþ@¾P¸úÒK/ÍÇ@ðÜsÏUò…ü¥4ˆzï½÷P«×ƒ/Ä2RÞ±²dIÚJÒ.NW† ÎRS ÈøÀ«(”‹ƒMÑ0ÖfIÚ'a²9ªé +"h¢L‡0x ÏæzÔ?瘜˜D½^Oëó ×;—Ïý¯4Bâê»ïÂáŽÚ:6rX(¢“´ŒF®(§P«FÅz»4Uûö„0k' 9‹ˆÙ¯WÑ,ˆmša íT›c0S# aÄýRHëqÂ5º‰†ñ`¿‡;Jû^¹ò6Rΰ“nÁýõ¦ €ÀopÎR‘ßxã ø!ëÖq¸Úh‚s®Â@i͌콙7 N¢Ô£p¢ mIÇÌ:B36OL‡e­H€nPÁ¬9èÍIÍ·Á.o´ùEs¥ž7”™R¢ˆ¹eœÁq8æSï”Vh)\õÌ«›‚àüùó7 --çÓÀªR©`äê¸Ãõ€F !&ŽÆÈvÒ6 áÊB ?pŠb#ÓÍM)™å+02Køv¾‚í,ÚSÕ’€FMWer=nNÊd3 ÒÊÖȼ¸®©™]Æqá¤Uù\>¶ N, ^ÈþSZVÂù×Ï+´*m@|iÙy:õ,rœ¨Yd<[`Z@€`6§0$„„4{Û!e-¾Œ!ÍÆó&kâûÈ O °õÔu(M§„°"‚H 8†–rŽååe¼såÝTrâŽãµ¶¶þÚZ‘|ûöí·êzª °>u솶Ákxð|¾çÁó=øž¯RËÑ6´j¥q¢‚ÕN"$ïÀjµ:æU( Z¯0<%ìH7.ÇÊÛ@Iâö©2w$Q¦‹@0%Ät8+¶@[¹ Ù\VÛ×€(âÑHM‡ápШ•Íu]¸Ž ×uÁ×Åðéa\¾üV*”Ê¥S—Þ¼tÄ~>©I‹Å¯Õgë?žæ^9{ƒCCà‡@ .9$"®#¨ É´ÈÕ¦b¡8²¹ zzzˆ-77È‚02öC4Ÿ»8Æ ”Ö4!cYMC› ú¼©"ÍFý€h/Ãw°¦¥9ŽnÏšÇÁÂÂÞ~ëíÔýÅBዉ䒤'wîÜyriiéïK)×¼ùE= 7÷m&És¶¼!©CEÇ´˜‘š-Ù,NCgÆ*fF­ž2öL’Îjv3øWX‹ÖÒPÕ*”%a nrµ@Â?åjÉŸp¸ÇuàDZ ô žþy,..¥uG߸ðæO&š‰¤'Ož<9ÕÚÚú\jßàüy,.-Áq¸B¯Ú3­î ýÃ?Õ"è`)ÛGð…0#±©ê±Ðƒ©Â¤üp{¾`«¹ðÿRÿEÏù¾¯6õŒ6 RSÊ¢ó‡Ÿí«ñóÍ d2"M¡ýs8%ÈêÌ£ã8¸:r““SiEƒÖb˯7¥™5{aËÖ-gW–+?‰UöNLj”XXXÀλ´-U«†©½ˆloÚ°åŠK“UÔ¤û\„b2¶6ÔFRÚ<>«HeM4—Ä'Ð<aT7EbP¨)mF²‹˜€ˆ ‰¢dQS­¾wà{>¾ýíoÃóÒµæò¹™‹/ý@3ÚvSLOÍ ­Vk;SQÏQ*—ÐÕÙIÌ‚Þ T iPÖcNš-df²ÕBÖ–IÝ q‹!p˜À0„nù¶ðaÙq³6B" RPª5'îÂq¸igNŸÆÔÔtj-ÐVnû§ÓÓÓ/¥Ö°eKÿpµZý))%Oó¡cãcضu[0vFÊŸ^JÙ\@0ùøªá‚ŒR̲/±Q¦>Eê4*kÔ+ mz`rCá »ÒiLC•dv¢0V¾0·àåp®ë€ó ÅÙ³çR ŸÏO^ºté3«²ŽW{qjjêÖÐÐÐîju%Õ„3)¦¦¦°k×N“C˜DÀZ€Ü@¢ÉŠz­̒ؼÖÊ5H.1‡L$ VyÛ07²ùê×Q€ÔÛçÅö` c¡ýÔKËËøöŸ~;Ø^0]½]srbòuƒ¶oßþÍZ­þyß÷ri>¾Z­¢V¯chhH©sc蓉@ˆD,c‘3‰«dF eóêN@’³55TSD‚¥É,!ÌDÅ”’”­þHðÂLÅìɨ#GÍ*þÖ·¾µžÎ"”Ëå—.¼~áܶÿඪ}l¬¾óÕ¥ÅÅï‘)ãÒ›7n¢\*¡««KWÉ0/ÉÏ„cÝ É¦™Ì ÷˜íHÓœP@ØÛ×5ó ŒR¯òöõgQµ¯…/Œç©ú§“à…Q5À¹×Ñ!¡nÂ}êÔ)Œ§€ã:µîîžgÇÆÆæî0>6~ª ÿjµÚ¦´_æúõëèìêBG8™ìBKDZ@H0¦5ÐïHÚѤgG ]“åÓüøVS ìº3á ²^"çÑw8\7T g_y—.]Æzní¿qîܹÿ±&À¬õ¤;wî|niyéï¤I E·Ñk£Ø²e Z[‹j•ÓÁ•Ê3'¡!qóÂ7Yãß(\¤²6¸Ý¬b›Jö.¤gÂ\ÙI=$±$5¿Ð| ª¤ÎBaSpÎà:.._¾¼.G …üÄåË—?5æ×,б±±CÛ†J•JåhÚ/%¥ÄµÑQ ¢¥¥@Ô=Âôq‚ H “Í&”Ù¬ NFú× l6)¶Khª›½¹…Œí…¤…&êŸFN¤þC@pud'N¼tÛÁÞ‰Ù?Æýö®Žï›š˜ººfӑ榧¦¿Õ¿¥ÿ3µzz³àû>FGF1Ð?€–b„hd-!£»jíõnmmƒx·“1-MÂEÒ¾FI»¡È„¾XjÆŽO>%ךæ#̸ÁêwW9„¾óÂwÖèìêüâ믽þ[©ü‡´²cÇŽ¯W«•Ÿð}‘M{¬çyx÷ÝwÐÛÛ‹r¹:‹Üê7d¦Ã(Í•OÕ>¨·OJ¸êfÕ È¢OoO_3'ŒÈÄ=¢m5ƾ(C˜ä*à„©uÇqpñâÅ@Èõ ÜV>}ñÍ‹?œÚ‰L{ÀøøøÂŽ;/V*•R¦ð#„À»ï¾‹ŽÎt´w¥ßh®AÄâbÚÞ.ÉB·7½"3Ì µ©³ÇÖf¾4 tí@û0ZÉb+‘í§«_‡~AYØQÂ玃sgϮۓB³mmmOLLLTï:`rròÒÖ­Cí++•#ë9^J‰«WGÍåлi“ší @¹¦`Í´1CœÚXý‰ÎÛj{ˆ˜##q$Í1Iø„C•£?×RÁA"R⥓'qñâÅu€sîwtwþÅsgÏ­ë$Îz?xzz曃Cý‡VVj¬÷cïannƒpÃI©œs²Cˆö"a(0„šÁ61“ªþãuÄŠIP‚´ÛÊ“ú+¥„ÉŠÚë•ïhÛ€sŽŒëb~aßøÆ71¾Ž<‰–egw×?~ýÕ׿¼îsàÎnü¡‡:3??¿ïNNRl-âÙgŸEwW|á‡ì=3X•sɄҨ÷QM 7vce±}šÎ^lJ-‘†æ¡fˆf i:9²\œ9àN@€ ’>A60(´»w®¼ƒ'NÜÑpPÆ€®®î/]¸pû¬àÝöìÙ“©VW.,--?xGhâ ûöíÇ£{Uþ€@  ªEˆ©P6>[À IlÆÜÕD…ò ?ÐqTk5¼|æ Þ~ûÊ^z´··ÿþ¥K—>{§çÙA­û÷ï}³²\é»Ósµµ·áè“O¢··7Ôzz¸ƒ46ihs´¥#Ù“QWC€9D’L ;¥íw\ó&ɽÃ5Ò'Ð WÞ¾‚Ó§O§nIÌvv¼pñÍ‹ŸÚùmØ´ÞC‡ LMO[YYéÞˆóíÚ¹ O0Dà¾ðC†ŸXSSS­Ðóv?\&iš5¿€4¦DÍ®Üa¤Í=TýŒãÖ­Yœ8q37f6äZ··µ½rùò[O`-“=ï%àÈ‘#½SS“//W*q>×qñèžÝؽ{7 ù‚Ò DNƒ©KÏšD³ÝÜÐ6×ÜÌš‘íúT—3‡Á˜Žjœ1ÌÎÎâµ×^ÃÕ«#ëNþ$Ôž»téÒwo6°oß¾ÒâââðÂÂÂ#uNÇáxä‘ÝxtÏ£(¶´´-)­Z½0sö:4´ízó«Á莭Öãˆílv2ôxroܸ×^} £££waÐÕÙù•7ß¼ø¹–Ù]Þ¾gÏžL£Ñx~nnöȆ~YƱcçìܹý[¶„[ó Uî5Y<´½]¹x±=Í‹A¹Ž}]ÍCÒ §…pÆÐð<\Å[o½…‰‰‰c²»»çK.\ø…»!¯»:Á÷zóæì÷oè>9:C†|»vîDGg')Ôˆ¦U>£ð#ãáV QÛÌ&ÔHõKxûí·1:2’z¸ôÚ¢&Çëééù¥óçÏénÉé®oãð‰O|âgoܸñ¯Fîn}F±Ø‚AlÞ¼}}}(¶cÄ{ë¸ÕboXYKNhO³³s˜œšÄøØ8ÆÆÇRm?—öÖÒR¸Y.·ýÕóçÏçnÊèžìåñøãï››ûÆòòò–{ñy¥r ½›6¡½½å¶ÚÊm(•JÈd³vþ:.ýðáJeó XZ\ÄìÜæçæ011Z­~/~::;^*¶? —úÀƒŽ?žùÚ­›7K‰÷åæº.ÜŒ‹|>l6‹l&ÇuѨ×Ñhx¨×ë¨Õj¨×kª§ð^߸ý®Î®õÆoüê½úÌ{¾«Ïcíý©›7g¿X«Õ[ññÍ2kÅëííí?|îܹ—îåç¾/[;;v¬<55õ»sósIÁ?êÂÏf²ÕŽŽŽ/]¸pá ïÇ翯û{=þøãOÎÍÏýÞÒâÒö¢ðƒìèè|¾½½ý³ÃÃÓïÛ÷¸.Æž?·ç ó³óÿ°Z­–?*hmm½^*•~úüùó_ßÁx¿\”C‡¹ËËË_X\œÿ™J¥Úùá~q¢Tjý•óç/üÖýòîÇíùž½{~niqñ——+½á—J¥+­åÖyþÕó¿sß™¥ûùÂíÝ»÷oWª•Ï/Ì/< 7ù ÝÜŒ[/—J' …ì?õÕ ÿï¾õM>sß¾}[«Õê¯,//o¥r眅»ëì1ÑÚÚz%ŸÏÿ~©Tú7ÃÃÃK÷ûõe´ÕµoßÞÇVVê?»R­=³¼¼¼òøãoôýʽ d3:9÷[G 8oΑai€ÎÀ!&ˆCÇ)0Ê)p ÞI§óo~ÿûß¿ú¡=Q†qèСôØäØ>ßó0ŸÝÇ|osÕó»ÀyjÙN!³®›êw]ç!ô”ã8Ï9rääÇ x/½½½{9÷¿P­z½U¯º‰s¤ïô18ŽSt\ç¬KÝÄÿ}ôèÑóƒà6ëþvµZýùJ¥²Ï÷½æ»í”;”Ie~N§ÿÇáÇ_ûËt9pàÀvy¿Už/ý„çûÍ”›rÝþL&ýW…Bã~ê©§F?ÁÒ„ÿ³¥Ré×ËåÒЮ¾%•L6û\>—úýgžyþÕApsϽþóó¥/W=o͇Ìç™tæõL&óoŸþùï| ‚è…8°ïËssó¿S­z+ñ!¿d2ÙÓ™LæwŸ{î¹§?€ÇŸxü³³ÓÅÿZ®T6à#t!²ùÜ‹ùlþKÏ<óÌù$:´öÆØ?+NÏôrŽì…:´ÚPhø_étúWŸ~úéòGdÿþ}_)ÎÎý®ïy9||¤3™+õuõ¿xøðáï~¨Aðä“OöLMM~«T.âc±Çùºü·òÙüÏ?óÌ3³:×u‘N¥á¸\7×uày>ªÕ ªU¾ï£Z©`¦XÄÔäÆ'ÆÁ|ÿÎÆRéáúúú/9räžô¥/¥.ô]øëé©é/ðÛ¬ü3é4V­^•+;ÑÔÔ„B}éLœ3€Á÷«Ãà‘SA@@<6?_ÂÌÌ4ÆÆÆ122‚ë×®Á»ÍÀ „T õu¿ÿ /þÁ\?Sœyº4_Úr»Ž~åÊN¬Y³hll…̃+ãààò¾yû§sÜÑ@Pb\9ãÅë7ðî»ïbrrê¶Ãººº§\×ý©£G–>p è=Ø»³8Q|Æóª-ËýÙØ¸qÖ­ëF¾.Æ8çò |H`œ¢Á>DžB¨dB‰x„PBµÿãx÷Ý÷ðî¥K(•–_VÙlöG™LvïÑ£G'?0 Øw`ß3“Óÿ×g¬~9tCO¶nÝ‚ÖÖ6pÆà3ŒqpÎÀ9Àãâ~ tB(¥p”8Ž£Ð.c€B 0Æàû oƒP€ÁfJip%œs\¹rgß<‹ë×o,·ªëoiiÝóôÓO_¾ëAÐÛÛû3“S“ÿ“3–Y?šRlÜ´ ÷Þ{êêêÀc~¸ê…à™¼ŸJ¹H§Ó¡ñçÂq(˜¯«ÅàJu0Κ$¼Ï‚Áó|ð,‚pPШ€âÚµë8{ö,F†‡—Ï`L§¯çr{öØÙ»{÷îþ©éâW9ãÎrÙ¶­[qï½÷"—ÏÉÕɘ/…È é2‡\6‡L&Bßg&à þ Q‚€1iH•Â9X¨R$"Â÷) *@8 àÀq(FGGqú3^&0¸®3ÝÐP÷·žþÅî:ìÙ·çW¦'§ÿ ø­f[{+>ýé‡ÑÒÒl Ÿq¹*3™ êêêQW—W ð™i “sùcåsÜ4ƒ+BvjEc°G(8—ÇBà8J=è`Ä«¯¾Šù¹ù[>׎Cç Í û^8òÂÉ»{öíù»ÅéâŸ1ßwn‘îðàƒbÓ¦`ŒÃ÷ýˆðëêêÑÔÔ‡ºÁsà’¾…‘ÏÁ áK—0|Ny ÄtøŒ)#2„cÊÈPŠÐe¡  ŽãÀ÷}œ9sçÞz[²ÑÒ!5YWW÷ȱcÇÞ~ßAðè£îŸžžú>çü–l€Õ«»°{Ï.dÒø¾€Iá 455á¾T$4Ȉ%tHЃÍÖsHã’)ÖPF(m€DSM”P©zHÈ Žã„·8Žƒ±±q¼pô(ŠÅâ-•RW›v9räÚû‚½ö>439ó¼w Q@àvâÞOÜR¿/ÿû>C>ŸG[[Çó}%àPÚ„)\º~’ÚÁ¥ñ§G”ÐcÔcSŒbõCª@×\Tߥ40") ™!`×uàû Ç¿„÷Þë¿% ä²Ù¾\.ÿЭ¸K¦ïÇ{líôäÔ‹žç5.õ3ò¹<{ü1¬ß°¾çÃ÷|0ŸÁ÷|¸Ž‹ÎÎN´47«Õ,\3€~Ch.ÏË`¸Òð½aTPÄô?ñ˜xÕFPÏq+²í½@ büPU‰c· OÇq°~ýzd39ŒŒŒÄÆ-sñ<¯…þ™áËÃb…@o/öíÛçÎÍ;T.—×.-­­øÜ…BžçÃW¿ç{hhlÄêU«à¸nè§#л$4ÈBAC - !4j[E%@La Ÿ?\½R°LšÀy[ ’Gƒï§„ ›`ÙKÄ#B,K•ÒÞÞŽÕ«Wa ¾ï- UoeϦ+‡‡¾wÇ@н®û¯f¦§{— €U«VáñƒÃq\ø¾R¿`õªÕhjj”®™\Áb‚†BEèŽ PPÔ/P‚ýy¹¤¹ ‡/—RÕßG‰ ,Ù¬@)•÷C­#I‚! ûY8® ß ¢¢4ÌÖPþˆFíT£t謠tºXù&kØÀBô½¶¥ÿ©™T«Z Þ«„Èd– [ìË¥6‚¡DÃŒ%Õ‚t:––ô]º”ÔHt]Çi_ˆ j¶|ï~t÷?)•K‰;ƒ|ðAär¹À` tww#•JIþ'ܨæ @ ýovJƒ†Q~§p(Õj©ù:Á”ÊŒ·µ,ŸCÃÒ1M••å„“ðu”ù~@©™ÜÒê%SÉcVq y|”‚3O†ÓUVµkMº»“‡ifçæ~ê3ŸùLKb&hokÿkÏóU ·´4cÏîݲÀ÷|4·4cÅŠ²`ÓˆÉ [ j5SåBÂÌ"êŒb„‰55"¨Ñ2rµ¢#Â(ZT*ålÀÒ Äz6Rªf?pðàÁ5#WFJZtÿý÷Iæû>êëëÑÔÔäÈ#è×,.,m™(ÖRºÀðåaŒŽŽà Ô sñ+‰Ã“ÆâÇ‘T.ÊÐÅí€5P‡j5†\«6V©ëºª®Ã(/ãŒ#•Nieg,¨0b,È ° XD¼–q¶ÃÕƒSUÕL) BÆ”€2 ª!J´Û”À÷ý Úêû4EñÀŽ8vìX")ÍÏÎý €ß¹)¦§§ÿ9c,QbiÆõ( ¨V½àÇ3†®®® & \­<&Hb¬‘•ÑXæK󘘘u¢”…å]Aù73HD¦Ž1?¬6+‡õ’0»|]ÿõY ‘§œ›C"„wIPU¾î‘] P(E'”«À¥àÌŒ+¡F@ ämBH Z©(·Ô³±§NBqvñÍËåJ¥k÷îÝOüð‡?üÁ‚ÞA¹\þɤ¶Àý÷ßžÈ 7ÐÔÔŒ|.†Ì‰nòDJ¸ô ˆ ë6tãJéV£Ž0J¹ k M},ÞcÖ2­Ázž‹"U¸j¤šuŒD¶¾‰Ãž¨¢Â‹.(ÂÄUÔ.2?(¿÷Ã\Â}÷Ý—<¹äUuA±··wW¹\ê ÏÄ¢®Ýk×¢¥¥%løhµ«kup24Ý =B5PP=b¨^+lD,hÖBÓ‹4T¹x¸ò´ònn©€`ÕÃ`¡¡eóJˆ@Uäª÷-À`½¼]Ä2T„R¹FK5SÜqq ß÷C6ßø ›6ol®òš››{ôСCNMÌÏÏþrRdm¿çÙñã3 Ȥ³ò Ðëaƒ¨ŠîA‹˜U8 NÏb®ù|+;;µ~@ÜçLSz¡¦^ÊU DdÐ@¹zšß¯T‡ª$·23à5KÉmÐ…jª  .ÝFïCÜçF^DŒ‚†Á,}¥›jAoz¥@ÚŒ LÕÓ³IdÆ”«ÕŸÁÃûÞZ­V:’•‹m tV€l6ƒB¡ ó÷ f‰•¦TŒ ¶ÜÛ¬ 4Ê7¸Qˡ٠¦ÐôÕ Ø«±«8²ÂmP1SݘÜ [kv€tgõdWtè-va†g`444 cÅŠDlPš+*¼Äÿ>O© 6HpÆÐÞÞ.ƒz/€ÒSµ0Xf*YËêÛz>ö?Ç‚B·m‚›Ý°ìÛ0UE$O•+¡Z 5KÞôÊ%³ffHX¹Ú+¡ŠØ²eK"¹U«Õæ½{÷>ç{û“Õ¶¢¹¥%0¨Â¹ÍÍ-¡AHô%oÉš¡zˆ^Œ—žû/DÄ ‘s_ˆª s®áZÞ‚Hûʹ10Rʲ ŠYPÝ0ÔÙ@Øz(\€Ãg¾œ› *“Ö­_F8“”¨W¾A©TJ§õÖI$²°v>“Nëõ8Jð°X@W²Fn@Ö,ðãâÁ¡VlÔP”.bª…,æt“x¦`• Û*ó -Üm²Öfó>ÕZãÄ•#“É ££#W«ÕG ìîÝ}Ÿçy…$Ú ³³S®*Κ›ÂV1Í Œ$WÌd ±?ˆÅ j²ˆmÅ׃nø›¡Å¨Ä¨›ILÏBAØ$<Â0qþ?¬šÝæ5 о ~1t­Y“Ì8¬”· ð+þg“P ¥íí+ŒAýT@ÐZÖÍ‚éBk)ƒVTj®.ÈÈ^-cLwãæ(•€ØÇc¯ÚÌ#VCýÀö<9"ekÔH`é¬G­J먇¤’M\ ˆïîìèLX‘\mÜÕ»k‹ó¼I+W®„ë:jC8B¦ gΜÁÌÌL„lkÉ‚eË€’)ªeìÅE •o³«aSX“KÌNe<Âñ줒OvÝ$d8}!èà÷m&àŒcÅŠö€5ÈôJTª^¢Jâ®®.Í0R†!óós8wîÞëžç™Y;½< f›—‘®16t!Û1ž8Æùí†Ðe4)c|ÁÙöH<#~°€«Wíé0jj¨£D^µÉ‹óOŠU«’ ˆ÷ýêƒÕJ¥+‰>iii1X@×Ó¢Šïúõ8{ö,®^½&G¼¨~¯à6·‹1lu€ø A-wìfÞƒsÆeˆ[zúœÎjÛŒGÚÈãl »ÕDOÛPeë4êÆœŸ s‰k#u@´µµ'² À¤Ž7FZÝVV±Y »@~·VÛØØ”,häy­àV|ž(ÑPh¥žÇ¥€Ìú9kx„FžWÅ•«W1:6Šæ¦f4·´ “ÎD(Ï8F9xLÚ‡ß< «¼®÷ÁEcHíÏ6Y@© µÌ(WŒÀBúzÌ„sÛH²ê u&$Ö„µ@44$e¯nçÎy×÷ü5I¦œ´¶¶˜I‘-ƒ©ß <}\lpRÇ'&055…úB-Í-ÈçóV¹•˜@,ŸÜ Å€à±Ñ;U8rÔHq£–0¾œÜÔ0»¤¡îs®EO N´Ì+·POdÙ!Ą̈jŒËåJ§P­,º›™¤ éµ.Kä`ÖÕÕY1úQÙ›l`‘¿@ÄÜÜ,J¥Ò©òù: är9ù2J«N¥ˆð¸Š%ØÐÕÁ¡g+…Lììeôu5ÙA³€å4“ùµ¢U­ÀRí‘è¥8&‘Ǩ««ÃäâA×w»]Æxk’©#²ÀB¬`Ûð}ÅÙ"æççà¦\Ô×`öAĨ5:¡l¡Î¶]°Pj8J–<ÆÚëaÉ=8ŒÐ¹2y|ézÜêçÍN©#èfN"OŸû«\Bh6 ¤Ò©š±þ#ŒJ^݉†•‰Vj],17;‹7ß|§ßxMMhnjF}}}M¡ñÔŸ—5€‘;XŒ]¡W fa2Ý è“I¬16Vº9"à¸s‹bì¢t:Ùôæ±z×g~lFáº)eù.ãÜ:!zqbbccãÁaJQ(PW—G6›C>ŸG6›Yµµ J`Ät&¨Õ,²P+(¯÷ŒY†F }-Éæ.@-UYg ™LIäIov“ +NÕþRë>-$µn±…þ}¾ïcbbãFh8›Í†Q*…T*×uÃÁ‘®,䈢bû»<σçyAyyXÚ%ZÃâšC¢Ã+îÌCÂ>H¥Ò ßG¨ $]Ðwßκ<( ÄììlduËYH5&‹qÎeß‚¾"U·óÍþþ_’´‹„(ðïÀ¦PÜ®»•“Bô¡’\ëP¸¸,ôR0q(ä°Ç¼çy‰QàR‚D#²*•Šî¬i0›Š~A; Îÿ¶ !D2ŠˆT-|0°\¯p‘ÞijÊ#«žà&L`«¾…ÍÁãO]¢ ¼¯J5éÄ36çrBJI ãëCHm¾5·6@¨µÊ‰aêSÈF:&~µGéÝ,O‹˜2¾¬15 ¨[Q™zCü ½'Š5;åR%µâ̹„cáöªUÏeÔˆ¤;3ÌM¦Ð«xITUpk†ˆín¬¤wß„=ìÇã€`«ó3xÕ¯"Cbƒ 8IŒ!Œ‹ÍR‚[³ƒ[óó¥D&KÜ ‰6L˜šž6rݲwǰ¸œäg‡7![¶…ðÅö4úýÚ™B1 ”x=Kd\6* ™…«Œ¸á6 «Ð#nÔž^2/~´Æd6Ðs4‘’vm¢«ï{(g’ê®!—¸äB+|ôÆ ˜=ÁpßbR‹x¸y”ö¼¤€h'KŸ!Hˆ™¬Vùú¨@)¡`PSÒ͈&©itÕ2IìüÂHà3²^)ÑjX¢ ®ºãØÀVöž @±X4Âß7»PêxÇŽró™ü™$°)—Ë(•Jp'Ö.0 BnšÜLzˆÙR-ˆÕ«Íà’ö‰e~òšÔ.æ 9wÀ€ÐÕ‡ Ñ k€…ªúHÊP´¯/€Y;¸ðH@›‰kÛZU·nLLL$s SîÐçž{n€:N5IYÒôÔ”j¢ÐÚ¬d0ÌMÛƒÐÕ@äõ «ˆ7T˰“õy”Dº€å°«˜ /ì†F'qŒe®mµ`¸£èL¯˜æ¦*Œö-h¯×Ù@2 6Ý,üíÓSÓ‰ÊË\×½!œe¤S©±dvÁ”ñå¢dZQ˜»g1­a²É[Û»[  «{Œ¬Mߺg‚šÓÍi|PÈ>ÕÞ'€˜‰§èô&\ÓÒ€ ©<¦mâ¥ç>ôF XȤFÃ*Ll¤t*Ý/AJ¥û“@èÊÈ­™R±Ú—Ø<ÑÞ@+¯@5sùƒ£z;^›“ÑH¬ae„HàÈ€lSJ¯±€Ü¤K«R&R–¡ÈÍšHX̽kÜâÈaèòP¢ò2BÉ ×¥g“¼{``0z”h:‹+À,ô°sU”—ûÙ‘C=ŽO5Áë¾ §ñ‘ÁX¡×˜y\ÓsІh®°ø}F߂ɶà¥çÄ,CÒ2 u˜-bjrIä褜ãÒ?LB#ÓÓS(ε¦t¬m˜‘1màšJˆœ Îá¦\ó-r]÷Ç®vÄOQÏ4@-`1E”X[é ²É8Œ½#@°;™Y±eÜýTcßÐ>H:ÒŽÂ8ÏKd2™ï„%1*®\¹ªÆÆSÓ.ˆìK³ÿ Úc{3«XvçZB†Ê4VhÌŽ'úNçâj²„6ÓÑÜ‚n#Äy(oEc¹ÝîBíï,Z醿dÖ` ÅððH"£0›Ë¼ôÒK3GÌf³‰‚FC—Ã1¬ÊÚ¦”˜Ýº±*Ô³F˜ce¢“P£6@œ—@Á•1“LÃk¬- 1n Ú›tÄåTÏ‹ö0!‹–ÁÅ1‚q.ì1½ýý‰˜ “ÎÈЀ4©³ÙìIúØú.öÁ÷|åZió{MÀ8 ²a"2^Æd¥&xÄØ‰ßÌÒü@âÀ@#ž‰}<ºgbtKÀÜwQWúv¶ðg¡>t`IDATB,#hª€Z3‘¯_»ô&_*•z&‚t:ýͤ)åÁA9ET]PJµ®TB,"¥Â\8£P g¬rDǹ†adµ×6oæ-Ôz¿ËT‚î&ëÂ, ÷HUÊjƒD@Si.^LlÔ××ÿymmm_§Ôñ’è•wΟWS»åÄoª¹…zêÚ~>NOÚ-UzéXlPÈêg3+€H͸r°è5>ðëa ÎF°'­¨i.vïÍX@‚<~ |ÆpîܹDö@.ŸïÙgŸŠ€à©§žšË×åÏ'AÔ{gR©¬Æ½k£ã#'#lÃP’}úäQí¤Ô·^A¹rQ0`q.bŒð)nϵ¢îlÄdq£q¢, „uB¶Ulpyè2Êår"&Èf²Æ|c£3›Ë~# ¤ã¸ðÎ;áÁ"”P8r~0j²Ür>fus„ZÐ*rã€@­=”,WŽÆ¹q Ýôâ˜Å˜­D¢ÝWjSmb ¿°{ uhÍP–çƒ)œ[‡:òö¹so!á ;îæÝ?¬ 0ü¥$Qá©S§à3‡:p êPµm² N¤ ?ŸËÿ·¤U 'Ož”:K²fH#›º‘i}‚L›¢gs(5˜ÄØ4Öª°€·Še 2K¤vÑf c¾!X8\“«ß˜º®U@놠´µ° ÌÌÌàís‰L8PÇñþ`1‘tË–ÍãåJ%ÑXO>ñz6ö Z­Âó|xÕ*<߃Wõ´}Œt£F‹ÜÉ鞊ò„ûS*•011!·¶³'„ªé&$6ž¯$˜ÛÍÅütsF27ZÆÌ&7£yÊ5ÎÐÜÔŒl.«ÔMÚb.´xL1¢/_#šiRn Žë"•rqôèQœ=ûf"465¾ræ3Ç÷X5Èõõ…o—ÇF!ɼtü86ôô„jò‡Q'˜ÏÏ(å,˜×ÊN4‹™Ï‹ê d²¬\¹2šö¥Ú.(ÖTÄNýê;•r,Ü@ ¥¯W†WcÎ ¶ cºñk2€l†1f)2åé*A³“D•Γ“xëÍ·÷eêò_{öÂ1 $uÞÐÒÜò¯FFFŽ'fX»¶ûÕùùù_âœÓ$_:00€M›6#—Ëi5ö\âVñhm èm<²¥,1V81û`qrýµ1¶Fz¬a„à`š»ËŒ,(‹ÌC’QBCpk°¶b!pÇq‘ rS¸x¡/½t<1òùüÕ3gÎ|a¡×,‚áááñžžžíss³‰&œ1Æ0<<Œí÷lC¸\ÛFžÈ€ÐÍ ªuíV/¢uáè`¨ 赌X`j)‹|\\ˆÚ«ŸGæ%é­òæÐme/JáºN¨¤0=3ƒï|ç;ánk‰ò…hëhû—‡.¿µdÀæÍ›ÿ¦\.Ùóª™$_?77‡R¹ŒžžkÔF 8æÇÁŠ™.·÷ARs{ôÚ=ùV=jŒ°5ÆÝK0h)_ÆŒ@ pÙáoUWhÚpm{2:Ô›R*œã[ßúÖR:‹ÐÜÜ|üµ“§~óf¯»)úûû+ÛîÙVššœúLÒq×®]“öÎ<¨tÁŒ¸é[){€sa‘·çZïŸ {û:Åš­R+™ÅÕçÑJ(¿Y†¦ÿ7½"Æ£p®#àÂu\PÇÁóÏ?þþÄpSn¹££sÿä-ƒ_Y·~Ý¡Ò|iEÒƒy÷Ýw±¢cZ[Z¥GDë95Uƒ¾‘´š ŒØ¥…eÏì1KÝ¡meoVùÔ¾Ú¥ò"‘Ãâ‹`â„Ϭ±ù64A€ãô/né%œ>}K¹´µµýÑñãÇÿÏb^»è`ж­Ûž›ž™þÇœñD»§rô]º„µÝÝhh(Ö¤R1º] DÍ¿Ñ%±ÁP²{‡Û.k µàQZ×weBÌÄTüEc"<´L8NðØÙ]’!uuù+?úÑžÀ",Z ýýý£7m,‹Å]IŠ1†¾‹±aCêêò1@ ªšF,YÉ%fŒŸX1 Õó¸Ðh[Ô´ ôê_iÀ1S˜‘­rãjâV¿å9V¨ûS®€ .âð3‡cF.B ”ú­m?1<4üޢߓä F†Gw¯íþB©œ\-x¾¾‹±~ÝzÔÕ×É¢PYŒ†ªð1º± šzp v¯ƒ)|;fÝõÄÜ5%2ï×.aZXÙÐÿZÀu]P‡†Âwäc.\ÄžþÁ’í+Ú¿zò•“š8I¿dëÖ­ß›Ÿ›ý’ï±tÒ÷V«Ο«W¯FSc“dbÕpÙaÄÊÄ@/gW¡KðXxc ñQFiìé KøÌrA #€Žã†n ‚C®ƒÓoœÆáÃÏ&œW¤y-Í'N¿~ú§³GÒ7 LoÙ²õíb±xˆsžx°ï3¼sþ<ÚÚÛÐÖÖ&)]ìŠ0¥ÌÃ]ÈuØMžµm€A­Ì5Âßò= ØŒ³p{:kå#j¨ PüÑ#ÔqðòñãK¶ —ÏM´4·üØàà`鶃._¾|~Ó¦ž¦ÙÙÙ‡—ò~Î9.¼s™l«V¯Òv •;$Ê (ÂLJ5šÆtÛ¢Ðó5[àb¯ÌÜ"GÄöc€xá3Åb ¦ÈŠÚçxöÈœ>}zÉ ”úíí?þòK/¿½”÷;Kýâ‘‘+³¡gݧæfç7-õ3úûû1>6†õëׇƒ³‰ì³­A B¶¡mw‹S ñÔ®‡ ãÏØß‰i1»˜DPÄmXºßL§Ü&&'ðo|ƒƒK!à+:;þÅÉWNþÕ’?·v¡Ÿüä'OŽ?p+Rh(àóŸÿ<::V©Þpl¬~›…;®&ÔÜF.ºƒˆµ•ÌbÙÊ?w)²IfÈDušË0î˜é`Çqpî­·ñì³ÏÃ'—ttt~íµ×^ûÍ[9ÿ·<£sçΩùù¹7§§g6ßš(Á®]»ñÀÎB/A€Ïà3 ŒY3íº/|²(Ä€/èjB,áS­0T ¾T‹ǎ᭷ÎÝê©Gkkë×Ïœ9óÅ[ýœeÔº{÷îæÑ±ÑsÅ™bç­~VKk ;p«»Vkä øáÎá²oqm·µh ;±*’oþ³£û˜ãtTÍ€¨‹t©› z‚êkÇq@Á¹·Îá…^HÜ,lo;vúÓ{—C~Ë6­wß¾}]Ã#ÃoÌÍ͵-Ççmß¶{÷>Š\.g°g Œù±Ù89ûÐþͧŽñ˜-l0ĹE´&É7®âÙgãÊÕ«Ër®[š›O=ûæáf“=ï4àÀÃ×_›)»–ãóRn ìÜ;v ŸËe`Fu.7+ucB·z¬~ªm ( HßÕ†YÙÕÑ$,·w@)Áèè(N¼zï¼saÉÁŸ´¶=wæÌ™Ç— ËصkWarròÕÉÉÉmËõ™ŽCqÿý;°óÁP__+|;eËym^3}`1Ùº>nØ•œÏ®ü«W¯áÕW^E__ßòX¬hoÿë7Þ8ý³Ë-³Û2¼}çΩJµòÂøØØÃËz°„b붭ضmº×® ’OVµŽJãri±ëcaì=Í“3É\Ÿy \X ÒKQ­zè»xo¾ù&†††–WH„ðŽŽÎ¯:uê·n‡¼nëÿ;îûæõë£?¹¬ûäˆY.‡{?q/¶mÛîà΢™;fŠìprœ`AUEŵ·s á­·ÞÂÅ nÉÝ«í59ÞÊ•+çĉ_»]rºíÛ8|êSŸúµ«W¯þ‡j5YeR’K}}֯߀µk×`uW—ÜÆ7~tNŒ¨s•Ç0Õ06:†Ë×1Ð?ˆÁTÊ•ÛvîêêòcÍÍ-çĉ/ÞNÝ‘½<~øáOŒý`f¦¸êN|_cS#V¯Z…–ÖV47»¨565"“ÉÆ»–ûH0[œÅÄä¦&§06>Žñ±1 ¡T*߉Ÿ€ÖöÖã õ OˆáRxÀ¡C‡Òû.~ûÆõëOrŽ÷åâº.Rér¹²Ù,Ò©ÜT •r•JUÎm.—Ka-à¿P‡z+ÚWüû×_ý÷ïÔwÞñ]}>ýé‡~éúõѯ–Jåz||1Ãç…ú¡––ÖŸ~ùå—ßÉï}_¶vzòÉ'†‡‡ÿbl|ìóŒ1úQ~:.µ·µíµ×^ûÊûñýïëþ^?üð#ãã9=5½þ£(|BÀ[[Û^hmmýâÑ£G¯¾oÇq7œŒìÁ¯ŒŽÿ³ùùù† …ÂPSSÓ/Ÿ8qâ{ï;ï–“²oß>·X,~errüWggçZ>Ä¿ÒÔÔð{'NœúÓ»å˜îÆíé·vþúôäÔo‹ÅŽ ñ£±±±¯¡©áßxåÄŸß}Gw_zè¡Tœ+~yrbòÎø΀L¥S•æ¦Æ—òùì¿yå•׎޽ý\yä‘îùùùß›™™ùìììl'¿ ‹¡1JYCCC_.—ûzccã JK§3óétz"N_u\§/å¦NRJŸ;yòäéÓyûPàf—G}tÍüüüzNy;|¤cÒ¥)z•rÊÂP†f^|ñÅ•óòÿÿtIxDqãIEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/trashed-big.png000066400000000000000000000265051271170121200235130ustar00rootroot00000000000000‰PNG  IHDR€ Ä’dýsRGB®Îé IDATx^í}i%ÇUnÖ]{ïžîÙ5£ÍH3ÚmŒlÙ–d „dâ)¬±Á€Ÿùcƒxx`‹Àü&" Çdì@²Á~ïÉX~H²ÐbÉcÍhFšÖlÝÓûz÷[œ“™'ódVÝ{«zK᪎Û÷VUV.ç|çäÉ“'³„ÈŽŒ2 dÈ(Q £@FŒ2 dÈ(Q £@FŒ2 dÈ(Q £@FŒ2 dÈ(Q £@FŒ2 dÈ(Q £@FŒ2 dÈ(Q £@FŒ2 dÈ(Q £@FŒ2 dÈ(Q £@FŒ2 dÈ(Q £@FŒ2 dÈ(Q £@FŒ¾ÿ6[…“'Ei½Ýw S>A¡5¶Ea³ålæù|†æ7“ÇfŸ ÛíÈc³uÈ·sÁr®Õ¾ºw¢ñÒç>'›­×†ððÏ î [õßaðcPãðù¾2x³Dx›>¿*Bñ­vî÷¿ò…êäFÛ~´p_.>/D°{£…fÏmŒåÒ ¨Õ×ü‡×C!þë—«OÒ©pâdß B´_b°¨Ñ±=â¦#ï;w…|)UéùBQ”ûS=³Õ‰±ÎårÿVg›*¿b¡,J%IΞG¾PëëKâÊÔ9ñüó_—¯œ¡gšN<þXí«=3ñ¤@éoáùc=ðßÄ£ù=‘Ëm¶[K[å,=R Ãêk_ÿ ñ7÷ÛDžx¬þ®´ÔIÌ=0öF›"ÿ¿ €ÂÁƒ·‰_ýÌÿΘŸ–Ú[˜>qôè]âÒåWássÞwìÖâ7ΜjžOSLbã­?„¢3ÿà}ŸA3å´Ûmñì³ÏŠýûöÂ5¦Tàg­Zx?öI£J¨£ZŠIßj6D½Æ0(A^Hl&éÓ ¤UkU™³Í~Eò‡¼;T¼ûs@£Z eZ×ÞÍ„?›níØ1!n¼ñfÓÒîÿŒxöÛÿ¤ê´ÿ;|=¹-È…¹Ÿ¡’ù|QÜ}÷#N¿ñ?ÿ‡xñÅïˆ[n:•`UÖ¿Õ—º¾Õ¿mqœ½~wª‹ªgò<Ý|’?—¶•þ=ï¹Kô÷•Ĺ³§ÅÑqð%ÄQ°Áðsîõoã鉇O–o[àlRX1îòÀü{1Éw|HŒ ï4©—––ÄK/}§³”'­I–®7r p+ËóNÚø:ÏùõÞÙ‰ªåçH„ïyßGü¿ð…/ˆVK©ø0$5–¦ YÚ¤(À(þ~qéâyóØ]ïú°Ø³hŠ Ÿ<ñ3ÂJhÌøú Ìg``TüÐ;ÊÉòß¾ùMsž )+7–Žw¯33S&´Ç~òC¿Hçýa£dNz•Ô,¼2¹3ºû݈b±lò¬V+bòÂ… ½¨¼E÷ lÈ=<<(ÖÖVLθïãR@ñ[ä3è¢ORlO"ÿó”ѽ÷|ÌÉó‰'¾$ZÍ–¹Öng]@¢o4M.oÙ…Ú`ò¼µõÊåAñÁVí«„ÅŸNRNW(…’ë»w7}“çÿùÿ×9og6@šo8M.粫XÌ9Æ÷Oüø§`”¦ì {]P KAã˜Ë=ïÿ˜3Äk6›0yÝ) ãOzo*A>ïúíúúúÄ…7-&&ˆ»Þ…,“Ç=ôhß{Øa ¤NAuƒàÇ×þõ_´Æ^mƒÓ$;¶Ü¤RªÕU§ÀŸºÿ—̹v u­PG<òˆ˜€'ħ}·ìøñ/ÿúµhÆ™ °mܧ! _ÀÈȰX\œ3—Ñ)tüÚíò8ñá•néV©ŽhËhDHKò^Oúq¸÷êi3eòo·¬A¸m”øÍ8Nú‘xýêô%‡*ÜÿËtžË·‚_Û‚0DçöÝý×õûͧžðñûGÖl:ólà—24<£±¦¹Œ¾r …AøsÝC±àÁ•AnïÅßyçOŠÁ1§Ì/ùK±-…°§í£ÀxÎ96ùæ“¢T*Š ¬1˜Æ1 €|;”ž?©þï‰'¿ûÊ©Xvdn€íC)÷Ä•’ÃYv$u Åg~óšwÜþãNÆ/<ÿ¼XY‹„%É48ušÛCè—2:6"fg§Í夎¡Nœ,|r9Œ9á´o¡àzÿé‰':¶0ÌTÀöprídòWWÜYÂ$Ž¡¨òÒøÃÃûãµ_x±32Oжã{ã;w@p‰5Γ8†<ð€(ƒÿ´oßMâè ?ì”yöìkbæÿ;™+¸‹6~"±{>\7ðÔÔE'ÝCZp.hÿ–Ÿ‰€â`éÃ@šüïqî¿Û‘M÷äцô²(ãáá~'.ãС;Å-7ß#oƒ™xÿC)ßÄ+á ¿WBí}ï=©ì³Ï=×µÙlà†ùÛóÁœŽê•p|W¯^v’ýć>mÎsùPøtp`ÄäýxãÈ ïŠ¸~õº˜›[è®L`c¯jf÷ÓR Mø}«é:éЗ34¸CiP< €‡Nöï‡2¢àøqãK6izÆýe -[“§÷§‚»=Ù×/ƒ·Í#¹Ûoû ßþ©O‰"Ó2 G(Ô{÷ÎÑüwïÙ#>þÓO>ù QK³Ù°®GJ<2< «„v™giè¢cgåu¬:›nº½£ˆyØ4ʵaÛ¢|ãòVõIž'¯;å§ó0™xå[ê8Ñǽê[.—!ľ&ã/›„ÓlæEÜ¿‰/•J°d,'`¬¨×k¢¯סºÇõ×ß!ž~ö‹x±8½P‚¸òúËxb€+h1ì -ò3ùô§Qà';Þ~à< ƒœ 5h‡ÁZ+?ð¨T—¯Y ӌҤkÀ5}¾ƒO$ÍŒùVÖ·P`ÝBšÅˆÖ»oA'¯ôÖÂâ ,B\—¿Wb ŒŠ¤!XÜó² ᜸­Š0±¹&-KwËÛÛ©üÕUë¿®—"Éà¿Ýx¥*J‹pmìÌ™§Å}ïÿ·r)¼|‰°Ù<6Kô4Ïo¶®]@«âUVr€ô¨ïìŒhmSnðØc¢uâ¤À ÿ¯}ZLO¿ C¸e•àÅ‚d³DOó|š´4ÈfóHó|Š´qÕ½2m"ˆk¹õº™;vÌ`Eÿ”s¢Ù¬‰'ÿí/Å~ÀD–h•JÁE³ÉF¤QÑiÒvæï5lï&»¿ním·›â7žÑÍ ž†ù<Õ¿ó.O–¯Öÿ~xWéàç Ï=ÿy±{âfqèÐÝè³9o²Áo…>8Ⱦí=õê?‹õ öî0T ïpÆEf:Yz.þ¦-Àî·ßú_Ä‘Ã÷Bw°†ªÑÉÃkÝßo¶KJ%Ói˜–B»]«î/ [âÊôwÅ¿ûÏD£!…~¹Ð¬ùâ…‰"bzøÑÒoBHøŸ$ý,áÛ‚0ÊÿÇ?_ÿc^ÙØBNŸj}ëØmùï€ wûþnäó¶ í[µ’ºkQm·À¾ûìãÕÿȯi×IfX6TÅGò…à|äôà„ uÞÔˆ¢‚Lp¨FÔŽRíáoüÚdäåEQçQ&X­¬nF´4ÕË<Šéôî,??S./3’/O—ï×ÓF7Äd«$í¶Úí:,ÖQö]£.¾ñ³A[¼Þ ÂǾòX#6’§w”döÉ_)Ý ýÿ÷0ãÛn»IÜr‹\,¼é£ÙlK¦´Z¡ôq·åw(¿1¸¤ÕÔ×!M³Ñ’÷šp Ÿk6Ð'ÏÆè‡{’QDD:UMb¢4Üá£ç*b™Í¯i03ÄÉìˆCú9còsÆÌ¸< Àø,´­wor­ˆZCÅ-qô¯þ¼ê®Ý‹É¢wœ85È’Ý [Ž×ÒW‚Ÿ´) ¢¤G~£Ø3qêK-4Hà´ˆ ¦Á ”Åå‹bnáuÓ§ûÒ¨¿·éf€J]æ*£p±ÁOQ Fû–Šj»û••5CÈaë@K €G üŒCÆHäLÜ0ÄfÔ–ÄdõiåK~ÿ.16 {ƒÎ¢®A0·pVT`Šôðû rdnÆU(À ›¾°-îu@ô²˜žùž¶¯,|($Å45Fý;É:h‚^DC#"¯›^*®&¶zîh*‹äͨƒ3HwÙ¸ WRÝ%¬Ê{Þ8†ö‰£G žîœW©8±·æÄåé"ê߯×þ=ï€Mšõ É4#žéV}ÅI~îEòÄ^p×t{‰5@rä  ÐoHw0?Û€ÈXÜ!|oõß×7!F‡¯J¤¦7v ã7&8#VW¯ôÀb ®¿÷HÔÝE öìºE”KCÆãG?¢LgW"’¯þ©r¸S;¾]×þãWH hˆ? ­g“ Wb¨.@UX  -øÄ‰$-(.M=/û~~øè@ÿ¸˜Øq$R`úàcG º™ý°4ÛݲM•åôøT¼Í§‡ú—c}ÌA¦‹}BnCV*Ò_  ! s[¯‚Vnž @  <Ö%œ„™Nã i¨D—‚èiÛ1r40‘ŽÃƒûÀªnÀ²÷7º:]°¬=»nÓþÅÔ‘‘ëÄ­7=(F†öŠ«óçTóúT“¬úwiƒÆH;€« “qª\¯@0 Т! dÔ Ú[oaËt5ù†¨¼¬@â·Î$梱·O?MË¡½ &u KG ‘=ŒPvLϼÑŒŸ²î¨âÇFAÇ—÷ß%nºáG¤tbíÂ⛎Æá%¯{¹ŸÕ¸¥}úørÇWò¨É'垦.w #ņ¥Ä ± ÌÀTýzÍÎlŸ àâ%‰úÏçJ l?¬¹ÐÍâ®Tô«ØB92Ø9~,âüá5Ù·ç±úû¾>»sÚ,H?íÆ»¬Ìkm_¨âs`6²BÏ0z¢íDTFÞçàø~o¯˜.¢—…úúl)³R]€òECõKvÚ„j¸gÙ«þ‡‡Ê:ÅYÓ>)×åÜQC_WgO‰ñ±è¨k‚¾2zô°þž™Ó1÷¾ú—"ÉìPë˜GæI¦k3 mÓ#øª+J9l²´ÂÔî,|ö³à¸Ox$6ÿú¯EªUÁªUå”0u‰³HT¥Dê_Ytöп×*Ó ŽõÚø^êî¯WæÁácw3ÐR1º€Ä^@LŸxaJ·&ia ’îûQr6/ÿ¼ .;9ÔÛ™w…ÎÓ6AÕÏÌŸ‚M¯÷‚çïlu{E3.Å|ŸØ·÷tWAöŒf<%/±~®­¬MÃV9ðÒ&YµÖ˜S·ù}!”²«&ˆ¹)ûý é6Ù&ŸJ€Ÿ®‰¡¥‚¶Â]h ñnÀœŒís|`ȓϡs—à·úÎÁ5á½ (ˆ×ÎYG‚;j]“ŒXSqx50gæà=»K3|Ø…MŒ—ù¬M€yäa‚gÿHºü‡`Jv‚¹Ã8 ƒ¼¨Fòº•ŽLïÕ/`Ëp©»à&ÌÎ2 Ø ”a3‚ºYø¾[kl7Ðm7¸tGoc#¸¯¡+}V#hÕ,Õ´ãøèÊ­K¡ŠÈÝàéó׌@ àØŸø"ÓIë\Ù†œDAÀÀ-.‚ÿ@el%ÜWÿ²(]÷Ø®îYf9éþò»>\oªQ™–Æm@;\ …ò…Çú‰µUP¨®Î½ý÷ŒØ»ó.Pwzõ:Ÿ»¨þôXËœ$Î"$³¹ z4v4g¢nzÖ䨢á4ÊÂ&ž»LµÒǺƒ*N  ø3êÎì8Úâz2›šåŠ€PÌ7€M Wk5ŒéÑÚøÑ-ôkeí¢˜¼ôupج(µj>Ð.P§DMð¾þ ß¶ëYÛ}[ ¤¸­l ›ƒß(MDZ€Õ&l&/Ÿ;Ê ÁZŸ¤„X-rª¹€@n GG»•K˜¡õ2U+Úã†`Êf— HWt¤edZЗ¦¾V7Ÿ#1äØÚ³Áà|ã,otß«ƒU:þ'ê1Ý<ŠÞÖ)¥ Ít¼ß×7¢F*µsß–‡‘¿ÉîÊî²N 6Ün¥ò¤Òˆ.¢ºƒ‘2"`“ÀÐh…ZûÚ"8RÐÀ#;ãôPpbG@§+7 s4ìŠDâÆ•¸Nîg•J³Ñ7Ì((7ôøíßs«Ø·û˜”Úm@¡ †Ýˆ?œl™„û ·8S¥©.@9ðí­ôh®\L€T¢ÛεÐöÄÝÁØ7âP­è͈h4®Lì!M«Xlì®ï`"§Å.ú%J…A1Ø¿S¬ÂxŸca’Ì{úÜØF#pñÖÅ*oˆMš};ÆBà騖|XF?º_,­Àf\lìà †¸Ö…x¾ñGIÉ@CVNéBšKkÛg– Å…–Þ"#ƒ¥ï²Mu83†>r½ S3Ü0N·x ‚<ÊEØ•D†úÒÄ:$V×f Hs˜äÈ®¿‘µšÁVÚP]ØT?nÏ‹Ð=ãûÍFÌæv&{p[•U¨ƒdº~V3ÝW(\Â{ jˆ –X5¤UØí {甪 @th«º™.Ž¡[8Ö'æue†8땇áloë²Ó9çÌÁß#C×I`Z+Ñ“X8}¼§’‘á=|òèšvû÷Ý)4ó=lÈ2FG`ÿm£É<å¥ÛèuF]uÊA‡£(|!t 9Z‘wS©,,4ºdàjépÑQ)IA.ã½â%cØGÿi–v†+D*¥æßÔÌKââÔ¿ÃÚ!G&ÃÀ×ã‡ÁÅïwýI«4{¤EáâÚñ±ÄuûÞ^EµÝ.Ï×=ÕUÒÕ‡°ëT¦þ–íñ÷èàøò m$÷nP¤`Ù©lÝbDÙ®Dô†PÚZ]&„ñhÕsŸ½k˜ˆh ¯HKd>êõ£®ÔÖËba¦eI8??±ã¸ßqLŒŽ^ ;Î+»p% nëÂÉ.W§lpåìÄ •þëPŠ2Ñ>740a¶hS€uG±•‹G…q8É€¶Å$Z,sOÕÿc1©4€fË<V‘F @5wHãÔ¼r¯‚Êb†ÆŒ9Rû2I/Àh ¨'h|>ÒcSWaû-åX§™¹ïŠs“_—ªRhä)‚î£iÇC~ ó¨ÃÎk<58önM‚ àRÏqû¡TüÝÒaeÞšnóK­`‡ °ÀùPmAì;|CP†‹Áè IÌ I[œ&@†ºš n¬¬_?þU-ÅVk`pÈ›ÿ¿«¢v! Iö3P( •ü…ÃØŒÝ W×`fqp} ?eñ›ƒ3™]Gc <ëuŠTòPȳèdzøà´n‚ؾ®/]0Ȇ4P”Å0  ÝÁ*Þ }æhÜÅ(MàÞ¡_HQœÉ«ñÔ.—þ¯‚ô“s‰Ui±ÿWão%µšù±ÝµüÈ èCƒLõªJbF:¿#QK·¶=ð ‘µ‰Ó@1ÛXv‘¿>½€V—„©m€Ô]l8'GM ,wAÒld<︦‹%¼¦Œ&AÓm£”kvª¾W3snñŒŠúq¤OqÉê GZ5'¹öê†ñ{7\Œã“!|ÀÏz«c|»šH:–z0RÕHyÿp•otö³Ûí}Ú¯Ô Y Ów©À„Xƒù:и“ŒïÂpÛË€è°Lò×p M«2zÇåt Ñ&óô¹yB  ØÐu ­/ «ëðÁ{ €VÞZá§JÉr–.Bß ÒG 5Ž¥G–ãRîØCÉùí€ Ëãž×:{Qd˜bAˆá[ŠzȤ@£fÖ+fÓi9ïñˆeí¢¢³ú׌ÒÃ.Ê V_r¢wñúÔÌ‹°G …Dš‰„£ÜÅ@;™ “'Ø ÂÄ Ð¿c0Æô4„™é£TÐ}Œï8(.^Æõ†„›8P|ýãHÕ_ÕU®Ââp&¯ó©G©Ð`‘¨~¹n%½rŸTà w=Ö¦³VqIX<]” óIDAT1“DÚ›BÊòêE9,ì•‹¡ZÊÚׇ$}•¾ØÊÙq! v_G®¿W:«\·±€$ŽÉ4KøÈi ááíPbjâEm}šä°6€"„Q³†Ÿ(:ê„R¬¬\Ý nþÀ_Ûø¤UÌ:/I˜P‚pð¾2.ötåŒÿýFuà‡gÎ}æJ0”Ì”ªÚуçIí²”Ø8ãª0WãÆº'`Dêa`{ jƒB° ÐÒ•d‰˜ëþ4ºÑ­fD¤§ð¥kù`Ü_„YÄ®‡ÅŽL†„²Ö¾+¸ætDEY#B£ƒ]åÆ ç£ÃS8y}ò0P1A×» ×;¹z{ñV¡À}éÔóXNj<þW‚á…„]'„Ø|@lp¨‘8"ˆmž¼B´–—$–L+0ÑÁÛ ØàIªÎdŸ ò¦ÉXOZy8ƒÛ#°«Ç¾Ý·[Ƴ||aí(¼ò†ºûæÅÄìÜ|РÀ«Þ(ÃÝš«å`Ø ý4WR«ÿ @KŠq“%¯óíK˜Õì#ZñFznT?.¿uΊdæÄðQfà\öE‡ñøI{À±Lª#ZÍ6Äî•1ÀÄÍIžuq6 Wg_‡÷óœ2ÈTà¶ÂäÖ¿O-M Î-ÐÚ·:H£V¤ZDy¤ÖŠ>à†B«Ðà0ÐdÖ1<œI6ïƒ#ÈÐÄÇäÌõÊx$ŸX]ÇH+áN6„%*¬v´T\#OÍœr\ºq¤÷t‘_”<_Zž¯ŸZÅÒG¯/ˆggï«´î+@vdà$]ï\£)6 ¦, Gf{R8·Î ÎMBI[%!ÖÀó™lÏ•ÌÙ)®Õ–€q¸´›Yå Vq5€ŠàѺ’‘FU“åo¡'IMÓÝÑ€Êl}^w0.þÄã¥WÍáãq?kŒâtêo~+r_=äÃ@1n¬¯Ï‹Óg¿!@æÐíãÇñ3™=$³Ü Ä—wBºnH¤J´ 1‹UC À÷¦ñÃÂøð̪9Ò¦IÚ€|DP® OìÃèZM Bø€ßgîäúAp+ƒT ˜¼¸– ×Ö xôâÔK0ðNÇ&Àç.^~Q2Xî" i[ðÇùëµeQ¸ÒBNWáÉ@Òá]7á¡6ϱ¦m)vç°Þm´x²Z…JhW¸\*£¢“?F÷»,Õ„S*—¤U×%$°E䯡ÞB3ØÌÛk„©,(ôé4Ø84 [Ãrðá šp‘²ïòAm4˜¬zõßY[r&ñß|ÎWgQSꩽ€˜ï†l9 µSeÝ¢PÎv8,?™¬Äh2î¬ä[sOfͱs. ïìÆUÿ*÷¯óÛhü©¨'}"Gîa” &îØì󯓹˜ÕD3lGru¼Ácä\Œ†3æ×Ðh‹Tp‰ê¹©v;0D+_JÝSNZ½312·¸f0L×@Íp²ÀUîQéã«Ìò.­ ¨Ht3Ÿ›|JÎásMá0N ¾Ð5§_IÒ4œééî?Á}- 3ƒDÍz€þµ×\Š#­À޵¯[SÛbÁGNiKC±ý.À÷þY¦kF4Uez_Œd­9œa–A‹RÊÕ58éY*ÌÀÄôÁxñ.]±HÓŒþ?¾m¡á¨ÁgA‚‰’’ˆ¶nÚM|X[oa߇±gøI „¤Àt¸þ ?e6YaôRp¨™$}¯E8¶ëfí ‚XÒ¬?N|¿ f­:Ñi9%ݼ¹Æaríéõ©éW!âwþTå’Þw“)ÈØdº½•a"Ž{‰ÈD¡k° ¢WŒÎAæKéïž6^ Ô£û ?•JkÕv`xéù€n‹De¿ ªÕxÿ\aTÄÔDļùБ'™†-Ý‘Ã.‡å¬â °P±P7¢T9W>oL> þ¾ÿŸJ`˜î÷ ^{6ÂÜ$Ï @¸LOgê‚t’Ò7ñ­c·Ð œùk+€6]0.iÁ;kñP!zç*d“öï“JšÂi62¦»÷9œ¥;(*?·;î×¶Á"áÑ£<æÒ‚ÀÏ×`VÌE“D§A …WÕÓ&I˜š& R…ýô6W¬îep‡0¯è»c—ÐME óñ>}LfËóÁš8¬ª½aa­¶ ËüÚs®þ$3uÉ™nï+Ëʲè9Ó 1ÌyèëÌÖtémx©½Š–·x/ßdçüA^ÖK(ÀlPÃ’ˆ{Ê`ÙÆßŠR¯6F²±q¾äG¶rë¤侇 .^l¬A#!<f,UÀi¤1KÅ,U|I—º€™üæ> Yy®ú÷@‚ZÇö2:üÚ²Í-—6o°È15$ƒ“5bÌMõÃcúv€ßDG4”|ƒ” Ãòq¸Î¥žxE<Äoú8РʦÂoÌÐd‚oŒBËÀøµ5@bK¯zÁ„°dZÒã€+Zr}±ôE\ªw¶Ë†&´#}zìM’®ÊvÚ¨ÏízžÀV ¯¤I¸úw‹Ù6ɧbðm!t`ìaMïÒR«…OipÍAÀàh…n€«ƒÉÞ„9‰ÄZXZƒ—HÁ«ô¬`¹nA=8¶ciÅp>î7ÊÛ¨X1ŠQ—iÉhÃôú™[çô›qšk…)GÕp3EÙg4[塯lŸúÇûíõssÓÆ_A|[ñÆç›Þ´5&ÅXÓ7íÅ&3\[O#e°LüÊ¥9Ø1[ÍEòøNxE›yøË®ˆýr´nD6 Ö‡K¦»\´gHxP§N²‰cHV ‡.h©ÞxøÙÐéðÐN18ˆš"£ªËbvÖ†›/.ˆ—'>ÄOÎWS‡žãD’¦ƒËÚOç./üÇ9qèðx³Æêå`ÉI»CîЉoµVÃ'ÍLÉ+)4Î&CÊŒ´tÑt3ÙÔk¨oÍH™Ö–¡‹p§x™(ó:N@ ¿ªåI Ai }OÈ•‘v¨x_¾àq“{'ct2Nýâ.ãý}Ãr›y:æ.ˆ+SÓ’¦PÖÂ÷N5`y´ÞÏÖ~^é{±Àë#²eö#[zþ\ëÒÑã¹gJåན“³â{§^wÞ9›2`|¾ÓvH~Á¬Š5Bf.i0P:]d×ÌÄd#ŒiŠôd'ʆ’ s (†Zàa×åÈx¯alž¿­'k£§Db)¿‹ØÆ™Ù7ęӧíÒ¶¶,¾ l=aå“Ï7"/ßi‡Ç¸þŸFæ^±¼9>‘{9>99#Æ'Ê¢°i¶q5Á{º4ÃS œ&¾¦º{ljŸÞ]v•.;«E÷ŒÀÑSþ#àsäÔ&kë ð^£³âÕÓ¯ˆ ÔNç¸3þËÏ5þp}] º}$t߬“‡(Îç\(¾ÿG‹Ÿ ~+„g>¼[¿õl»¾ú,x¿,­"©7Òh$æ°äž®6 W¿n÷À5…•b%ž6 u¯_kg¦f#ê>ªþÚãùrÍ¡ë®3Ù" ¬­€Úz€«·ë/WV`kZì÷ç A;B5—'Û¿ûç›ß‚Sd.Žñéó^DZ:}u^G†cá/¹wI5Þ÷#ÅŸÛ €=˜·ñYÝ)ØX¾p!üýï>ßxF3KŸN@ p81rÝ6ùEaˆ^Õ¤À\œl¿\.‰gFrGà…×{2n€ØÕʪxüåçê¿uþlûL©÷€ §k‘É^sÇäI"mà;è¾ñ¿#lbWîîr1¸>ȇcP¬¦›£Þò½ýr…ír›ðšøØtŒ½Ó§Ï4þ£²,(C³m¿N}<×~×!B/àä Œc>÷0‘×Ðw’ñøöãÀ[§Æ{O& 1Ÿ@ß@`c÷½6%>‚éÈ츅á:1¾ÓD]ëóíW¨Ö¹&ðU~×—¹$‘Œ´ÿæsä-$ÐpïáÛìo#‰éü›wq èÙ‚´à@àŒ÷§ ¹ñ˜8<©gmppGRA@þõ8$¦ÔFÀ )çß<_@¯À“ÄþMˆ0á§Œù~/‰þ冯óÛ`tIEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/trashed-small.png000066400000000000000000000057441271170121200240640ustar00rootroot00000000000000‰PNG  IHDR00Wù‡sRGB®Îé žIDAThCíZ[¬Uþgfï™};·ž–^öô´R  i¡iZ¨Ò¢¡„x ‘„ Fx!|0Ñø¢Ï$5A¨ÔDƒ_ˆ±‚)”ÆØÒ­méÍÓsÎÞçœ}›ûøýkÍš=³/ôPŒ%‘Ù]™5k­ù¿ÿÿþˬ–è“ã ük@놿ëkÅ••Êðó~àoô]¯L:‘FZϸ…ª- }Ã`›¸Äß™ûôse¯xŒ²Ÿ/¬I®ëR…Ùub#w¯?1±’¶Ü¹ PýÀß^xozj ݰ¾µuóÃ0r´ÿoié5cd™y–N ?°IPéÖNï¸Þ9Ù1×_¿šNœ8N÷Üý]믯½ðøŽ¯ÓsÞKM"V¯Ô±¡ç¾³uë#ÖÛÇÑ\½Aa{\僡e®¦oجâ£i‘»Ìm/­Zy3´¿_Œa3_íƒÙP.•èÂ…÷éþûž*ç­âÓÏ>Ë¡EÉEÁyìsÛ¿-¼íСCÀÕ—Ÿ ]Š8?_£µk>KKÆW–1weìÜIVúܵåAýì™3T­ÎŠçáÇ¡V³A÷íþáp¹<üL€Q1wMLÜŒŽ.£ß¼ür‡5ÑÕ÷Ý` “—ÎÓí›î%ÃÈoØý@þ3 …*•±Ç¶oýÆ0wüýƒ €àcàÄÊ,ÔÈp‘NGDz¢P¬Œ~ûDpö·Þº#æ}˜Ä~˲¨êãÐ*ó }xÙ0š™/C0µ4È‘#ß÷Èó<‘;X»Õ Û6}Iÿý+?½'€lg•ŠÂôÒ‹/u(ô®¢>ôë×ǯXp¼f9(g4P´*gÞ:ü—5+–­§ ëã I_—Pÿµþ¹n£1ÍTr:òÛϽ¸ï™_ìøüÓåJyq÷¸r7°xF&ŒƒÊ%]»-¼€~~Ç?¾hš) »¤˜»ÿá¡Qøý0ð 9ÿ“©F®ô¶î¸_àÂ.S>ú„õåñÅ‹µmÛ‚_|ø~(‘àjˆ3—ªy^>¢¹ÎœÏ|’æzèã5B$F^C¬#®¹OÞó5§¯EŸ¼î˜I£|ñâ\Ö÷<ÿKçOJ¾L5ê…zÕqÄÜöĘ\NbÌç{? ” i@,Xúž…òÚWB³%(ÇÁµããÄs$@”ÏêÈçM²]ÏÂjÚ̺Ô\Û×5Í`Ý÷ci/E”–˜Ð¢ÅZ‹ïY †n ,·thÑ4¨Xd)+JÀ@8l- b°>À ž#¶šfÚ^¨é‘^À óµœëå8Yð¤AáM-Б&Wò㌲œ³%,iQÑò‡Imî¨Ø–Sy2, ™g`ùÄ"‚b!(ÒdÕÏ{‘ "I†¤ÑøsÍš›/˜:,tü»WëBÑ1ocaÔ½™«Ð¢Ñ ƒÍiª,§rñ!ðøØ tì½?br@üަ "oàZœÔòù”aR>gbZdݸ¢ž¡Pú{€öí#|*i!;,¿àòG–.Œ9õÇHÓ‡¾‚?õÆD”9ùÜ·Ú3©Mú÷Ô‘ÄŸr  45sRÐŒ#^.W„¦KÀB³õ:kò:܇ ¨^ïÀè@Ѭçxbá…‰¶1Ø÷vŒª³'ðèÓìü9h²BŸZz»‚fŰo°yЃù­4-l©œ[è^þ„¿ì•Q&õ81w€i5ñV×à—zÆG׋H£kˆ:87Z—èÒôQ„ÃMUßA¿Nåò j;³ˆáM1‡¹-´Ÿ•¸:É+ýVúÍt+µWÊ0šr€>x/ŠøMMlÑ,_²( Žšù!šžyל˜8ûF4_?‡Ä0™eÌëH‡`¾Ì¾qnÈ)”•'´FÙâq=rƒpÒ¶9óvJÿÃ@œgÞ:î«}ù^v`ÞÂ_TË]G0(øêÚ Î€à½ 4¯(Y|¡lùãdæÖñ±Â 8ãJ©ØjsóçéâäÛ´lÉ:zü¬Iñ\~¨¨°,Óï`ÆÓ¹§åñ=™}\o¶[x¿¤ñŠÀ‰]€À†gchüw–—À™—I\ö=íBµZÕ£¢ªo”IœW9"¡™âtÊjxæº-I«XvEÉ~ΛÁ¥G½>‹ïŒhýC,Ë*,‘ö¾æfV§¼ƒï¾{bÓÆ›n3‹Ö„0¡,>ÉnB‡JËÿå¾Nvk%CE£xi¦/ ÃtúÌ¿ÚÕï5– ÃËÉ‚4ÅŽào:û->÷È¥¯ŽÝ²ñfݲÆb)çcgÆ5@q¶–ÇN®|‚ŸÇ~"¶éã{EGµ±5[ºÕnÐo½N_šn>äc7{ ŠÁòœÙz`Dl"öƒòÚŒO¯ßhýH7ÂuØ»òèí&õ‡¸ÇUËõôÃǸ?9{*8…©ìÜêhàdß³O(Žñ©¬Ÿeë±>„8W4”w×XÓ|怂(ibÁAÿ…€…eOg|½°ä+’ñ')Êð™ô$²ÿcÄÂIoŸx”IEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/two_pages.png000066400000000000000000000002511271170121200233000ustar00rootroot00000000000000‰PNG  IHDR üí£sRGB®ÎécIDATHKí–K D3ºÿ•M²UŸÀMLëi´Çˆ3·ð!¢”^Œ{ØüQX^`hcòv»ÃƒŒ«7½² dÉÆõ@äf ˆ€èFƒæfˆ€hÀF KßõKŠÊå&Ñ!r+¡/«5IEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/two_pages_selected.png000066400000000000000000000003261271170121200251530ustar00rootroot00000000000000‰PNG  IHDR szzôsRGB®Îé pHYs  šœtIMEØ ;8¿5khIDATXGí“Á € ÁXÒö_ÁötÆŒ€&çcøÏ²7ÕŽ(‹Gª%ìEú¶Wôpz½ !ixwž}†?/jw^èp£Gà ó‹à ° ° ° ° ° ° }Ò ¹h¨ˆè‘IEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/undo-22.png000066400000000000000000000017631271170121200225070ustar00rootroot00000000000000‰PNG  IHDRÄ´l;bKGDÿÿÿ ½§“ pHYs × ×B(›xtIMEÖ 8Ÿ?T€IDAT8Ë­”_h[UÇ¿çþÉM{›´©Í]ìŸté\u>Ì—"[…Õ¢¢ÈŠú2‘1Ê„>íQñEA¦>ŠŠ>8(¡/¡¢ Ä-[ctæaÓÉݬڤ’uÉBþÞäž{Žž[.Y¡xàǽ÷ÜŸó=ßßï Ã¸tnìd2Ž] i§K CG4óÏ3Øåv‚H™øä³ÏàoCgÆ{m×`e'¨ÅG *Ý~E2Þ°pôðSƒ‘ó×7ÔÄé÷šÕN`¹”ÓUÀó8¡Ø3ª#<F0l„úür¥˜/í}äAûÅð„úC"ÉJÁG^žœG݃ä=àPɤ(Úè=lxtßêÝ?Àï=žP¯%’l³-øÒÙ½Çuú×W“3ã¨Y#ˆYÑ\)6¬fz™ôUpx¡ûýÄ{«i{ô€z%‘dV+˜À— CG6˜4 ÀˆµÊò[¿Ü_|èi\ÿþÇŸëåBê¹yÌ··zœ»ÓD©´ŽñhCÓ¬£Zɬ@O×Ö~¬\[ºùÔI<¶cWÌÎe./- M_oÆŸ˜6P5O}óÝvr‰÷ d óÞ¾u‚F z<ÁQ`«sÏÎe.h`:uñ6"‘Èöüá ‡O€ÊÞcÅüÝ­zuÛÔ‡‹É=4wÔ ¹£FÇ2;—¹²V7žO]¼ (cî<éÕSwÏg äµõõ"´®^N­‰-Û"š"¨øfŽÇx嘇tuqi*äÖ-Æl›pØ/OYz? ®¤­ôß+¸ññYÜPu̵€ãñ}ýGÚxMZîçÉ]ÁÜ…€Õ½u1×AE,IEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/untrash-big.png000066400000000000000000000367421271170121200235510ustar00rootroot00000000000000‰PNG  IHDR€ Ä’dýsRGB®Îé IDATx^í} ”]Guà}ë}Q·ÔRK­]²,Ë6ÞmI6Œ‘ ¶‰Á–3Œ†œ &’ “ä0Ë93“9™åœ0Ù&dâ$ö`c/€w [‹µµ¤Þ[½ÿýϽ·êVÕ{ÿýÿßï–tÜRWŸßÿ¿÷êUÕ»û½u«ÀRY‚À– °%,A` KX‚À– °%,A` KX‚À– °%,Aà|†@ü|~¸J϶óó©ËÖíöž\»ÛÛ~üÇ¥‡/DÈ3_ppíoÀª8x#6x×®Ý˺ø£ •.(¸ú«ÐœÌ$þɸ¤­idóiÂû;×íòºôÊ…H± 衽†©äŸ"ò¯_ÖÙ¿ó~ØuÍÍôø^ ¼oì~ yÃ ó¨ŒØõ…ÄïãSߟJ5ÀÞ[?íÍÝзbLeF`ht01xßõÉo÷?S»á‚»¿ÿ("õ+^̃÷¿ó£°¼½WãØƒ[®þ ¬í[P‚^¬ðÝ«¿œ÷àÆ_O^_ø6"5ñžÝ¿¯½ uÿ,<~è›óÛ–ø[„ÀMÝÝ+àÞw±}!‚ ùXè»X*ÀàÔaèíØËZ{ «k8ºÐ[¸¥o§·¯ÿéÒ¾H\¤•ÎKè¼-ñ‡ˆÁ77·ÀGoû´4¶3’_=õ„ùDD¹B†§C_çÅl ÆEè?u-ïŽõ»ãûqñÄ"ÅoÍaŸw°ëägù¿ÅáÃ{ε097 Ï÷ÿ€¹]8?øAÃp]ÂÕí[Ñ=܈îá &Kè8¬¹.õ`ÿ3ùÓ5¡¹+œW°ë×ïBü5~bäîm^s9¤s3ð³ãÿÄQ?%ú­ø7D€òž®Ò³…Y¶ 6¬ºNŽÉ©‰/V¼¥çíÅ¿>õd!Ž«ù¼!€Ý_l؆,þ>móMWß ×^| ‹yxþø`:3îý Ýꔽ=*Áøì¤MÐÕ¼ 6 gpäÔëä®$÷ð†¾âÿÙ·OSÐyB çÜô«°¢ƒG›«/ÙrÜvÝG𧯜| Fg”ú6ÜO/á‘þöƒ®78y½‚ÐÙÜÖl…ýG_e÷pªÓ[³‡ß;Opϱè `ÇW!•*$Ðg÷®îíY÷ÞòYHÄ“p`ø§pâôŠÃáæÛwŽ¡Ð§§&AoÛ&hkêB÷°öy C%š=œÃÙçÎ"Xô°õêøŸ vïnoé€O¼ïóМjE佉ð‹`âxG÷Ëï0îu@#ÁšÎ­Ð‰îagg§wèØë„÷w­Û齈"¤®Å_5ìüBâËèªýz*™‚ßñkÐÝÖ é!xùÄ™‹Ë9\p~ر5É=š> k;·ÃòŽ^ðÐ=<1p çO¼÷£{øèùà.ZØý@ü.ÔóŒ¢ÝûÐ{>V^sÙIxáÄ#è×g}Ö~û-ÚcÐçˆÄ(A:?ƒaãaŒlƒÕË7 A9#cÃI$“ÛVßT|ðÄS0¹˜åÀ¢$€]¿‘¼ñõ0"?µg÷àÊÍï€|1/"òçrS>£ÏXû®å¯5¿½&–@ˆ±ˆ÷ѼAÝÃUí›`ݪ-00v ¦¦'Ûc¥Ø­‹Ý=\tpý¡/VŒ=ŠÈï¾úÒÝðî«ö2j_9ù8ŠÿaŸKç7ðüÜîº~b ø¼jɱÆfO²{ØÝ²Ö£gpääΤWÆ‹±;ûŠ»XÝÃEE”Ò•H'¾G)]ú¶À‡ÞñˆaÄ,þAœà©fÔU"W°èpÝCCŠ\褙Ãe#X½ÜÃm‹Ù=\Làm¼&ñMÄÏ­]ÝðËïý4$áäÄg‚§6—vlŒÇPÝØMu`FÑ*tÆÈP©tíºÝñÑã?.>·ØìEC»¿ø÷Üûšà_Ýõehoé‚ñ¹AøùÀmTϧç]!ïZýö·ý7Qâ!@îẇkÑ(\Öº’ÜC8t=ÂìYŒîᢠJéÂÄÍÿŒ)]Þ/¡»·¦{3Ìf'àUŒôJùsJ„ir§Àúe—²{K d@÷°äyw­½!ñÌ+Z,’à-O;HìBwSºî~×/ÁŽu7 Òðò©Çp‚g.Äè3¡œ€M¦0V c>W±ŠÉ$ž=LÀ:Œ(÷pFdžRžWºs1¹‡oiØõ › 4ÁÓ~ÓÕ{à—½ç{ŠðÚ©'a:;pæüb>èëëôƒ."q-µ/añ(ÌUÂÝ4{˜A"ìíØ ëÑ=;“Óíè¥Üܳ§øWH£¹·º$x˧t¼ 7nÇ ž»wŠSºÈâW<ä¦YÄ02Ç•îŸÿsm'à¸6¢h§“±ã³§ ÜÃÖ5èlÃÊ=ìMdc—áìáƒou÷ð-I:¥‹Äþ®•+zá“·}'x 'wh‚ÇE6OÝ0âõ¤9v$-ýТ^ˆB¸Ü=öM ¹Äd&Ê €ú¥ÙÃÎæ•œaÌîá1åNvÄÚ0¯ˆø-[Þ’Ðy{ü¿£Cþ‘––6øôûÿ-¦tuÀrÚ៕ëuD”"AŽþF¤Ó|¯ûqÌDòóùô×üߺ>ß«’F]Ñ/EaØ<9qzÛ7CGËrX…D{໇;×îŒáìáOߪÀàXHAnMÍû*µá% mˆ£DÔ>&{àžBcéˉx>ýÁß¾î-l`½|ò‡î%$¨'®7¿yâ‡Ïa:¿ÒÝîõò{¨~Û’6¤~ï-mýþ.–r/äÕ# M0­…”æT;¼këÇ  ¿xþùɇШ€bÃŒ÷•¶Qo?Þ²P†ÃÀ£7+‡Vuç^úã?^¸1/¸ë#-+K…ìW0«‚R°¶á'2‚«B®ãî+º8ßö±Û?—®ß…FÖ,†yÈ“2„ 5˧‘K¿Ñú!_Ï2b…´Z(ésr?Õ1÷êßDBBDp4±Ä¶…A:ß6ë¦È÷Ý“¸É+q±#¿?gdyYIhªG!Ïî ߆ÈÉÃëƒ?†ôùENb4‰v Û:¢žD6#+¦Äw‰ oD¾ïÆ0tļª§ïÑ’$WÌ Áá¬"ÞïQ~0« õ›ø[Òÿ ”{8ªÝÃõ,½FƇ ×T‚†êf^<Çm7¤Z P0ŽE ;¿"+ÝwÑŽøþýû óJ_¯k4wîmÄ5õÅWq,Í4 ŽÎ•°uÓµ°|ùZ¤øT]DO$¡¡±…ï™ËÎÀ·ö}³rÇáêKn„½ï¸ŸÏï~F0]Ûˆ{Íñ,à9ÃWs?s>ñ»ÝêøÛ<³B"ö·þü.Fþ†5[àî›þ5s×±ñ×ùýƸcnŒà7®óDÄ232àÉÐS܈¿GÄ¥tV‹oºFˆŽU íàwëÐ42˜¸ –iÜ¡¦…xÕ—>O‚€l & =‡(<Ëκ`ËŠk`Ï®À·ýs˜8 Ï=¿}ï_ 2_ØíýÀWà_ù:üÕ7“šÀXdékø]7DÆ{yˆÿ)u¶víøÕûÿ÷‚‘O#ÿ¯ÿp?üdÿ ³­ î{ÿï@c²…iÁ<ÊZ—‰uÛèXļæJV :¼ƒb]KiKu¬ÎYUÎO#ò'ˆÿhW"Ÿ*k'Å¿"(¥ÚøX¥µ€G®{¸ÝÃG_ƒ#ƒû8”|Ŧ›ç‹{Õ7ö·yó5pâäëøáì´Þ‹.I>¾_þH= G–s^òCÛFjüæ›>ÁA)Å".¸|î9XÝ»ÊAC2é4ŠAå§Ë#?ÿ øáËÿRÉøÔ]¿ - ¨3G¸Ç¸u¬·©hË‹q 9Û°¨ºÎ\®G@+Uö/ž'Ng‰¡‰ ¿æ²S0—ŸÒ6ƒF´î†{d颥 ‹JÐ@ŽE"Ð1Ä•(¢ž9ò…-+á¶·ß =ú7ðà“Ì#ºvÃûøÉ2Zn Ïé×ʉ:F°lY7lÙr±åí{î‡ç~¢Ö±z^ñßà×c¡À®p2² p×Þ†!8oŠcÆíû/¯A{ÛrÓäç¿ð¼øâ˰}ëzŸh3†’È]$¦Sý0؆ɵ´Oý—à¢Þ+Ù€"w/‹¢˜}x­·Ù²×Zœ¿Éh#ÝÎç¬g@çÙ—óÚ´îa½‰i&2šD’" :àþ5Ýpõ=Óþéè1œ®ÏÕôqsÒqû_C÷ð1Ø» :½µ #­ªü¾îºk ge45µÃæ-ä|©òïþ`zó'ô³ˆ-mC[à`T"ˆ$ÈøC ÞH^~Ù»}ÈŸ˜˜€—^z¹"—‡ $ƒ¡Ög˜íî¾ùŒ|‚eÜ\Ñw‹ÕõŒ@ŵò§AÎÄ‘GKýÙ~¦b†mqï»w~È×ì·¾õ-tMÜPlõ^s±Y8Õþ$”bØ}å»a×ö÷ix©h^<–䕼ôG™GG ù°5mY”v!Íäi7P¤M¶8 £sÇ SœÑו¡ü|¹ Ò÷üíÔÑvß#ê^ÛʶÐú?pÍÏÝ 6Wm»¶m¾eW‰à»+ÍF@_èPijn‚ýGÌ=×\õ>XÙ³Q—>yçG "EE à ^>A­77wÀ•WÜæìžxÂ+½]½ ´?…øWzåçaßÏ1âsù¬,émU\3 ÈdÅk­Ëà³wü¥§!Jï+¡2ÖHôOe‡a:7¢Œ8+gÃIí‰Øç®·+ë_³:µ-D€§¾þ¯Az²ü¹Õ}>,ª4&ï/~%ÁjÎ7þááXÓ·AA ðžwþò¯¿D‡M¥\êÓYòØj–š*àŽ½‰ØÊjéúkï†$lRÒé98zü¸9ŽB Üž-1Á÷LÎÎo?¦ééi¾?Ãás¬_)s2ÆÈð+ÂXúzÄÍR*Ñ#DlA x" ­×Å)#sv<ÜÀ­ y§I‚(%á¸Ümm-0334_Båí7ý"üý?|ã8´ûÑkûO>X»ášàAüãBÉ7î¾×7Îï|ç»PÈÛ­tŠä˜×(=S×AÏÔõ\KD´ü.zdÐÊÎkÌ žÏáö@£K‰}…Ve#"`n-ÀFG)•;FJNÄ3w>@F®¾dü{1.„Ô¨Ußv®,þr$C.ÌØ‹+çªf‰Å­Æ&ivôÈA¸dÇ•|_CC ÜüöÃÃßÇy4t çJÉäþ¢V£UmšèÁ‡e¬÷¬Ø[6_çkïÑþ?ß1»Y (±êüRCÅO²¤"‡ª°¼V®—žÕ#à’??4÷&ÞW6ÒåD~¯¯9繎Ør¯¹.ö…²¸oMñR 'Cçýñ+ºÊŒ15Û’LÆ|Æ÷­·ÜÇv$r k–ª)¥îÀº¨•Ý»îõé |¹ñà›¾ˆÿšƒu+ˆ æùAÎFäçKF’Eº Û~Ó5u½üŠ!¶4aˆ!¨ñ_&êz‚ú+Çãþ¸]cc#?fqÐÝÝ×\E(ãrå÷4Þ\«—ª€öŠeá¸å_þùÍù3ž$1£V§gîºHD~¬Èb?WJ;œ® ?—ûcL.!ȱ’ ñV:¸DÂÆŸ¾îFÏÜóTo)̈M§•=$å¶=Ÿ1¿u`¨j£ àï|/ݽyóµ¬Üòƒ¦ ¸ea N8jäk_ÿtú¤ “š[µ+gÜ8!„€kgJˆw%AÇkWϸ†•Œ‰:Ÿ"juqƒõÛÛÛàôéQsz3NÎm»ˆìv.w¾ïÞÔöj}T$€\²²n p?Y¿¯¿af¢LûŹÛ[Q"y1gÈò“0•Öœï×Õå6€BtP0÷»£c Šëu›¬÷uÔα¢"q!õ*¹°t~hп‡Õí{>+]Åâïóó"´„)øÃnßõ×Ýíkã‰'ŸÄmSÔÌ™[îE‘„Š‹˜3š>fD³__ RýÜíW bŠÁˆÄáÓùŽ40A  ®Dôá×]3îxÁ›[ÛZѳ¡mŠÕH`m¥U …J€÷ÞÛpv»g_qù{ ¥¹Ó×ç÷¾÷ÝÐ(U˜ô©ûi#Ý ô !ƒÂJok®´A‡{5'³¨7:ÞJƒò{D’(‹O¢}–À*z”‘F_o¥˜3ù¼7•JÂñãÖ”À®§Cá=†@¼XâÈ•w“&ð—×^ O>‰¨÷¹+Ö'›?[œƒ¹AŒ¬êêi×b¤︄rÎuÿÊî¸}Òì 7Ì ò¦ÀEny˜*0š±FôX´»6´µvÃe—Þâkø…矇©œP )*çÜ"€92úÔêo×eSçIž‹áný;R@#žï‘˜€K Ò—Ñÿ玂.`Êí022hNK`HŸÐ¡rÜ”À{oÇj¨êõ×ß~ÂùÇï|§2WžC@Óºœæp¿ ÎÈ9A¾HT×µóˆŸß¡*£ZlðçÜ!^€]s+NOùCëQCåÀ‹³ñG%èûÓ¹_x±2œÃHÐlnœñ‘ÂA%T<ÍJ‘ â×ëkNtÐDýñ0\¯¥ˆi;ÐǹvÜ åÖ*]Ë—ar‰5Σ†|pûíЀôRG½½[aóÆ«}}ÄÌçž´¾È€RÚ0&04tÒWõÖwÿŠ9ŽÅKlàKq €È~]Ø´ñª²Ðo.›…ÑQZ’]¹Ø´í(C=uÂBÏÎ95i¬R²Ü©}•¯ò¡ú\W=‰iªF?Ì]gÙþ©'ý¾ ^‡g ÅrZ[–ñ1ó®P¸coÓj¼ÀŽã¶m&–lêŽ×Ìû;€KYÅò12HAΰ‚öÙ!‚IM¼L½+}è·JêtL{jb–.±€að<æFзŒõLEp*¸û46ùó È“»tÇÍrË¥÷ÝI90¦e©”Û$©Þ=¸ãE°ô¬\ ¿øáÁc=shiæså ÝÚÛÚp•Ð s«où”¶¬”-£Å™óÛ=ï³½uV€2j^úKÂfÄÀ‡ñ£Ú´÷iC0ä|µ¶ÕÀ•þ7¿Í¿Í—ž¦ç÷àÖö¯˜~Ýþ+þŽÐvؽ ˜bËÕ0ÿ’-Ÿc]8#›J¥ “Asæ²Ù 4bÖp°¬[w<󭸇äàx óʳü¢LCxs»ˆp‰ ù•_ù4Ðg©,>¸8Åi™¤QCÅ’7C«j¨Ì¥ÏÝî§õˆÉzꆡèœÞ_Á&¨gÆüLŽ7‘pÔB©d#ÚèB¼€J^É»ñÓØ\¨R•#â <°ZUá/õ,ì~~†ˆ ùY`B[Ú—îÞJýOOÛø ªfóæCMÅÜ«iHÑ{q:÷ïnÚõËLD´Š5ë°”CÛX(Ð빡c­B$¡Ò)\dE'ã¶9y¯8 ÀâÂ{’üïÿû¹3ŸÏÀc?úxçÛMfI}ê Àê±a„WÚ—bàèR/œë¸ê¯ÚXéÝI‡?«‡è=ƒóyf)’&‡²Ó¶"õ{XsãOŸÿ;èé¾C‰*‡?J© ` |à·‚^,Ï»ïõ‚Ù9õÖ;ÌôzØÅeY|ñ޽©{ð$½uÓKàî—^ò °iè–£×[>yx®õ}du X8ºú gáè÷×!-Εú£2§_ƒ§ò?!‡;Ÿ`™Lä³›¾ým0Y¤¡æ»îI}SÂÿc®_ª³x €^þo=ôwYÚtÛ”ÐBÞØWx 7zcbïšîrœÅó´K#%!NáJÛ´ï¾úЃYÚFÆWªN1aYk’wÇÞøáéÁNL=jm­¼©‘Z·§öïႢфÙé7Z`æ˜jÃ6Â]…ÆèåYÕÅ2)í†ÿñ·šËP!_š|“8{Ÿ¿_{N[¦WS]÷¯Ç)¶ú“د~Àm‘ãEÜÜ*«D=ä²ðægŒàžo¼Òƒ?˜ Íä‰4ÇøÉÏ¥.FýÿsjxÇŽ­°};/^pÉçiG$ÏM¦à„ã$ }h‚%¯Ïc|Ž&Z0ÿÏe2˜™¢‡Ås¸8)G±q¼fwêPíZ"´È—];,æzå¹A“êC•_H„ˆÛÜCµ•¾CogW*Ë,¤,[WgÂÁoA£g+e<>‡ …È¢˜mS¸Ë™Ê Äu¶›¿ñ¿Òþµ{!«g„7á‹Çi- •L6ç›.©ÃÑ)ë>‘P(iæ¦*Ó”šÄ}rDˆx$"B€ÌBær¸QU?y¬GÇ\W~ÓD ž£ëE¼Ÿ¿ñK(yA†“! 1@Íõãö¼b.ˆ+ê •ÄF­¢´¢ÑÓ4ǯ¤~Hõ`ÛÙŒ&<Îf J¤•âr³#9\ñ¯Wq¯H$ÞéÄžs2©PÈ9¼é%VÏÜ©—ô ¨Ñæ,ª*î¬ùÙ$·m’¬øUCÅa0°ér?Dí9$’@’> pq+Ò@§ÚGtœÀ5ˆø‚R§….cS ’ô »$G%KRMh£(\*ñ ãµxW}Zo‰¡|  "^z^¤¿œeÆ3OØ…’Öd1DéA Y“élBY´MÁÅ_‚çˆkµrä=„à¸rÆÝõˈYƒ …PÙÌg0¢ÕGŠÃ°ê‚!‘À¤ TM¸l_zÖbÉAj…ƒmü¦ýµT[Zr˜~´Çö¼z$…ZdLÏ"sªÆ"ï½I(Ãè>d6­ö³ pvѯž4x>Ö0ðÑ?ì±pœC+ ƺQiƒ{°çœê¡?Á‰@Ä¿HtóqôbqH5 „*%YUPSdÌ*ûC…–®SiH¤“HtŒžb =!Úb‡ãy@éÄÁdòhÉ,vvíK–\à+z°Ø­HÆMÓÄå2™Klª_? ú‘î¸l¾FBPVÖ°ªÃ„:,•ŠB'õEêDy5ê7¹ÉÁ&(QTö à ±qÒ‡K©É ªÑ%”P¯xLedYÇÏÌ.ñ•ˆ[?G@þ…‰Ãà¹2I aèÉw›+4gú¤NØ0œsAÜ+"QVÖ]ßF¼ÅêÄYÎÆ-%²R¬„ >¶)¸Í.I”Ìh ÚÆÎ‚ðŠÞ¸ØEÙL;V€ôÐÙô*ë]‡Ë®Nði£É¦†n>ÎâÖôÙÜtum•Ý—Ñhb#çðYÔ #¨M œžì‡Ññ7N:òK¿rû6†¥s£+ʉ@©†`!¢ iAŸ¤f>ÊöM%›¸êÔÔŒy^láÌ(€XPÉ`œ±Uÿ>Þ€ÏEÒ\RImÅkîn·IDAT¾Á£³ ÷0F™%ª‚ˆ`tü Ìá醾› ÄP•äU$pÛÚRã2Üw ½‡.f‡Æ?bg*¥ÊEHUÉ ÏXN¾3Ä|bìÒ»&x6² Pq‹˜²¾KÞi Aä"ÚÕ“:×›ìÔè¹\Eüý­Í½°¬cŽÓ?ç•Â-é{º/A…“ƒ/8b8œûV¯|4!1ΊÿHn Gº½9ŒóÃ`GpOb’'½¤CðPÈ»vy‘%@tˆ¡ Ðb5Çá`$úTÙ¹b!ˆÿ:\äÖÿÝÐѶ.DɪI-twmEI°¦§OÕâ5X·†öHÔêÂP‚ºà²rÅv|¥K«ðˆ:jø©ÇO×á(ƒ£}“éí"´öŸÞ¸"€¤¾¦ÎôÒ÷ÆV"€Rª¤q‚ n“ˆÜD´jÅ¿k5…‚'âMÌù®:ñïr#I‚ö¶µø:z?Š2hý77uA÷²MþÑa£IÔÁm~ª™Õ8Iåß²MõåÓø†`LCA"Q·˜Â¾>&æÒ“ø¶tÞ*F‚¼°•à¯@(À+Åμð ±1é€$€ŠØð¼°õ¦0‘k$§‚ìY†ïï‹B˜Ƕ–^´ªs¸ìýp¨ñåqåŠ:þ¡ÚÞ¾.Ùú^ho]Cc‡TU½Š:Þ'‚ºCz#c¹‰«9'ãT¿AÄ 6@A\@l¢àϼ à• FdxƒH< ò[g¢¢;šïÍÀÖ0w~·6¯B1©sX*‰Ò®`14ò^-“>ùñëQæÅaíêk`ëÆw0wRíøiÚ¬J—0¼2Vÿ$q”_OÜÞŒ:¾¡â+y8ˆ„jc,%i—0 ¥R*²ˆ(Åhª€O6cçΞ à§]P8UèRß3ÐÞÚgõ°¾§"7âõ¹ô8·hÏ£{Ë».*#@w$½+/Ã7ƒnÇp¯Ý9m¹_TªÿÔóˆÇ]Ïc´ó9M&‰–ï =É"9®ض:°WLÖ‹L‰ìì8nÉM) b8ü*Í/ì’ ùÚ‡/'¿øok]«ãåý(­NNÑŽÊøÙ]å^Ó‘¾¡± =¹Yêœû øƒt}‚[œÇH×6Ù ®Z«:z2d%"k÷ ôÆ¿úU~ßN¤Ù‚û³?ƒ4ŽoŽÆ˜æ)aQ‘›ˆ4 …„^gæQëµñµÄ?^ŸÀÝñŒ‚?#cÌ8Cݲ@»SÓø¶p|ù”ÜUAü{\ ñžJ¢xÇO ƒ8vJ]„ºú®X˜!Ô‡çÿ±M¶ðw_m«T@ô‰ ê§>ì•pž;ÉfpBˆðÏF ¿Æ#r£V ¾œómKzú,¾ lpô˜š¦2”tð3p<1y¼Lä¢ £0J¡¶‡GÑø "`C©cÈáä¯Ó?É[ˆ‘1'V¼ž®Ö!ÆrÑï Û¯ÓÔÈ”¨>äÁ˜wCô@ý '„²(x«VqÏB,ÀuÿX› ¡3o„`Œ“Oà{G_…,½ Jê:¥¶fçF0%Œ^]ã÷½É  ¤Ò9ìír:ÕCã ±½Á$®ßšJ õŽÈéýû9KE¬´2ŠÑôª“Zª -%ØW$ (eO«ÌÈ».Noa¡›Èd¤È™š(ÿ†½ìWíË8¶Šö¡‘WqAK/ºzkü+lãô”ƒe^ ¥YOÏXÑM† ¥˜0¡LÏb W 4ƒhUÍʆõI€ºÇÅî…ò”ÂÔÏj šØtž-ôg5ñoŽºÕr£úU.îK¬H×/kß„î!½_§3³#˜¡^\%¥­eô­¾u´r!eêÕ‡ŸVK=º~zâ8‹yúm¶È ªÝeJ»¡„®ëUµd°šùÌ<¾~×L¯c÷z}àa4§ikz•»è~âœ3™Pø¯Hš" yõÃcûpÓëUùëínOiÄ•ðEÎлêmH µ&öŒF(¸SÂhmV6䙕/´Ä|7DN }û˜‡z`Œ‚#ô›Þ+¨¾cxŽß1ˆßæí8ò=NŽÄp(Ž $NYRyx4‡Gñ=»”K‡3|¤Âº»¶q;3hPqœ`¡Ù?á®pŒ»”k;Á•BVc±.(—HµRéµô=¹‘ZçqvÖ‘‘ƒ@u@ÉÃAýXô¾[kœ™h ÿ¹1¤Ûq1t¶Ó¾†Ž¡&€e`jÑ,«6ðLWÇÖ•\5âF­›é‡¼Mœî&B!ßßèeªÇÖ¹²ˆbš øe–HL Hp§Ocü@5l9<(þ9ßO=T5ȵ µ” ñ jp§‚³øÎEóÞY´8ã’ Æ/<Ö¬3UDü“D}õ÷0¬Z~ Š;½z„‚OüòFšŠ¶ñq†/þ£@"F¶Ëƒû$Ž‹Dý Ycß³á‰h”…-8÷#ÕrË{uh¨Ê…Œ&Iø3ä‡^;€Öˆ˜Ïe,þëH¥^ë‹8SÂéÌœæˆÖüKµÌß©™~8zâ ØL)±bÞÓ!P_àMݘ¾m׳úDxX|1ά¦‡àŒ€Jy0j[å¾5` Ã¥dØi!Eyz$ ‚ÀôK…¼œ·«Œ@˜n<0º骖FcÐ03ôdÛ¢ˆßê•—@oÏvœ”êq AM:؆—H±š»…ËØJzS*@èí­rk¬!YÔźÅXaœlO*&ÝH®YÑ -DÑd\™Ü=¡4-béaW,[¹i­XSè‚¿S‰hiZÓèï˳Sx>ÀQÆöМîr¾éBŸ`‹¤ ØÁ²Îµ˜xÚ¡9—Ñw¬†‰)ÜŒ‹‰Í)®€1 U†^% !6²<¤;ÉOÌœ=/ •HŽô±”Ìë®È([  ™1Š‘«ôfwÕ­ºªtb’GC·@5F–ºÀ£dGëz˜ž6PV™=©®Û `y5¥Ç-²’¨ž:;ñMf#fs9Ž“=´­ÎÔ4ŽÁ}.ô @±#©ôD~*¢$Ͱ0Q@˜ŽòÂh·¥ºTQ—mC¢ᔺE¾¾ _ Ë}dЬWÝ;ÜÞÖNß±‹úÝÞº† ÓâX±o`áÓñ~€sfX˜|UÓŠ®M°º÷rhÖÈІREí¸ÿ¶‘dåĪ„›ÿ¯š,ƒ€î#w”˜¯„ª1ÇÞ _­KüS_u€¦.N|# À•5äG| {FŒóÑ?Û‘›ÙÎÐ è«¥ñ70üô<@j‡ škokAÈŽà¹ø¸Ú Ü ¶è0c‰¶«s#¬é}FÕv»–|49 œSºiv]ËŒß"=á8ä-ƒ€l#Þ»AÝR—@}×eè'&*k¥5‚D}BCÄm…*KÄñdÕ»1y×°Ñ^VÀÐÙ¶9 Gý\›ÍMÂø$NË"’h~¾{Ù6èZvtt¬Ã…GŒ’µÌ¦Ó‹]®]ôq'nD(ÇdUg í}­ÍÝf‹6~Ÿê‘{ƒ § pb@ÛbL¸MІV]úŸº©Kh´ŒQgslÚÓa…;ó*¼Š"Ëù30vž_ïaŸ@o ©'h‚x”Û†pûÞ €\Àpïkpèè#,¡È…†ŸÊˆ º7àÛñØÞøTˆaxx骥íÍù¶ Ù.—‡c\‹ýH-÷ݰ2o ­o€Ú«[àFƒh`ð!C€Øw ANCï JΠp[˜$ „ú%ƒ,¼05{ãøC WÚâ¦ú”r¬ÿÇ\µ#ˆ£ß! E@"-¬ÃfìN¼4ƒ3‹-³§ÑðS¿).’ód¬Q‚g6+™J•¹½|>$œ0\Xç1 ,sáÞ} €89ŽÐá`•ïF1s2îBŒpíÔ/‚(Íä¥xj—Ë`)!!÷ »«ê’ÑGa\Õ©ðm¸ø·†€”Í]hÈQ’©FšŒáDzÙ¨TÚö (ID¨¿n%G»=ë¾>†¢€V–D_,­Õ­pÃ9öòX7)³uŒxW¸’® zÚRV. aA£¶‰Ë5:ÅFH=½_eýø¸Oµa-M >nÕý¸b806ÊßÛ¸n7çññ@„>ðg¶Aß®&âÀRˆ¨v©F¤¢²{I-DWº.ûÑõl%‹qý* nðpBH:Ìà|€¡$2LñUnÈ" Ü-cf5X£`Ó4gïø1D D‡¼ñ“_ž›#&,”Ž è éÖ5’)ÔµaínL Á•·–ùePÜÏøD?ê^ä>nÛA•SŸPNK¹CW Õý ¤p#¯YçE‘¥:„Ì[ pŒ«1;g6æ¹øŽPqU@2Ìäϼ)*%“ðeïÒùáq«·@’àï'oƒpdr]T¿Þvâú^ºÞ×{5q´Ø1*†¬A8YßÃãGÌÕhõ‡&FÆ›ŒÝ(8";Ì.p=)’²õÛuœ$=gg¡üóyòä †ÕË =÷2ýžž9ùê‡Ó³Cx| –woGDkó jœ`N4ÊØÄQ˜™p¬€€ï wy×f\b×rÖ_°èæ)µlfÓ–"Uiµ.!rZ¨ü´!`‰jø¹ƒpm€ —º]$÷ºAu"ßXš”@†q?GóôäP¥û'Ë)îƒqÔÕá1ÆèôäÌICØúÝ„þu2ÑjêÓž{†°¾ÉÁ£&1ÍÛ}­­ tÅ=êÞÖ–°fõßxN=—ƞƠÁ#"’;;úø~òÇs87ŸÍÎr".â.“ŠÑ_­W™‰b¢Bq7¨Tˆ¾&PpT7 s7NócT2ì ðue¸´)0ð”-M8lÀÀ×òË÷¸g'„Í¥){ÅiŸÖ»{¸¤foé!gø-ã4ö¦F7åÞWñu;.Z‘K³|¦MÚ­”’0éêwJÆÄ43]RÉVhFõѵl-ôŸ¤õ†Âù~—³œI*#\ê áòŠ ê Τƒãq<?û‘À¢‡‹DõË¥hÃ(Žò>d"ßÕX#`î@ÀŠ{±ÝGZÇMaorºŸÝÂF¹”ª¥¬}]|„¤ÏÊ—³r¶ ðX‡Ôצu7r°Jm.iK¦ˆÓ‡Í"|·iù­@ž#›ƒW^ÿ¾3I†ü莙’A¨øƒ@xbÅN¥n PÌÇǼ˜–˜^,)CLM¼ØÍTkÃÚ  x.+"0×ð`jê8«ÚüÁ-Ai­Ê`Öm K‚)ÁzLol Åž~‚òùÿÁª€?2÷zçJ(•Ì¥ÎpÀ÷aFB ÅPa`ŒKã\?×½oêøª›Ó6)„T€æ®(KÄüáO#ýC(ci…)ú?—Áµ|è÷'q±j±´ÃÕPÆdÝÞMkþ;ÚU‘5"4u8g]ãÄŽðàͣϢJyzÜU:ï@^mD€Rô5Mפǫ›ú¾¯íº9MBÎ|@hr¨á8ˆ>ŸX‹ûDzߑ Œ}Œ_9ZÊ­3~ æG¢mXOZè onÇ]=z{.µˆw°dðŠ[‰.nüXÿ 02zاB˜xE’1ñÙOu*®tU-cÂFî—y•²ÙGM£4zøØSe×ÕMn«ŽÓfgÇàƒsÈý|îÍav`T„%YnŽÒË;D¨"\ç%êv™ÚF$ܽi‚ia®{æsÕ|ܦË¢~…s£1 íÍ:’JøÁ¸?ÍÜñúA +ƒT% HQ¶‡t¤¹g0y´à%œ¸ÂgÐ}ý'_dó.¢X·€¿ÉÏŸÍLÂæˆò©ŠÌ'ÜJÑô”N >Ã<[;„»mÏ‹ˆÚdñd:ƒÐ¡p^*"Êçþ]$û}oÃxaFd•\BB[„7®(éM!X“­)²[õéŠÛà.aûx¨·†5@Å´HN&\˜€ì;†‚Dm$÷EüW––•Às¡ÕYn$õº£€ÔǼlžÐqnÖR!ÏV(Ÿ¯„HG¨–ŒdÐ û„¹n¹ïÛ£\ü«6ýtîȱg­:ÑÄ¢²žô{þb„ ›oS ßwƒš¯"´*_psx.F?õçÌ›0|Z:NÓ"Qí£±Ø Sxîó€h—Íøëñ/$éµD¡@ªGÛ¨kë[¨&8 w5šÖ•-S˜ùÐÑ'yß•Aú"Ôœ"i‚ë"}ÜâÆZr’f†uòYâ3j¯¹:J½€k _·¦¶ÅÂOiK›R±ƒ* ýS:ÞKÅQôOC5˜ `žElŸ›eÛq›5F¤18µÔ12Ñc±Ò`£x'NÙ ¤eÆà?AÛB“#?Œ}æ:0±ªd{Ц݂‡™Ùé>Ê=£OdBˆJTÖѧAS™¢’jfEÞk6 UÝDËmMˆ0IÂ`˜¨±ö¢å²rkßå_!>G¬:|3~hçOÌ0æÖ*Ä`€š¶Ôj¬óˆx\MŒ@bºœ³AôÔi²sùŒ#ý]ÓÆ‹BÄõ~¢ÁÜ\aÚª4¼ô|@µE¢¬—ÍKþ4ÿ8¸ë_Úu]@ºÐÎJær·¸Æ—ºÓåH¿5¢D9ªØŒ‡>ƒñwÿ?UÁpzP/„Rd¬ÖQQlÑ2= œã&•oÁ[EµP‹\ä"˜™BjÓÓ‘¾³–ŠJ QM 8ôêèæ ðƒkÚ‚Å·tG±šËmÑ$•F2Ó¢D£ ¶ ˜øygõÄ\t˜Ûª9(ÎýµL :pZ•“T±PüBÞæJÃ=‰á‡Wò]Q%T„|º.ÓØä˜7Ôøf1-¬P´©aqôÁó%{ìÄp²ƒ@éöº²¬‚LV~ìȆdpú¼ckú«e©âoOð囿zèmjœ‚<[D@VŒ@Ú£“A¸ÿ½%”¸^mŒds゜_¶•[% ÀûÀBnÓqaÆÄ†#í&E^ÈRq Ïú°«€/ÐwÔC§³\Ѥ”Šd-£Ó¯-âüF)%NÔØÌ+›–‚”XF­Žë§›Ñ;‚¤äQBñÜp,è–wáy—ëW‚Cú–o •@…ÑÔMôM šFè1hNŒŸ™AJ,èU/T—L3<làç@ð4ËKÈDéqûˆB‰ñ€Û_扪¡(Âñ{!)ã#ÕŸ% át+|Ãåg^~kÞLo ‘B¹‡½KK&SzR€+±]"p À'ªIWœ1˜†ðM˜GéyÇ'fð%RøÆ*=+ØÂ° vŽ­/­no&+b…(Q<¤³41H¯ Ÿ,·RÅÕ'F˜ÛýTå"ÝG– uø‘î 5ñYw…–&»Eýèè ‰W`@ÞV!¸ â-x\“dkLɱ–o:o›™‚g¨9\&~êÄ(æ"qz§ ¾¢M/´W’ ¸ìÊ"ÆŠG;x5:œ‘î—öˆo—€…J ;‡"™‡`ãPVp§êÖº¥nL‡ÜÐÖºZZHÒÓ;&adĦ›Ÿ‡—\Ü~Û­NUÈÇô5ýD=iD†æ<^|²³+þžð^øÙ!X¿¡߬E¹z1Ü@²3i—ñôVkå>9Æ{š‰´%†”¨A¼L7‹!ªD}[{½ †pŸnŸ$‹tž€ò Ôx¤Mñ*”¤Ð×4™vk–T]~ÁãߦJÙÉ4õK»Œ75¶ñ6óRFÇéA~^ìküçûr¸I“™Ùqœ±<¯¿ñ*?®v:§ñ_ùiî÷gg‚@”øPíCD"×Í(+EˆÂ@aÁ…ä®w&?ÑÑéý* ˆ6ÎØ°¡¶]Ò‡Û®/C…ï·Ã¥UÂõFlŽÃ9,ÞÓÕÖqůHŽHâßÇýZªè×Ú™©Ù2q_.þØs$ŽOr豫ÄLg‰&¬ Í̃ÑõÖó¸èC½s¸þrjz·¦%½?Š9jö‡‘;y´ø•—ŸÏ?…‡„\òñå›~»:O¾º|›aU":O'ô/Ýè»;ß‘ühç2$܃y>¼tO}@{wòøñÒï¾ö|îYTB¬|*‚‡/G®Ú&¿DøeÆ_`¨Ì0ýG‹¯4¤à¹æöØ&|áõÊúg©vT °ÓsÓðÐ+?Í~ùÈÁâþ \$B¸œ+K¬5w,‘$‘Á@ƒ\7q‚m—Å/ê^»¾!é­óâ¥NìVë›£Ž ó½n—›Ç×ÄãF Ãhì½ñÆþÜÏæ&A0È·¦èuÑñ®4ª†2Õ"ºA"aÈw#L5 úb<žïø:›Ï'¹÷b¢ò…仨ÜýÀ(£ÝBõ$ì ãyA|¥‰96t¾·$—H¸’ (ò«¾Ì%*€E¸ßîœir#е¦Ïw.ôù‰‚t÷ÛUaDP³ßz À%ñÁ)H×xŒœžTs´n7ÐCP "øzD†Ô| Àí@¸ÞývÛ%X’‘QZ‘À¤Ÿ:ÈBp¯ÕÕÓÿ1J¬iø®úIEND®B`‚scantailor-RELEASE_0_9_12_2/resources/icons/user-trash.png000066400000000000000000000012171271170121200234100ustar00rootroot00000000000000‰PNG  IHDRóÿabKGDùC» pHYs × ×B(›xtIMEÕ*/Á3?óIDAT8Ë¥’ÏK”aÇ?ÏûÛ25E²\3éЭ„ûêb"áÚ)ºv‰/ ݪ›Ý²C CíaDç$ 2³pÅ-Ë1eÝÝ÷}žy:˜[aKÃÀ0óá;_þ3@ïå¶ng¼²U¹96²t×ëI·÷vtœíï¨h}|bèLÎxÀhßÃÃXYþ‚㸸®óÏÚuî,»¯ñfzò…³Kœ}7GAHîfDEDQÕφ!¹•\Y‰°°ð–¡û÷*:afú厉=éöÀÕ®®SäV3ˆµˆXŒ¶hm1Zˆc!I :±ˆ€6c ÖÚaГn·×o¤YߘÝñW,›%”ªÆskYßXfk³Àv^S,Öeêõc#‹Êû›EKóòùÌ}zN~ß…‚E´[ž-›è¨_Í#p܈Lö!ÛÛó8Ž‹|¯Š( il¨Ý P¸¸N@Ô…-¤šÒ$ºÈâçI2ÙW45ž@¬`Œ!ˆÂ½€À?€ï× ”ƒrkã(­©ó(\šw"Ö—4îojË(å`­ÁZËêÚõ5Þ1ª÷¢ãø%ÞÏ¡u‚XC"f/@‹ÁZƒˆÇßÉ~{F”òù¸ð”¸d£1ˆÖB€lf=im;ék‘DÐ"h- «¤šNcŒ`Œ%Ž Ù¥¯ àÄqIßzüdävè É  ‡Çû‡¼ñàIEND®B`‚scantailor-RELEASE_0_9_12_2/resources/resources.qrc000066400000000000000000000045541271170121200222220ustar00rootroot00000000000000 icons/file-16.png icons/folder-16.png icons/undo-22.png icons/appicon-about.png icons/trashed-small.png icons/plus-16.png icons/trashed-big.png icons/untrash-big.png icons/minus-16.png icons/insert-here-16.png icons/despeckle-normal.png.png icons/despeckle-aggressive.png.png icons/despeckle-cautious.png.png icons/user-trash.png icons/insert-after-16.png icons/insert-before-16.png icons/play-small-hovered.png icons/play-small.png icons/play-small-pressed.png icons/stop-big.png icons/stop-big-hovered.png icons/stop-big-pressed.png icons/single_page_uncut_selected.png icons/keep-in-view.png icons/right_page_plus_offcut_selected.png icons/two_pages_selected.png icons/left_page_plus_offcut_selected.png icons/stock-center-24.png icons/stock-gravity-east-24.png icons/stock-gravity-north-24.png icons/stock-gravity-north-east-24.png icons/stock-gravity-north-west-24.png icons/stock-gravity-south-24.png icons/stock-gravity-south-east-24.png icons/stock-gravity-south-west-24.png icons/stock-gravity-west-24.png icons/stock-vchain-24.png icons/stock-vchain-broken-24.png icons/right_page_thumb.png icons/left_page_thumb.png icons/aqua-sphere.png icons/big-down-arrow.png icons/big-left-arrow.png icons/big-right-arrow.png icons/big-up-arrow.png icons/layout_type_auto.png icons/object-rotate-left.png icons/object-rotate-right.png icons/two_pages.png icons/single_page_uncut.png icons/left_page_plus_offcut.png icons/right_page_plus_offcut.png GPLv3.html scantailor-RELEASE_0_9_12_2/resources/win32/000077500000000000000000000000001271170121200204335ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/resources/win32/icon.ico000066400000000000000000001453751271170121200220760ustar00rootroot00000000000000 00h¦ è(ö 00¨ ¨Æhn" ohÖ'00 ¨%E  ¨íµ h•Æ(0`€€€€€€€€€€ÀÀÀ€€€ÿÿÿÿÿÿÿÿÿÿÿÿB@‡ww‡wwwwp‡‡ÿÿÿÿÿÿwww÷wwwwww†øÿÿÿÿÿÿÿÿ€ˆ÷÷wwwww~†`‡ˆÿÿÿÿÿÿÿÿÿ†g÷÷wwwwwwˆF‡ÿÿÿÿÿÿÿÿ÷ˆg÷÷wwwww~†`ÿÿÿÿÿÿÿÿÿ†'÷÷wwwwwwˆdˆÿÿÿÿÿÿÿÿÿ„Owwwww~†`ˆÿÿÿÿÿÿÿÿÿ„g÷÷wwwwwwˆdˆÿÿÿÿÿÿÿÿ÷„Oxˆ†ˆwwèBwÿÿÿÿÿÿÿÿÿ„g÷÷hwwx‡wˆdwÿÿwwÿÿÿÿÿ„w‡www†wèBÿÿÿpÿÿÿ÷„g÷÷‡wwwx‡ˆdÿÿøÿÿÿÿÿ„‡÷÷wwwwxˆvBÿÿøÿÿÿÿÿDg÷÷wwwwx†ˆdÿÿÿøÿÿÿÿÿ„‡÷÷wwwwxˆ†Bÿ÷ÿÿøÿÿÿÿÿ„gÿwwˆˆˆˆdÿÿÿøÿÿÿÿ÷„‡÷÷øˆˆˆˆ‡è`ÿ÷wÿøÿÿÿÿÿ„g÷÷ˆˆˆˆˆwˆFÿwÿøÿÿÿÿ÷„‡÷øˆˆ‡ww~†`ÿÿwÿøÿÿÿÿÿ„g÷øˆwwwwwˆdÿÿwøÿÿÿÿ÷„‡÷øˆwwww~†`ÿÿxÿøÿÿÿÿÿ„gÿw‡wwwwwˆFÿøÿøÿÿÿÿ÷„‡÷÷ˆwww‡~†`ÿÿwÿøÿÿÿÿÿ„gÿvwwx‡wˆdÿ‡ÿøÿøÿ÷„‡÷÷wˆˆˆ‡~†`ÿÿp‡pwÿÿÿ„g÷÷wwxxwwˆdÿxˆˆˆˆˆÿÿ÷„‡÷÷wwwww~†`ÿÿÿÿÿÿÿÿÿÿÿ„g÷÷wwwwwwˆdÿÿÿÿÿÿÿÿÿ÷„D‡ÿwwwww~†`ÿÿÿÿÿÿÿÿÿÿÿ„D‡÷÷÷÷wwwwˆFÿÿÿÿÿÿÿÿÿ÷ˆD‡w‡ˆˆˆˆˆˆ†`÷wwwwww÷÷÷‡hˆƒ‚RRˆ„„DD„ˆˆˆww‡DDDDDDD@DDDDDDDDDDDXˆÿÿÿÿÿÿÿÿãÿÿÿÿÿéÿÿÿÿÿíÿÿÿÿÿìÿÿÿÿÿîÿÏÿÿÿîÿ¯ÿÿÿæoÿÿÿ÷~ßÿÿÿó~ßÿÿÿù|?ÿÿÿü|ÿÿÿÿüxÿÿÀ€€€€€€€€€€€€€€€€€€€€€€€€€ ÿÿÿÿøÿÿÿÿÿÿ( @€€€€€€€€€ÀÀÀ€€€ÿÿÿÿÿÿÿÿÿÿÿÿ‡xˆxÿ÷w€‡wwwww@ÿÿÿÿÿx÷wwww€ˆÿÿÿÿÿøwwwwwfÿÿÿÿÿÿx÷wwww€ˆÿÿÿÿÿÿôwwwwwd‡ÿÿÿÿÿÿôwˆx‡w†wÿÿÿÿÿÿô÷wwwx‡dw÷ÿÿt÷www‡†ÿÿ‚ÿÿÿôwwwwˆdÿÿˆÿÿÿô÷wwxˆ†÷ÿˆÿÿÿô÷÷xˆˆ‡dwÿˆÿÿÿtxˆˆ‡w†÷ÿˆÿÿÿô÷ø‡wwwd÷ˆÿÿÿtwøwwww†÷ˆÿÿÿô@ÿx‡wxwd÷ÿˆÿÿÿtDw÷ˆw‡w†ø†÷_ÿô@ÿwww‡wdøˆˆˆÿtDw÷wwww†ÿÿÿÿÿÿô@ÿwwwwdÿÿÿÿÿÿtDwwwwwx†ÿÿÿxˆ†VD@ˆHHHˆˆwx`@@Dhÿûÿÿÿùÿÿÿþÿÿÿþþÿÿû~ÿÿûzÿÿýyÿÿþsÿÿÀ€€€€€€€€€€€€€ÿÿàÿÿÿÿ( €€€€€€€€€€ÀÀÀ€€€ÿÿÿÿÿÿÿÿÿÿÿÿwwx‡ÿÿ÷wwvGÿÿ÷÷wvÿÿ÷wwvwø÷G÷w†øÿ÷Gwˆ†wøÿ÷Gxwvøÿ÷Gwwvx÷÷G÷wxˆ‡÷Gwwvÿÿ÷Hˆedˆˆˆ‡þÿÿÿÿþ_€€€€ÿÿ(0`  (2= ..!+*53345!!!*** $=(-?+12/89333:::EI ]S\.D!F0E 1E3J%T?P %E$)B-3A17B5>’EEVVƒZZƒBB’KK’OO˜RR“ZZ@w‹@xŒY~†``…jj†gvi}rsƒs}€tubb’jj’llœuunn£rr£zz¢m€ƒ`„‹m…‰eˆr‚‚{s†‹z‰Œl‹’sŒ’{’z‘–s‘˜x•›f•¡}š¡w¤¯f¢³~±¼„„„‚†ˆ„ŒŒŒ„Ž’Š’’“˜…˜ž“””‚‚¢‹‹¤ƒŸ§Šœ¢œœ«‹ §„¢¨Š¤¬¤¥’¥«Ÿ¨¨‰©°©°˜­´Œ´¼“±¹¤¤¤ªª«­¯±¦¦»¬¬»©µ¸²²²»»»¸Å‹¹Â–·À”»Äœ¼Å¥¿Æ¨¾Ç³¾À¸»ÀŽÁËÄ΋ÆÒŒÍÛœÆÐ˜ÉÓ›ÎÙ›ÐÛ¡ÃË«ÆÏ²ÇÍ ÈÑ«ÏÙ¤ÐÚªÒܱÖߺÔÞÞë¢×â«Öà¤Úä«Ùä¡Þë«Þê´ÙäºÜç¼ßéžàí¢áí©âîµãî½âí¯äð³äðºæòÃÃÃÇÈÉËËËÏÏÑÎÓÕÓÓÓÓÖØÖØÙÜÜÜÂÛåÆàëÉâíßáâÂæñÄèôËêõâââæçèçèéééêòòòýýý;; › 9P_ P›;\PžD\ž _\\B 9£ žD_› B¤››éððîîºÐÎÕÓÛÚØËËÎBžëúýSîþþþþþþþþþþýüûëêì Ÿóô÷÷öèèççæããââáÉ–?9µþý\µþþþþþþþþþþþþþþþþþ£4ªøø÷÷öèèççæããââáÉ™w=ý_QþþþþþþþþþþþþþþþþþþIX5Ãøø÷÷öèèççæããââáÉ™x7 SPîþþþþþþþþþþþþþþþþþþKN0Öøø÷÷öèèççæããââáÉ™x7B£þþþþþþþþþþþþþþþþþþý^6J òøø÷÷öèèççæããââáÉ™x72žëþþþþþþþþþþþþþþþþþþýW€óøø÷÷öèèççæããââáÉ™x7]ž»þþþþþþþþþþþþþþþþþþýza%ôøø÷÷öÒÞ¿ÛÓããââáÉ™x7œµ»ýþþþþþþþþþþþþþþþþþýzau&ôøø÷÷²MQMYB—ÌâáÉ™x7­é¼ùþþþþþþþþþþþþþþþþþýqat3ôøø÷åMP¬ÎÛÊÌ[[¾×É™x7íùëðþþþýîîîîñþþþþþþþþýzas3ôøø÷à±èççæãã•DÆÉ™x7ïýîñþþþþ¼ÿ;õþþþþþþþþýqal>ôøø÷ß¡èèççæããÜ“R½™x7ïþõüýþþþþDÿîþþþþþþþþþýqfm>ôøø÷÷öèèççæããâŠPš™x7ûþüýýþþþþ\ÿùþþþþþþþþþýqfh?ôøø÷÷öèèççæããâ³QM™x7ûþýüüþþþþ\ÿùþþþþþþþþþýqfi@ôøø÷÷öèèççæããÍMQ™x7ûþþùùþþþþ\ÿùþþþþþþþþþýpfg@ôøø÷÷öèèÙÓʳŽ^PP˜™x7ûþþñðüþþþ\ÿùþþþþþþþþþýqd*Aôøø÷÷å´¢QPPQQQP‘È™x7ûþþûëðþþþ\ÿùþþþþþþþþþüqdbAôøø÷å®PPQQPRP[ˆ×É™x7ûþþý¼¼þþþ\ÿùþþþþþþþþþûpd`@ôøø÷¨PPPްÀÍÚâáÉ™x7üþþþ¼¶ýþþ\ÿùþþþþþþþþþùzd`Aôøø÷²QÁäçæããââáÉ™x7ûþþþë¤ûþþ\ÿùþþþþþþþþþùqd)@ôøø÷UP´èççæããââáÉ™x7ûþþþûžüþþ\ÿùþþþþþþþþþùqe)Aôøø÷ÂQÞèççæããââáÉ™x7õþþþþžýþþ\ÿùþþþþþþþþþùqe(Aôøø÷ÞM’èççæãã}ÇáÉ™x7ñþþþë»þþþ\ÿùþþþýùþþþþùqc(Aôøø÷öÒDÁÔæÝÌ«ZÜáÉ™x7ðþþþS»þþþSÿùþþþSîþþþþùpc(Aôøø÷÷öÎ’MYPPTâáÉ™x7ðþþþ»Sîñ ÿ¶ù»ýþþþþùqc(Aôøø÷÷öèäÓÑŒ³—ÜâáÉ™x7ðþþþüPPPPPPPQPPþþþþþùpc(Aôøø÷÷öèèççæããââáÉ™x7ïþþþþþþþþþþþþþþþþþþþþùpc¦(Aôøø÷÷öèèççæããââáÉ™x7ïþþþþþþþþþþþþþþþþþþþþùqc„(Eôøø÷÷öèèççæããââáÉ™x7ïþþþþþþþþþþþþþþþþþþþþùpcƒ(Lôøø÷÷öèèççæããââáÉ™x7ïþþþþþþþþþþþþþþþþþþþþñV|FLòÖÏÁ´¬§—•”‹‹‹‰yO8/ïþýûîé¼¼¼¼¼¼éééëëîñüýñž¯ O &R%+Q $_07Q!$d#%m#%t#%z"(y-BB,HH)MM>‹>>‘<99hpYåþøÝþþK7þþþþþþ÷Líöõv9;Wca_R$9”—–•’z1Œjhe\„™™™™™|G‹TFS\ ™™™™™vJ‰0^P.2— }™™u(KŒjh` A˜"™™tLŠ3 I“"™™rMVU][H‘ƒ"™™qNˆ+fQX@Ž!™pNŒb4/Z?’&%9:™pOŒigdY<™˜——˜™nš ),þÿÿÿÿþ_€€€€ÿÿ‰PNG  IHDR\r¨f IDATxœì½yt÷uçû©­«Wì A)J\EQ’©ÅRdK±âXŽå]±Çy²s¿çÄy'3o’L<“óNŽãDÎIœ8±3ñ’8/Ž/Šï–dÙÚ%ŠEKâ ’bo ÷Ú~¿÷GuºèHõ÷œB£kýuUÝû»û…&šh¢‰&šh¢‰&šh¢‰&šh¢‰&šhⲆRþ\—HðW‘ûCJ!”Œ”jA2R*RŠc…‚x./ùÕvM¼²Ñ×××nÛö¦)Û¤”›TUÙ ªj—¢(QMSbªJTUÕ¨¢BX–ÅIÏ“?sfø`åy UÓºúj’†á¯TU Ã01ŒºA ×U±,á ^¶Xt ¶í±K)™œ´ÉàyŽcaJ¤l‡©ó¦J»t AçÑ ´Æœ;˜,/Á€Öò²(\|ê.á¿ÁPôÿwl°³P²ÁµÀÍ‚ç€Pýuž®E„ë߃‚íß+Çó%“¼ yf€¢ Ø“p®|ÏÒ€Àßîà3 Ÿù¬5ôEáý½ð†ëaçNèԀȆáaøþ0ü>Ó¼TÑ^ñ äÚJZŠàϺ$¡+)ºhS ¡B'ÕAøÇD P5ˆzÄ?£ èhq0¢ & ’„Xâq|Ž//ê ÿÈWe œšHß0Ÿ H©31‘'ŸÏR,:Øv‰×½îF`–áazzÖljÁëGu!qýS+¡àë6ÉDœžžu$qlÇ¡X´Èç28®‹!ˆDâÄã-˜¦ ºŽ«(œs]ÎØ6žmC±ˆ´,T×ÅÃóÐ… *mRÒéyt A h)߼Š30ÊKâBot Ÿº åÏ">Õ[€ ^J–ÏTì¢ÿéæ}Æ",ð4p_â”Ê\¶è‚âAI@±ŽiS.Lz0>é«A|‰%ƒO”yf,ÁLÖÇ`[\«ÃU ؘ€u½ÐÞÝ7ƒªT¸’¯†'`û¿ÃÛ:`ØEñ™¤¢øªšªøê›&!/}&:±Ð}s îͦO<HŸàü{3Ëv!šîÏŠ^ù{Ìôo;.˜PUÿ>j:˜ÏÿÝQ£|ŽèѲWK-9;5fð»ãÐð7Fð‰ÕðÇI”•'Ø•†!mÃÎut`Æ= I"¡Ð××ËÝwßÌλèîîæÞ{ß‹iÆË6B;ç¹$“IT•]J_,VUµ¼håE%W,‘™™&—Í ézhYL%Äâ1ñ8±X!$…B|¾H>Ÿ'—Íãy!RH£‰š>³Ñ ÐuÃ@E)ɺ.§]ÿ‡`ÛþRfº”èB •UJ4)1¥$!%I)I A«Ä j1XD©˜"]}áÈãë,ŠÂˆ¦ÑéyÜ(—ï˜ D«&.þóH«*3d’IÔÖVŒÖVâ]]\¹g¯¿é&¶ïÚźuë8~ü8‡bppþ¡!NÒ××Ǿ}û¸îºëxùå—yþùç¹ùæ›yüñljÇã@¥¡ÞÁ0f­ :¾*]&üÙEJ‰DAU®Ü²UÓ±¬'O&b(ŠŠ”´µ¶ÐÞÖŠah¸žG6›#“Í13“ejjÊW¤Äó<„”˜f„––$QÓ$‰`š‘Ыล’…eÙ‹%Š¥™LÎW7¤ôÇ%üO!%Š®¡™¾ÕS¨*B×Q4-\\À‚iÏëXTÇÇAº.ÒuÁóФô™†hŸª$€„”Ĥ$. )1¤$†/JøÒÂjˆ LiEaDUÓ4²ªŠ«ª¸šFNQpTWU‘š†ÅB‰ÁînZ=–BX6KÊuiÅW©ôó_²‰óÀÂ×ÛÒ@6•"—LbÅãè©FyIuv²eûv®Û·{ö°iÓ&9vìÇŽ#—Ëqà¹ç8ðÜsÄãqvïÞÍë_ÿz<È©S§˜™™á‘G¡££ƒýû÷sÝu×ñ£ýÈTH²üÞ;(І®GBŽ/VÀ'Dµ¬u !yî…#ômÞ@<ž ³qS[ûz‰Å¢äóFÇ&8{n”RÉÂó¼2ÇèºÆ¦ëH&´´$1ÊþÆRÉ"›Ë‘ÍåÈçŠÌÌLãyYVD@ìH±(m-) à 1ÂOÏØŽc;X¶ƒeÙX–mY¸®ë3 éŸÓgiÊìwUñ5L3‚ª*xªŠ¢hhšV¡©èºNQJÆ„Ÿëº ªç!„ÏCxRt_ñBt!P¤DT!@Jt@|¦R^W È* YÅ'bWñÇ*T§ÌôD$‚­ë@ UI)R€ô¿›ª‚íØ:®×¼×u}w‰¢0.žãPÈdÀ²PŠEt×Åt]œ|ž„e/ˆe2´JI ŸY( ŒûR‡Ã¬Š,ŽibÇãb1 ‘z<Ž‹¡Çbhñ8F®¿þznºé&<Èðð0¥R‰o|ãÜpà áØUUÃ4c(Š‚¢,þûî{ßïï§Ké[B‘ÒŸ­u]ÁŸ”%-©8ëºÚ¹òŠ>:;;&&'Éå 8Ž‹‚x,ÊÆëXßÕI2és×õHOO395ÃøØÅR á \!B %ƒ -I¢Q“Dsó(2P­ü稠`Û6ñˆÄq\4$»wï&ŸÏ—›‡a¤Ói6mÙB,£X,bYŽãÐÒÒB$¡T*Q,(:ý33”²Yâ€[( J%D©„[,±mb–…Y(`æó¡±6° Ôrq-„Àú)˜5‚¸Ìú.íòwÛ0P"J‘ˆ/ª*®aà¨*ž¦ái]GF|É3‹á†i¢F£¨†jÄS)Ú::èX·Ž-ëÖѱ~=-mmtvvÒÕÕEWWSSSLLL099ÉÄÄŒS°m½ø"‡_~!†aÐÛÛËæÍ›Ù¿?oûÛÑ4'NpôèQΜ9ÃÈÈÃÃÃ<üðÃtww³sçNÞñŽw`YÏ>û,CCCLNNòàƒÒÓÓà 7ÜÀÞ½{ùéOÊ=÷ÜÃÀÀ@xÏ4M+«>p]§,µK4Í@Qt@I&ùÉμ6à¾?1F‰ô”GÔ4ˆ{wmE3"¤’)R©’©$mônì!™ˆ!„`tl‚Áa¦¦3þL(%®ç!¥@QTÚZS´··Ò’L¢(þËkÙ™lŽl6ª ¶ã?N!<éeb˜ŒD¢* ®ë–™‡ãûËL%`.žç­ª*¨šÆN* 蚆$áQ²¬Ù™¾¼O,j …ÀvdYÝB¸€‡ç¸ \\ÏÃP=¦r‚Ñi~ö³ŸQ,À²,âñ8ãããèºNgg'™L†t:‚ŽŽ¢Ñ(¥R‰\.‡ªªØ¶M4 %&EQ(•Jå—ÐD¼b;ŸGØ6¹lÏq(Yzyâ± $K$PËóˆ'|;k4'‘ô}=©¶6̨oi[·]×Q5® ¿¥¥Ó4‰Çã ‰D“Íd2xž‡mÛäó~ZK:&ŸÏS(Èf³‹ÅòŒiT1I)e¨&wttÐÕÕźuëBÆÐÙéÛÉFGG`hhˆsçÎaÛ6š¦…"y,㪫®bÇŽlÙ²…‘‘Ž9ÂéÓ§g™>ÐÝÝÍ 7Ü@"‘àé§Ÿ&N‡Û¶nÝŠ®ëäóyÆÆÆ(•Jœ={–£Gð¾÷ýŠ/Ù–ýÿ¶]Â²ŠŒŽŽðOÿôÕ?yöÙç>¦+ŠÏ`}7ï+ÂC /t¨ªÁTz†{hoKñªë÷r嶭ضËñ§9sf×óp]EUذ¾‹îÎ6º×u1 @b;c£ŒŒMpôÄhù…÷Pð #ù;;ÛHÄc$“1RÉD5aK‰m;Š¥Ð>Ï—˜žž¡P,–%ŸäüèÏ®žç¡ªŠOÉm·Ý†eùòPp¿TUÁ4£¨ª†ãØå@ 'ôðP ¾·kw†I$czÐÅ>g3™j!b¨´$clènãÖW§ËÄã ’ÉBx aãÆõlìé X,rvx”¡aŠ% Ïõ‰ÀóÜPêîj§»«ÃœD>wÎå LOg˜žÉ’NgÈå‹~ƒ”(º¼/ˆ²‹QJ‰i˜‘YiÀÐ5"MW1#4U ¯Àu]lÇÁ¶<×ÃËòƒ …"RBɲñÊÌͲ|˜e;º2!$º/QQ²ì²Bâ8¾¨®ª¾èe;†ákú¶å`Dtv,»L‘ ë¾Š`Ù6†®•Å·²×[tC%1T,Ç%Ÿ+ú…Há‰(X% ×±qléÚH鑊Jt\OâzpjL24é²\×套^âìÙ³þo·,FFFBÐÙÙ‰¢(d³YJ¥®ë†Ä¾zã8‘²HîyÙl¶*D5X‚}W ðz{{éèè`dd„—^z‰ñññy’ÅîÝ»éééáàÁƒLNN† mãÆìÝ»MÓxôÑGÑ4l6K&“a||œááaFFÎñ|Ã0(•ŠXVR©€e9Ç¿ø_’\0ÐBÎeút†·$#ŠÊÊ:€WÖ)͈Áö«6Ó×·™¾Í½áƒ;~ò4GŽõcÛv™Ø=ñ8]lì颳³Ÿ%ù‚Ï ‡†±l;Ô­EÙ. <!FD§¯c=©D‚––8ñx4$ ò­ ?-˦P´(ä‹~°R¡ˆëؤ§‹H!È勘¦A±d5#‹¥ð{,ñu}×-{8 Tòŵ¨i/xèºJkK+–e㺑pñ¸‰P(•R’H˜eîAx‚bYB0Œ$†®†RJÓ4Pp\ºSó…C£T²üýe9®¢ÌÉZb‘(3pž+Ѥ ª‹¦9D"­->Á»ž‚çU2À¥™ît]gß¾}ìÛ·/\çyGåôéÓ€?Ûçr9òù|ÙÿGÜš¦Q(ª¼L•PUÕ7¬–‘H$BÑ<‡¦iáL„³t€Jñ¹˜žžFJI&“!‘H ( ±X UU)‹Ø¶ÍÌÌ̼±+Š‚®ëttt„ËŽ;èêê`bb‚¡¡!†††(ý8™À '„`ýúõ\sÍ5tww“N§yñÅÉår¸®Ë‹/¾È±cÇØ·o7ß|ó‚cU­2ƒðï£ÄuírðãKõå÷§rüºëò¯Ã¼- úæwÚ¾k`ZH$Hba#•aèìÙu{v]I%Q"%3™ýg9rì¤ïŠŸm]—d"Îúu´··ÐÑÚB,fú× ŽG2=“e*=ÃäÔ gÏÎø3²ðõ£J‘ËóŠâÇøãO1ÏóˆÇM„$âQ¢ÑšÖîç4˜‘ÀÌh$œÍ+‰Ô?F…ªWÎÛ¯r[¸Vúâ{ ‘º®Gɲç+C&„„bÁWo,Û&›-’Íå}Æèyxe}Ô¶Š$c$ì’ÄÓ%* ],KR*I¬Òâ³ür¡i{öìaÏž=Uë¶Tf†@8 3`,«šé*¬ ^ä¶¶6LÓ¬"ìT*…®ë˜¦I,cff†¶¶Ùt“b±H©T/u^TªªªÒÚºpz:&›Í’N§™žž&NcÛþs tï³gφ†7EQèîîfãÆÜyçÄb~¸ÚÔÔGŽaff†b±È³Ï>Îúûöí[‹ÅbD£Q<Ï# B`ÛÅðþ»®ç9¾+¾L7ºmóöqŽß »+³vÌŠ‡£)*Â\ZZ’\wÍ.®»fQeB’¤§3ŒŽO1>1In¨èëÑB†ÜÊõ¼² ‰D”xÜÄŒ˜ÑÉDŒh4B´üõ¹¡’¸äiÁ²¤”dsùª}=OËÂóÌÚÙLu ”àœ²]¹eöÓÿfÆ7ú•ŠáÏ¿ŸÑ¨/}ºŠ®Kº»¢x®ÁÆî™\”ñ‰,¶eã: Rª¸Ž‚®zlïK!ܨ?Q^ƒ²4`Yèéé¡§§gÞúB¡ÀÑ£G ÅaÏóÐu=džç…Ĉۮë†ù'QÏÌÌ „ð½Å"Bfff€À^¥†Ÿr¹Éd€ñ9¨u|%Cjmm¥¥¥…öövvïÞM[[[¨Þ˜žžæìÙ³ —mŽãpêÔ)úûûÑ4-4¤^}õÕçe6KE$ –Á= P©T,‰òX츮ƒr¼G sSö¢B¢è*ЦøÖø: ½­…¶¶;·†(Y¦.2X %¹\žbÉ&Ÿ/0=“%“É12:Žm» %š®’ÍHÄ}¦`Yv™°|?¹¿ÍwDY–o’)ÅãQߪ_AȪ ©dtvŒ@6WžŸÔ–%‡…$„Y”ÉÊëZZbeaÜßš/ø*‹ë Çvˆè*ë:ã\ÑÛŠôÜ*B/•Jþ÷eNôçèVñxœë¯¿~Ñý¤”¡±­P(P,™žž&“ÉøÆÃHÛ¶C•ÂuÝiøv Û¶Ñuß-ë8†aÐÞ>›'”N§« „ 0‚ï!fáÀpÇq]7œýO:Ued ¤™––¶lÙ®]»B×o½ĦêQKK ý'aY_*!|fä8>RêPfNéH÷•8#$îJõ@2'™ŒÓÝÕ ôÌ2ˆ*ÆQý¿”µ×ªJ“©!©økç7ç 7‘gœ–eS*–ÊÄmaYVø©hay+#Ò_@pÝ¡(J±6±|âý¹Ó‹9TU 3]×ÁuÇ!˜Ï9ižÒd€Ÿs…Ï”Ë6Þ«‰&.]$‰Ð¶Ìz82~ü­ï—-ÿE,«ˆm—°íŽcÌù*T¨€/ì–sPª M\úXƒ@]×)‹UÞÓŸ:ȹ᳡ûÏgElÛBá@™¸52<5@)GÄ !PTÇYy‹r«ƒz›X!ósqÛàY~úïß¡PÈ•cŠXV‰ÑÑ1Û²JA™ÌÍ3?V[AúVD$†®‡ÑoM4ÑÄÚÃÌÌL•gA—’=ýüä›R,汬SSS<÷Ü‘GOž<ó}˜5Λ ðãß)‡•JãÔRš¸Ñ´é\hiiaxx¸j]6›Å,gÞîÎçiúÏâ™|þh¶ðÊZ àÕôò¢(¾ bus6ÑxˆµèhbÙPÊIf•B Wèx=¶Íûý|ƒoB6WQWµ¢RÊ'¢œhâ1éM\hÒÿå…l6&V-aôRà(Ô¬—­((¨éç¯ùÖ\.hª—‚à%!Ñh”L&ƒ®/ZÏ)ôFÀL­¹Ý…óóÍ=W4€—š¬üò@*• U€ °2#s1@…ùNP˹銪 ªÞ4qy é¼lÏç‰ÇãËJ‚  80SknrÝQ(WiJM4±–ä0hæÒŠ®*ÀL­R z¹îœ‚‚ª¨¨ Jnh¢þh —t]§Pð3X•E¬1GûSJ¯•@ªâ[‹ÅÏ¢é¸\ÐÈlÀ&ê‹Àè·”:Z-P€©Z¤-ʹÐ~5Q¿Ôp— š à²A©T ‹±.*€ ÙbWBU¿ˆ,±ôšÀåEmê—R©™L†™™™°@ÊRxlg^ƒЪjÏ7_˜Ë R6ŸçåÓ4ÉårUA@î=v˜±g{+„ФDH*2›/Íå‚&?¿|ô¨Di‰1;p 5€+ªª„/ËlK®&.u(5󚸴&D"¤R©yu +¡Íi:Ä ÜŠU¥P!JØ+X·½‰Õ…Ú.+UlÛ®ª´‚i _ËXn èï¨Î-‰ÝÄ¥Œ5Tⱉ‹Dп –ƒP°jT ;¨ŠÚŒ¸ÌД.ý +«EOf@¦Ve`EJTU)‡W×ZoâRG“\.¨4mÐ"K¤Õp/»† `( A¹|!¥ß±ö2‡”’GŸ:ÁÿÙòÄ3ý‹æW—J?}*ìì{©@i†u_VXJ›ù „ÖüPN³íÖC¨RúlCÓp/ãd ¿ûüCŒŒeè?3®;ñù‡)náÎ×îàЋ#¬ïNp¢”®Î ñðcDZl—––(Wïšßg­Bm€ÐßßÏ 7ÜP÷ë¬6n»í6¾õ­o­Úõƒ®Gžç…“A<ø'dJêàQÀo­Í%-5ÚŽËÀÐW]±~Þ6!$'O3:ž©Zoû®ÞŒë ¾ÿã<òÔ»ÀØxºj¿½»7±w׆Ên€kHìzüñÇ«zÙ_®øñŒçy ë4ù|ž|>ªª$“ɰ“óR2§0Ë\QTïÒ r]C/žá‘'Žòüá3¸žÇg>ùÒ3NL2tn†á‘Æ'rÌdç[Pc±ù¿'0“ñÛ_Ï%~€ãý¦§§™˜˜àäÉ“œ»äsƒÏì~ë·~kYǬR©–e…I@•mÂç"2‡÷‡ À«a¾€¢]íÁ¤”¼||„D<Ì ªjxCº:;ð„ã™Ç ­¦RJ&''ioo?ï ?ï:‘HÔ·òùÙâÝK¯ÍÖpÔ9è©§žšŸ^ ±XŒ~ðƒ|ä#aÇŽ‹î¿mÛ6þöoÿ–Ûn»_ýÕ_]’Õ;‘HðÃþÞÞÞ÷Ñu[n¹…[n¹…}ìc9r„/}éK|þóŸglllÑk¬¦º“ÍfÑuÃ0˜ššªÉ8Ì}cÃi@© ì”{¹ûµÇ×ñ[¶Ë“ŽñÄ3Çxöù“L¥óüÕß?È?~å J¶¤½½–– ……BÇÉóž·]Ϧ ~÷”WmbÛ–îðœRJ¦¦¦¸çî½Üýú]¼êš¬ëò‰8›Í266F¡P@J¹$]««cí2]«¯:÷ÄOœw»ªª|èCâäÉ“üõ_ÿõ’ˆ¿¿ò+¿Âûßÿþ%íû|à¼Ä_ »víâãÿ8gΜáþáصk×y÷?}úô¼D.—#/{’®4΃Vö- )×\ðó‡ûùÔ߀Wn`ÿuÛ8pè gG|ý\A©TªŠŽ:7’æÅ#ƒü—߸§žfKo;뻓¼øò÷Å“Éú*Ãöm´·•=%R25]àÿüÛd]7ìWŸL&I$óÒ¤+›4üÛâ^{¦¹}îu6>úè£ nÛ½{7ŸûÜç¸õÖ[/êýèGù⿸è~oxÃ.øÑh”~ðƒ¼ÿýï糟ý,ûØÇªl@•xüñǹçž{.øZŠ@»F(ȺoB®©lÀ¯~sv†9vòÿ«ïòÐ#/~ª«a¨ó&îÞ±‘w½õ&^½ Ö· ª ×ìéåüî›ikcèƒÃÕ–~CW™JÏŠöB2™ ãããónú\ñkl"·"¿w¥5ϯæ\ „<ýôÓ5·½ï}ïãÀMü×\s mmm‹î·”}ƒ®ë|ä#áøñã¼ímo«¹ÏbRO½¨¬…B!, î.q€·@2Ppžˆ¡aÛ«Ÿ (¥ä+÷?ÊÙs“áºH$ÂT:[ŽÆû¹›®â#¿~GÕqï½çVþè÷ÞŠiÖfb›6´ñ±ÿz7ÿÏÿ}ûöT‹‹©¤É§?ñ.îþŽÄc³FB!¼yÆ´À>°{ç¦PÝXK¨w=ÀÇÏ«J£( ÷ÝwÿüÏÿL<_àÈ壣£cÅεtuuñõ¯ßû½ß›·í|RO=Q(¦ ÷ª´ÄÄ J`u«R"¥‡E×Âf¡«‰gž;Á7¿;;»hšFWWWø}ÿµ[¸ëç÷`;.ïyÇ-þ>ªÊ/¾nï¢ç^ßÝÂúîj9ÉÚZã¼ûíûyÛ/íã«ßzއ9Æ=w_G¦ yîðHÕ¾­­­D£Q6¬o÷s)ÖX€`ç÷v\ ž|òɪñùÏž{ï½wůUÙ ·QPU•O~ò“ôööòÑ~4\ÿüóÏS*•5"×AU`8¿* Ut‚EâdùOØ~h ØnzÕvöìÚÂKGΉDhmmŶm’É8ïzË \··D£o¹k?ñùÖâ‹¿iê¼ï—oäõw줻#‰ªÂ†u ¾ÿã~¿‡BÑh”_xí–‹¾^½PO7àÜ™ðoþæo¸÷Þ{±,‹‘‘¶lY¹û²âý…âw~çwà/ÿò/?+ï駟浯m¬ë×0 Ç!™L’Ë媘Áb¨| æõ±ýšàa€€¶’¦Òy¦3­­­´··“N§™œœàçnÚÊu{77lë»SåÙn¿¥÷¼mOø`û¶vz7$6žåB3êgÏ©”~ÿ÷ŸøÃ<öØc=ztE¯µš à¾ûîão|cø}®ôÓ)MÓÈå–gsª´Ìë¨à‡Œ3¿®7Öš=2šæwþÇçxð'‡ÂuѨÿâ& 2™ ®ë"%|õþÇ™L¯žÁíÚ=ëx˶“ÏçæÉgNððc8ŽG&W«ëÂêB«STçÈÈ'Nœà½ï}/ÿøÇÃmýýýLLL¬èõ–Sý¦Ð4ù—¡»Ûw'?öØc C©T ƒ±ÆŸoe2PÖ*=בrwàÕRa5]cd4Íýßy’Ÿ<þmm­ŒŽùÆ%Û¶«¬üo~ã t¶'YM}Ûug£±J¥_øò|éßTn½i |ßÚÊŠ3#K ].Kø–-[øÌg>Så&íïïgÓ¦Mu¹îj¢½½?ÿó?çøÀªy É?c.1' ”\È/d7”€· ÀÖßZ<9•áøÉ!††§BKPø`ïîͼóÍ77||sqÿ·_˜·ÎõÇûWvÖ[ Äb+g‰¯D0Þwß}UjŽ=J&“arrr¡C/iÜ{ï½ÜvÛmŒsìØ±†^;˜ýÁ'~cqõ.Ü¡RHÏÍö×* gRJ§±eÁ"†NĘe*Û WÖ?ûµwßÑÐq-„ßù¿nçµ·n›·~d,GnAöÚxx¢~}>üásàÀy1‡Â4M¦¦¦êrÝÕ†¢(|ãßàÀ¬_ߨ¬Ð ú/^Bî_Érµš {å“kºÖð\€Gžx»¢IÐþØ4Mº»;Ù¼©‹Wß°Í›ÖFÆÝæMm|ð}7óÇÿý«êîK)9qj Í|¢Ñú¨W^y%û÷ïŸÇ`NŸ>ªªH)¨ËµWÝÝÝìß¿¿á®ÉÀíÄŸ,áôšéZõ~ü` ¹*E$‰jªeY¤R)n½iï~ë”Jkgf °mK'ï¼{é™ë»’¬ëN°mKûj« ìôœÍfÅ0 b±'Nœ ¯¯¯a×% ¥¥å‚¤«ÊlÀŒã7©2*Š/Þh öLMçøî/ iRJâñxUپݛQ…X,²ÆÂl|¼å®=Ì$}Uj-3oœ‹ò©§ž ƒc¤”+î x¥#311VPIkPIìvÑÏ ×™àw¢áÉ@­©8…’Mgg'š¦Í)·l^bÿ¥ŠFVx 9Àøøø"G4±X–ª®ëbY,ð|çZ*÷Ê;5j¬V )MS¹bs7º®Ï#þÖ–‰x}tØW ®:00€ªªhš†¦iM `…1¯/༾Ysüä•Ô]˜ÛH Še”é¯Q¡ÀÁLÑÖZÛ¨áykI˜¾ô ‘Kµ_4:Ì Ê­•˜šX9D"‘°¬ÝrPÉ«†„â#’Î ŽñûÿïyòÀ1žy®ÞvÛ¶™œJó­ï=͉þsuÏåOÌO[®Ž;†iš!ñkšF"‘àÅ_lÈõ_ H&“˜¦‰®ë‹–´›‹J0S«2°¨/1ï~æ ßáÌàõÙû™Édæm/‹LN¦ù×o<ÊKG‡0¢ËR†‡‡«ˆ?X*ªÑÄò‘ËåÈf³¡0•Jùv»% ÊX¨EãŠÿ§QA@‰øì‹é.ÒãÜZõ .UÔ+¨d³Ù*i#Pïš*ÀÊ# UUÉ/±@¥P´}7`ˆà± !P”Æèݹül|ÿb–êåZ€M,È¢{þùç‰ÇãUúð¹Ü¬µ&–†%¤WÅ€WQ˜œÓ TT%Ø¥1@›6̺÷jÞ¬]GF§Éå¶x6±0–ØCî¢0::Z“ø5M«ªžÜÄÅ!èM¡iÚ¢u•9„\ÅÜ9UT)QÊ `–ԅ⬲–˜”Ào÷uð…‹kñJ„ÄÏ`«'„ŒŒŒÔÔÿ5M›W¯±‰ G.—ò¬°Gàr Îù2Oqæ ÝhL$`_ï:vmß\Eès‘L&1 ƒ_ýåÛyí-»2®Ë BÖ_8xð ¦iV¥‰D.ÛÌÀÕ€mÛxžJÈÆrÓa~Q@ žoÐ5µ*3¯^xï=wðá_¿û¼]zâñ8ÝÝÝ=9¾Öª•_’º·yÄ0ŒUÃ0šž€B0Qær9LÓ¤X,b.1ΣŠÌmnàKAP#,ÇÑ*÷Æ1<2Íç›*À…`¹þâåblllñW~O$˪]×ÄÂ0M³*°Ë^Üî\ø3Ç   QQ´qÙ€©d¬Ü’|ñéý?~‘“g&¸¢¯“¯»Çu‰4H]¹d!•º¶²>{ö,®ë‰DªTÊ6lRÊó†¬6±t …åF†ž€*`Ïñ(”]qAyàAUÚ[Kªñ—ž)ðøÓÇxìI‡Ï}é‡Ürã6¬oåDÿ(EËæ÷~󗪊u6ÈúF;vlžAª’øÁÏaR»›¸xd2âñø²í*sÁù,Y‚'üÞ ¼éìH-¹È§mÛa-¾=<[–këæ.rù-©Õ-¹QÏlÀñññ* c.ñ³ÿåXp5ˆÿ†a`Û6­­­Œ.ñعnÀªi>ŠOüª¢ày^UXp=!„$Íf—Ôav¡°Ö·üÒMUÄòôŸùâƒ|÷ùµû^i¨W5]Çq˜šš:¯þ¯i~u©¦°2ˆÇã8ŽSeSQ/Ä87@“2´²+J㔀?ù‹å;?x‚l6ëç6/MÓæµâñ8ÿùƒÃLgfýÍ™l‘GŸ<Æ¿|õq>ý¹±,—©tnA[ƒç îÿîóLÏ\^Æ*¯Ž•ÝTÇ/ .dcû¼ãM·òÌÁã~EbUÅuÝ‹^K¥RD£Q_²1Œª§ï?tŒD|¾åê]I¥âüý—ž¥%e²®+ŽQyáÅa^xq¸jß·¾ñjÞñ¦½¬B!åUÅÐÐPÍÒÔsýÿuCx%£X,’Ïç—ê]EQŽ_°º°«ÒmkkßzÞöK¯æÈñ!¶]ÑËs‡Ï,~Ð2°PI,)%¹¼Í† Âï…BË1ø§;Te0\H×úîG¹j[×îñÛ–Žçxæ¹A^<:Æþk7rçk®\Ñß²lÔ‰1¥ÓéyÆØZúeÇ &Vñx˲Â~™ù%YÍœ×@Q¿,ø*ýå·Þ†”Äó/¼4ذkR¢($“I Åù1º®ÓÖÖ†ã8X–…çyåY¾ýÃcìÝÕ‰¦*=1ο󰌦¬:¨Ç“, Ui EæóyöîÝ[‡¼rÉ—šaÙñ ƒ@P•]¯@Hür¢éü>øµwÝÆ>Ì™Á Nœ]R˜p½¡iZ•‡BJ‰ëº†A6ïñÔÁslémáXÿli³#'&p=¾ŠO¬|6à¡C‡Â¹sQÉlÛfëÖ­+zíW: ý:‘HdÑšTÈ€U  3•@exë¸Ì÷"6ªªp×ëö”|çGÏñïÿñ(Š¢ÐÒÒ‚¢(ضM,kXÂR-(ŠR¥Z|çGýóšªZ–ˉS“캪«ÑëÂR¬–ƒ™™™šáÅsU€À]ÛÄÊBJI4ÅqœeìçZÕfæ:܂迥¬7ÏN𽞠]ƒ•M&,˪*tá8Bˆ†UÀ‹JâÅb¡ˆö³—GW¬´P+^£–þ߈Rd¯D$‰*7àRUöª½f 3ϬU¶Â¯tu´ /Ц¬2¼UJÉÔÔÓÓÓ )µ*™Ð±þ,ŸúÜ!žxvd•F£¬è=9rä¡ë¯– PUU¦¦¦¸æškVìºMø¨,LŒ±%Nzs);çVD*ø×õVU´®D,á¿üŸwsëM;«Ö¿æ–Ý\»wkø]UUº»ZñŒÄ„ùeã+«ÿ6 €ÔÉd2Ôý—ø6UÌí  •uÿHdõ€×â±WníâÊ­aµd)ŠW]ÑÎUW´1t.Ëw~t’Ã/Ų,ZZZÌ> ‰DˆD"œÈsÓµõmÛ¥­°X,†‘— ©?RÊ%Utjââà8N(,! ļWA©ˆð!Q°lû’´4s_þÞ )Þö†+ÉçýžmétzÉÔ3góØv}­+Qplll^ë¯ZF@EQšÁ? Â…¨uó€3‡håxv]×Õ ø²ÀÁÃCxeJs]—ÉÉÉ%©BHÆÓõÿW¢ÂÓ™3gX¿~ý¢Õ‡‡‡Ù½»Ù¿±Þê0L »ÄšóÞ9Gð•MÕP›nœ%ãwîâ½ï¸.üî8¹Üâ}ÇáÌÐÚoOUÆýÏý þ_­d¬W†¾˜ª ¨HôÇÄ< @ ( %ËÆ²ëSHòrÅ]wîdÿµ³Öü\.·¨˜æ8Ï<¶®ã’Ù8Ȳ\,ö¿Iü…¢,­å›Z!ËÏcî<€zw“½\ñ›¿~+QÓ¿wñøâÍ8¦§§9yj’ÿö'1“]¼ò…@áâÜ€GeëÖ­‹fÿ•J¥fþQYêÝYbÕçZŠ`U,ª)ð<³é X6 ]åý·_àóŸz'×]Ó·¬d#Ç©“1P¹¸¨NÛ¶1 cA½?øáÊ+W¹þá+Ñh4´!.¬Ìo *¾!«dÕgFºÜ±iC †®²oOí’Y áhÿL]Æôz¸P”J¥y³-ý¿Þ-È›˜E”]»ÐzóÚƒR"„DÕ´¦à"±mËù«áÎ%Êï?\Ÿ"¨SNqppŽŽE9oíUU›jcáº.±XŒB¡°¨!ЫˆÿYTåš{Í` ‹CWG í<ÅUkéåCçêÐÂë"ÀèèhÍê?s¥€sçÎqÅ Hmn"D`\¨YnQ‘î_+ j]‛*ÀÅAUº»6ªª:¯ko¢“!Ê9ŽS5û/Ô4ŸÏ7 €4‰D‚b±¸ì¨Ëy2šç—«Ú „@UÔ•.%ó B¡è02–¥T²ÊÒÔ|ýXUUzÖwòš[ùÁçøÈ½{–ÔyÙKóÏE±XD×õ°WD°L ø,ËØÙÄÅ£X,^Ð=ŸÇ4˜q*6øº©ãºèF3¦ûBqfhš?ý«‡ߥºPrL,ªsõë¹fg¥:&Í•4–‚ãdzeË–ªäŸJªÒضÝôÿ7AõëÀ¸P:ð\ÔŠ,TüòZj¹(ˆÕT.¥Ò¬mõ|uú6­L¥žóáB:+kÍUÎö•Œ@UUNž<ÉŽ;:MuB2™Ä²,_b_À{ÞöàÎÜæ R"„»­…Ž<—"Š¥Ù»z¾8ü}{ê› î,Íé7W«ýF 6Ñ8$“Iòù|ئn¡ç;Wñ«õ&f*çø˜”¨ŠŠçŸ¹š8?N¦ÃÿkG±X$׸rKý+çHé×ò[Nž2YeŸ7ƒ]Mñ˜ÎŠvï˜]ÄÒU¹Ó§O³~ýúª¿Rʪ˜€Fbt Œlddµz06ù|ž|>O6›%ž·™oB1¡È:ù&åÎÀÒÏpì¥×»Xxžà¡G^¿?9Ì©Ó#—$8xx×­~(‰D‚¨©2:–¦»3Îß÷*®èk¡T,Õ½(¨®-Á”J¥š„ þÓ§O7¼úÏððð¢ûœ;w®#Y;XÌÃSY (æôDJßØà‚ Ï>‚©ô¬x|ûÏíãoº©¡c¸Xø•à¹Ãcó¶% tMåÛOϺ¤_{±AqÆ5Œ@MéîîEÿJfP9ûÛ¶}AFŠ#GŽ„-×χ矾£Y]šªªVÕXô¸ë,«¢,XPÊÝ0ô†z~ô“çÂÿ Ã`ÇU½¬ën¥žâñJÁ²\¾òÏS*9üò[¯ed¼öC‰Çu6¬OÑèߤ*K»ÞË/¿Ì† jêüÁg°4:ûïþûï_Ò~O>ù$###ôôôÔyD«Ã0ªªO/ÕSë‰eì À„ÙiLJœU¶—ã'gÅ»¹‰%ž'дµ—ðçŸ~˜ãý?G×çÇþ_Ñ×Âí·llôÐ|,ñI§ÓtuuU¿”UUÃO ,Þ(d2>ýéO/i_Û¶ùä'?É'?ùÉ:jõP,?KÖ IDATÉçóD£QfffH,Q«e±±*çªø @€ëyuO¯DÄÐùнwñö7½š÷þòD"~úľ÷à !ùéã/òÑ?ú⚌L´,7$~€L&W±Í¢X,r÷/læ}ïÜÎÎ+ÛVcˆ,•wæóùÐÍWkñ<!ƒƒƒlß¾½¾ƒ®ÀG?úÑ%éÿ>õ©OñðÃ×o@k®ë†3¿w# „P¬YÀ®0ê€"nÙj¬ž'Ÿ}¥qëM»y÷Û_é¿Åw6W⟾Èÿü³¯ñ¥ÿ #£Óï_{žçWõ êâI)™œœ$Nó¿¿ôO¼ÔF—\8@M«5§? .|ëyˆÇd³ÅÀü"†Ößï>pï>püÛß"b¬n ª‘±<ã“y²ÙYCi, Cd+qÏ›¯fû¶Æ…ÌÎÅRÚƒe³YÚÚÚªföÀPÙtrr².Å?¿öµ¯ñè£röìYžxâ Ξ½øré–eñ§ú§Üwß}\{íµ\{íµtuuñÛ¿ýÛôöö®À¨W¶m_¶ÅTù¢4ÏC¢ø¶@µ±Y^[ûºyÃÏïãÛ?<ˆëº˜¦‰išá‹à{¾À[ïzUCÇ6ž?Ç£OÌvûUU,˪ øyÝmÛ¸ûw²–½žç1==]Õü³’ð+Ýù|¾.Í?ï¿ÿ~¾üå/¯øyÁW ž}öYž}öYÞüæ7_Ò ü„ d2ÉÔÔʲj2 U¾>#ˆû¦ñÝÁ&¦2|ó»11™ñ+æôô ª*‰D‚|>2=ü3Þøº}D"cP™\‰o|ûñ¨Á›ïÚËàp6ÜÌœŠ¢JDØ»³Gžìço¾š·¼áÿoïÌ£ãºê<ÿ}kí{iµdËZl+6‰˜à$!8Ëä„°v` é&:$iÒCh »Kè2¤›%C¤3 iOÛIlGŽc[²¬Í²–ÒRUªõ­wþxU¥Z¥’U%•”û9çI¥woÝ+ÕýÝßûÝßÒÕ^ü‹E÷ôô ±±1'Â,÷O †ÅrÐ],_ýêWqï½÷V¥ï|Özùr‹Å‚p8<Ÿ¡©Ì|Eïʯ(‚„¦ h+ë ôâÑ>LÏÌÈ¢s:p8eCYýƒ\²µ ºNðð#Æî›±}Û£®a™žáé§ñÄŸN8–Å«}!ÈòüªJ?‡mjqâýïÚ §]ÀžÝ-ØÑ]¿Úk¿,B¡¬VkŽÃO±ÝŸRµâŸ›7o¦‰EË$°Xù¹ü|¥4€œ³>Q×ÕSûÿ xGF§æÇ‘EÇ0LŽñéTß8\N Žö à™ƒ'ñÌÁ“°XDì¹¢—moÅe;6B §LÒ~)dEÃcOÇå—mDÇ&νþàŽÈµÞkºŽéépF ™L&ð‹·îmÅ[®n…±ìèn@­¬~C .弇3F% ôî?66†›nºi¥†M)ªªp¹\˜ššZüæ,Šþ÷õ<# ‹a0­ë D/;Ñ@¥8Ó?oø‰F£ :›>:€ÃGr"Ä ž;…϶®&üýß¼3§M$šÄ}ÿ(vtoÀÞ7u"4Çÿùés`{÷t¾Iž‘…ã8ØíF¯@ ]×qÅÎv|àÝ;à÷¦UãÚXôùüä'?ÅwüUÁ룣£0™Lõ¿ÔîŸöþ»˜£”ʳà4E@~q—®ƒè:­ÈW5ÙsÅVüÝÚl6£ i2™ãÁæ°Û0r!ˆ:Ÿ sº^Ÿ .9:€#G2÷BðÈo^Á-û¶a[§Ç^Ãç12j<ç ‚³Ù ]׉DrÝd!Ãïµ V>`Œ,BQ”‚<22’£þ—Úý†É?Êêb³Ù2aÛË>ÔòN¼™`e…3½çíWgÀ¾ëváôÙÅÏoEQÌ<.H’„d2‰d2‰‘ ¾÷ÐA€ÉÄÃå0#™T04R<^|`xÿþèqX-â‰ùX뺺:‚€¹¹¹‚l3f3Ö «ãÝ·ZZZñãÿýèGr~ a6›Ýýãñ8ÚÚÚVg”Ò»:KÓ4HÈŠû-A)@N6O&û ³âF@çðÕÿõA<ûÂI¼íæÝ8}öKjŸ>6Ì%$É0˜x<Äãq$‰TГ—Ë•"ј”±„§sà»]fìÜ^‡'ž62ëX-Þzmn¹avÓâföU†e€ññ1ôõõåh’$! ¢¡¡@ñÝ?­„B!Zü³F „Àd2!"‹>;%îSõ~€@γ8U…ªªXìJ››Ð±¹„ð U3‰D2ÆD"]×ápä&Õ$„ ‘HÀd2•TÒƒËå2žT²,#‹A’$‚ŸÏ§Ã„î.?v½¡ m­.0fgCèê¨Ãµ{Úa6W7‘G%Ñ à÷ûà÷_…çž{×_=àøñãðù|™”_¥vªþ×¢(–Ídy ÉVY®“UÍ È0 >ó‰›qþ X–à[ß<³ÀÓtoÙ€Ññ¹ÌωD¡PÈ8w8`2™L&Áó|Aìz2™D(*H¨ ª >ý±=hiÊÛ5R¥}ê£×f^#kdñ0Ð4 Ãàܹs¸öÚkÁó|Nú¬…v†aV4ú²0‡£££ËO :Î6X@'Љ¾`”ÑJÐÔàÆ•—·ãhϹ¢™OÆ'¦póõ—d~ÞÒi„ÛB077‡©©)D"$“Ip,‹[Þº ï{çehkõ¤4„Â> êzVBñé·Z­8xаÌÌÌ öɾt]ÇÔÔ.¹ä’EÞ…²RD"†qšçù%•e/e˜ÍÏKÃ’¢@UõeÕ–¯ÿ˱¡É‹Gû<ÔTŽ‚¶õøØ¯G{[=¬&‘ÃÎð—/Ÿ*h¿ë -øÔGß c''¸üÒLÍDñìó§ñ»'^Áwµ¡½ÍŽM~t´ùa2ñkl‡_œ´À0 úúúÐÚÚ Qsæïþé†eYZü³I»n[ËÌPê ”ŸSÔ¤%Â@€X¼¼Œ£Õ„a¼í¦+°­³ áH—nß>³ƒ\seGæûÿàN<ùL~÷Ç—O¢mKGCAŸu>Þuëå¸å†°YSR”¬•çú¥’-, ž|òI477gÎÿK=û§SPSj‡ô#ÙÜÜÜ¢‰YÉb6 å+¼‚¦„@Õ4Äã‹VŠÎö¦Ìx©uÊs,n½q'Þ|ÕVŒŒNcû¶ (u3DZ°YM%¿^`˜\À0 æææPWWžçÜý5MCSSÓ*Ï€’ÕjÅôôtæQ`!ÔTž „ Ìšªe˜œ 7k ‡ÝŒíÛÖvÄW%ɶ¯¯¯Çàà`ÉgÿôóÿÌÌ .½ôÒÕ>%‹X,Y–¡ëú¢ñÙ”zK†3PÆšà&Óº0 ‘µ)(ó ‚PTõŒHÇ•ÎÿO©¥þ‹±D^@P‹®#‘ ¨4m}«Ç¯ŒJÏ…»¼ßïÇÐÐPÑÝ?­¤SœQj‡´õ?íÐ¥–yZWRÄóâ6ht¤+ÀÒ¡k$'¿_ö•ÖŠ¥ ‡ÃزeËjŸ’‡ÍfÇq°Z­°Z­ %üòUþR žÌÓ|Hy*ªq@YÓ° )¹Ë{½^ Ýýyž_Ñ⟔ò™™™¢(`Y¶d„fYùD’€v”Îð€›W+€¦þʼnäÊT¯¡TŽEÆÍ9Û»/ý5ß~Zÿk›x<ž“¯q1J òš þ‘ €ÕàÌ( *24MG¢ÌÊ£”ÚEà ÏÈ줟Ù_½^/†††ÐÞÞžýG“Ô&Ù‰q–í h³á;þN˜ÓÉbDÛ©³²$g<ï(k‘gp¢çLMÏÀd2¡µµ›7o†ÃáÈY–3Z` ‹Å—Ó‰ 6`×®]8|ø0¬Vã#L&!I!¸æškVyÔ”…e9'^ð‹ õ&xÛDðDƒ¦©¨›Uö$Ïk¿Çq[©S€Ÿãˆ¢.=U0[zþ1Jm¡)qô= ¿ßsýýðù|Ø»w/l6[ÆòŸÖ¶lÙBj˜th{4…Ífƒà'ñÝõØÐíÉd† ˜ &øýf¶µ•ßg6ã#Eÿ£„dÕä‚Aáv›d¨ªŽ±ñ¥e¥Ô&˜œœD(B,Ã3Ï<ƒ'NÀçóavv6#$IÂå—¯nÑʈ¢UU3\*Ç íªVx}ˆ¢9sB@D]Ù,ŠÌûŠçМ€ǃçÅ”0Áí¶o¢ë˜šž]ÙR*ŽÅÄ àüè(xžÇ‘ÇqèÐ! # f€ÓéÄSO=µÚÃ¥,Bú$À(ªBM™+½‰ ‚ ½‘mJÙfÓ@Éþj\ÍHÊ ¦g‚+8=Êr!˜Ž1™‡Ý* ©0ˆJ,üõÍhhhÀ›Þô&Üpà `Y¶,!ÐÓÓƒ£G®ö´()DQD4…ªª)Mî&^ìô¦”˜6€qeç„Óu ÍÍnÌ…#˜ †ª2Jå ÇÀ ‹YDRa“XxœV¨„’*üzÛm·áCú¾ò•¯ ««+ã[¾P‚Ð_üâ«=5JŠd2™Yä„Äã1èú|‰·t¡cC4M/Þ¥ªú´¢d'„Ô2—ª*`‚¤,#05S¬9¥†‚± @ÀCÑ9Ä$.» ’Æb6ªÁé°Ád¶ ««+S믥¥_üâñŽw¼„”Ψi†††ðØc­ò,)R†=#1¨á‡¦©Ðu5G•ŸuȲ(*4 çâq%¢i*Š]ª*Ð06>…pxñJ$”Õa|VC0ÆBÕxÄdV‹ˆ¤Ì%Yx]v0,‹@0·Û‹o¼± ýûßÿ~Ü}÷Ý0™L jû÷ïG8^…R²±Ùl°Ùl™”`’$AU•”&§¦„ñ?›˜K‰„üëRÞ§çæÔ@î¢W ª2Ÿ.»¬³³Aœx­wåfH)‹pTŹ 2¢ƒ¸Âå$UŠÆÃjµ@Rf"2'œ;LfsÉJ¿{öìÁç>÷9Øíö’B  áG?úÑ Ï’’ H&“OÀôzUUš¦dmâ NŸžêK&µŸ•R<®¿,ËJÖâWRIP MMCs8~båfHYM'è‰apBF\a!),$•'˜À0<æ’:tð{]`SÁ(B‘$î»ï¾ûݾ};¾ð…/Àå*}TxàÀœ8qb…fJ)†$I˜››`|  AUåÌÖ4ÇŽG£òÇw€h÷ö÷'ÏåœÝ_Q$(Š Â(˜ÐÄ µÀX Ž—^ b&¬"&²Ê@#<E$ Øìv°pLÂT0»Ã¯ÇMÓPWW·hÿxààñx Љ¤=Ðzè!'°Š¤]~Ó'ÙkUQdÄãq¼ðÂÀÐØXôŽDB9 ÜýEtôÌÍiov»9ÏüÙ!É :ˆfÃ-7][õɽ^HWæÑT ªšZd©ïUuþ{-u…# ¼tbgÏGOÄ“:tÂB×Yp¼I!UYœDQ„$©ˆÆ“п¿_úÒ—Ê›ÇãÁ®]»ðâ‹/"‰¤>$åc®cjj f³Û·o¯î‰R”Ó§Oczz Ã`jj áðyD£ ŒŽNGOž;ß×7ñäøx佊¢õ¤Û”JH&ñl2©]+I‘:É>Ÿ©ÑfE“I„Ù,ByŒŒŽ"šƒÛí¬þ )!xùÕqœîŸF8"C# 4 „Mö0Yf³ñ„ IÖ05†Ùb…¯®º®gq2™„Ùl.ë}ÛÚÚðµ¯} ÷ß?ÆÇÇsÆ>ú(öíÛŸÏWiS@X­Vhš†ãLJFF¬ëê«ñ¸r À 5ýÒÒDdD"ÚÌÎÊ&'¥#““‰ã££Ñ3CCsñXÒ¼kçvO{-»] ÊÑzû§ðøŸÎàÄ™‚s*$dŠpÐt@Ópà ,Vx^ `&]ñÇd2ášk®ÁSO=•1•‹ÓéÄž={pèÐ!„ÃáŒ`œ=Ç133ƒë®»®J!J)zzz0;;‹Ó§O†††><99õ]EÑP´žßRþóAUÅŸTUÿ“$Í÷‰LØûÎŽî»nkyç,ÆèxOûÙÏ¢¿¿2ŽCßûÞ÷°{÷î²-(ËÇb±àرcç:tW¹m*!ðÃýòÃ?üÑ/=‡£¡{KGG½ßÕìö8]^·Ëi³™ÝN§Ýi±Zfﶘ- N§Õ¿yc³ck#g‹W1¥}ýÚ3O½vâôð—¿4¼]9xžoÑu=+Ä›YR ©R444àÁÄ=÷܃ÞÞÞŒ&000€|÷ßÿ²ßƒRgΜIŒÝ¹”6)‚‘H$øâÑWΔq¯Éïw´]ÒÙµ³msó†º_ƒ×e«w8u6‹©Þn³4lnknØÜÖl²˜Ë/t¸Ðu‚§þëdøåWú÷ò‰‘o¿òÊÐ+Ëí³µµõ*Q­Ù»Zhš– !½X|>|ðA|ò“ŸÄ¹sç2B`ÿþý¸òÊ+3Ù†(Õ# app𧃃ƒO/¥]y5„WÖb±´ìÞµí›Z77o¨oôzìM»­Éa37úýžÆ®ŽOcƒIÅ<‘TðÁ|Ôü÷Ƈ²ðu£]Vù¿ÏjG Úå½G‰v™ Œí Ï>ê˜|áµÓÃÏ>ùLᅢÁŠùÖnÛ¶íÞÖÖÖof/~e™ìرã¹îîîm»wïö]~ùåìrªÿŒãSŸúóÆÆF<üðÃhnn®È\^¯(Š‚ááa­··wznnn"N„Ãá‰ÉÉɉ .L ö>|ø€%EèÕªX GggÛöÎÍM—µµ6ÔûëÜ n—­Án³5˜¾Á_ï®ïêhq7Ö{2qϵ"&&ƒ¤çäPxzjn8žÎŽMG^}uøèç^{@U-îܹó—^¯÷ölÀ0 !x饗.…BÇ›››[÷îÝûíííûZ[[wvvvÖ_sÍ5B:ÃL¹ â®»îÂÈÈHFèêêÂC=D! ¯¯/2666‹Å&£ÑèÄìììÄØØØÄðððhOOÏËý0ê~V„µ*ÃÑÞÞºu[׆ õîF¿Ïã°YÍn‹YôÚ—(ònže½.§ÍÝØàq5Ô¹y·ÛŽKÿ9–.4MÇl(‚`0¢¦Â‰ÀT0$Ëêx$–œˆÇ“S³ÁÈÔùñÙ §ÎŒíí{FíÆªã÷û›:::NZ,o¾À0 z{{o>þ|±²?Ž}ûöÝÜÝÝ}uKKËU];vìð\zé¥lº\x)Μ9ƒOúÓ™ŒA„\z饸ö·¿ýºÕEÁÄÄΞ=žšššUUu2‰L‡B¡‰ÉÉÉÉáááñ¡¡¡“ÇÀŠ•ÝZ¯ \Lê·mkkoð{6šEÖf·[L^·ÃÌ œÝa3ˆÅ¢Õf¶[-¢ÇQäç’’ÌG#‰(ÇóÚôôlb&…‚±‘“g†F`ük"Y–-[¾\__ÿ@þoüu9}y½ÞÖ={öÜÐÝݽÍãñìòûýÝ---¾]»vYòöÑ£Gqçw" e¼;;;ñÝï~w]•—e@£££‰ .„dY&‰YUU§ggggÃáðÌäädàüùó³gϾ:<<|ÀÜj;Íë]¬kššš6555½d6›ëŠíþ Ã`ll쎳gÏþû2ÞÆÒÝÝ}Ù%—\rE{{{‹ÃáØêv»»ÜnwÝ©S§Üû÷ï7)Š’yðûýxàpÛm·-‚DQD£Q„Ãayrr2Ç£Š¢„5M ˲•$i.‘HÄ"‘H(‰„&''Ã.\;Ûßß? °ÚóX*T¬_Ø®®®Ç=Ï­¥v†a>ÓÛÛû*Aôù|?°Ùl7 mݺuèÝï~÷¨¢(‚Í04°ÇÙAàt]7›Íf6m§†atˆD"$™LJn·[Ný^'„h4*Y,I–eM×õ¨ÉdRB¡fµZã²,³‰D"ªiš  eI’$EQb³³³ñH$¢ÄãñH<ïï†¡Å­ûúwT¬SZ[[¿Q__ÿ7Å=0¯ÌÌÌ<ÐÛÛû•*Åä÷ûšL¦+Ó@:á÷²,"/Ò¥J,ï˜R“455ÝãõzÀfGìe/@ÀX„²,_˜žž®fÕOã¸^–eßKÓ1º®ƒeÙ­ üa˜ UUOUq ”P°Îhll¼Ãív“㸢Tù‚@QËÔÔÔ÷ª9&Y–G†±qwm¶0UÂÇqÜ»9ŽëPåšk~¡`QWW÷ß].×8޳Ûùó“uBÀqœ7‘HüA’¤ªªá²,?Ïóü ôä&§$ „° Ã\ÆqÜ{† êºÞ³x”J@À:Áãñ¼Íår=ÌqÜ‚‰ŠF×õWæææ^®ò5–eO¸bÎYß{†yÃ0×2 3C9[å1½î¡`àóùÞd³Ù~Æqœ¿ÔÎ_ìù?ý³®ëcÁ`ð÷Õ§ªª£†a®Ï_üyZA;!äýnàp ­D[¨XãØíöz‹Åò[AÚ»·” PE ƒUu )4M{ÀÕ©E^T¤„ ÀÍþ†çäñ•ã뉋ü ÔŒ(Šßç8î’rJyeWxÊ~a˜0vÚ•@Så.MÓÅÆW"©è6?ðŸÞ°Bã|]@5€5ŒÝn¿Ób±|¶Øï!$™Lþ" ÝH$~ Á0L7!ëž´!PPUõ€$I+4ôBH”rkj§/—N·ˆx©:C{}AÀÅb±l0™L3 ãÊÎ×u=Çïžžž¾_–å!I’ÎF£ÑÇ(ƒ6IDAT†9Îóü`ÍvÈIeñ9‹ÅªmÌæe—è^b;+ Û@'€§QÁȸ×#ô`Â0Ìý ÃlÌvÖ4MŽÇ㟠ƒ?Èo ŸˆF£÷ç{ÍœÇ/jC¨Ÿ°Pa À×¼ÀÛ| À Öx†  P^?˜ÍæÍ‡cÖår·ÛM<ñz½Äçó‡ÃQô‘ ¦¾¾þ…ÖÖV²qãF²qãF²iÓ&ÒÐÐPMoÀ…ø[ÌÇ_ç__+r¿Àw¤ë_÷ÂÐ$(ÕÖ „û!žüÝ?™L>‰D¾±XsI’ögÞ4MVG€o8Xäu†á/Ÿ9wø0€i[ìpEµH¡Ô ‚ \i2™¢‹…X­Vb³ÙˆÝn'V«õÊ´ä›Íæ7766’ÆÆFÒÔÔDš››I]]Ýj†²î‘ ){÷.£Ý5FS÷ž,ª¬1t]ÿ2!Ä–÷Ü×uý”™h"™L’$i6ûHa7ouG_’ÃòmåÔÀ‡`Ø6ø1VoJÕù0˲:Çq„çy"E‘ð<ÿOKíÈår½èóùˆßï'~¿ŸÔÕÕA.¯Æ ËÄ `óÀèÚ¾ÆÑ á+@O·(ë€ÓÃ0„eYÂqaYö(ŒÔfKÂn·ÿÙãñd ˆ^¯—ð<UÅG½4>‹y Ãð,—/eµý‡ŠŒBYeþ…VrÀ­Ó™Íf;èt:‰Ëå"éÓ„UÖ@p óó{ïÚ2~›j7î@¡¬ <FP(~r±šÍæ¡´Ñáp»Ý®h­Èh—ǘŸßb'ùt˜Hµ=„ʾ¡PV/¢pñÏÂøÀ_ f³YµX,$}š`±XÆ*4ÖåR#¹&p1U‘îÆüߨìyJ­â†qÄ•/–º;fà8HDQ$&“‰˜L&"Â’JJU™1GÀRK ³Ž¤Ú÷Á°P(k–/ pñh¸ØY–ý—ôIBú4çù¬Ìp+Â0ïé÷oÑþvƒÀx¤ PÖ$v¾ïù`¹Y|_FÖi˲ €ÝËì³ÒüÆ\g\L9¡˜w(¢õê)k’Ï£pñO¨[FŸo€qzÝçsËfU¸€ c|ÿ|íïÀüüþº‚ã¢PVŒgØŠ=û§ø»"}V³.ÀrøŒñMؼĶ«M+æ Žci=óG¨ý Áo”5€ ¹î°é«Õ{^*Òom•é-ÎÇ1¬·ÔcÁßc~®å6M¡¬$ÂEÆòS_]#·~v¿}X;»bÚ ¨¸s í²í4HˆRÓðN¢P\Œ3L>ß.ÒoUkV˜Ì«óa”¿›g;Rí¯ÊÈ(” ñ.Ò8–Ÿé†Cá‘¢†Úµþ—âm˜ûŸp}m²})h©1JMó ÀO+ÐïíEú}±ý®Ù~ ãÞ±Èýßɺ?ÀRÕÑQ(I3 óâI*‘¤ã €/T ßÕ€ðrõçQÜ–Áˆ(Ìž÷¦•&…²4îBá"ýCúm€q„˜Ýo Æ3õZ…ðsäÎéÏ0Rƒeû4|…Ó+Wr J¹ü …ÖOV ß{‹ô[õJÀ+€ 󀲯30*ÂüÑaú’a$W¡PjŽÈý°ø*Ðï³(\$¯@¿µ#›pþB/u•“jœBYq|0v§ìëÿ«@¿{PùwF˜ñzâoaä\LübµH¡,ÄÕ(ü°VÂE·ØÙÿÅÕ¬ÞÃPjñDZôìBÊŠð~`ÿn™}ÚP˜HT‡ ³^a| ÀSÈÕ¨Æ|tÇE¡,H±sú{—Ùç'Šô¹’å¿W›-0NÞ ÀµÊc©YhÚäÚ@*òZ|™}¾¯Èk¿^fŸk‰¾ÔEY€µ²Þ)V¯iýÝ #ø'›Yÿ±Œ>)ë*jƒÓ0¬õÙlXFCa’?Àp¥P(5È9ä>¯?u‘ý¼…GŠ 5 …RC<ÜE;p‘ýd'ÀH_OVb€ ¥z|¹‹VÅÒýõߎ¤Æ)…B©aŠå\ÊÂ*ø‘wDm'ý¤¬"ÔX;KV±e íïpEÞkÀ·R_)J ãÅ|üôUî±ÝN5òwÿÿ¬ü0)JµÈÏøBmDCÅ|ß÷Tg˜ ¥<ŽÜE<…Ž5ÿ…‹¿RÁD eI?¯g_ e°¹ óI2ó«:R …RqŠðÜUâÞvÕnòï—Aý(”5ÉQ¸ Xä>?€#Eî% 5ð(”5 #mUö‚îG®Àú_lñà\ÁñR(” ó+.ì¿JýN€Q' Øâ‚úûS(kž;Q¸¸Gü#€E~—v¾g5K¡P*KŒã¿b ½Ôõ­U)…B© ÅŽK]¿‘›B¡¬6ÁPû[üàX!R(”jòI”^üÿ ~ÕFG¡PªÎÿB¡£Ï¿‚îü” @ãÄkŸ>   FUŸ_ÃPý) …B¡P( …B¡P( …BY˜ÿ(•§®M÷üIEND®B`‚(0` €% P¡á¤ÙÅdÎá4¬Œo/¹ ÔRÙŒ ßÅ/Gd¤yŒ_‘z_àÙqv{ž á °2X ßcAâ“æpe?###õ)))ü)))¾?ù½2d… !Mdj Wtz D\b 9PT###X777þ222DDD]]]5557###á```¹¤¤¤pÒÒÒnÑÑÑjÍÍÍdÄÄÄ]­­­Svvv>¹¹¹/~~~!999@@@€€€"$^kp?ILz–3g{Pr‰{š½Ç¦—¼ÆÆ©ÔßÝ¡ÏÚë§Ùåó¤Úåõ ×ãó’ÈÓèŽÆÑÓ€¶Á¦SxL/KP'''???ÙmmmºÆÆÆ¿æææêñññüjjjÿÔÔÔÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþÿýýýþöööùëëëîêêêÛÆÆÆ¼ººº—¹¹¹mFFF  e]ns±Åâì÷ÆåðüÇéôÿÃèôÿÀèóÿ¼çóÿ¹æòÿ¶åñÿ²åñÿ¯äðÿ«ãïÿ¨âïÿ¤âîÿ¡áíÿžàíÿŒÍÛÿY›ä3:ÌÆ*+,í£¤¤ÿÿÿÿÿ÷÷÷ÿrrrÿ¤¤¤ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúúúÿfgg  ! m+RRÿ‹ §ÿÍëöÿÊêõÿÇéõÿÃèôÿÀèóÿ¼çóÿ¹æòÿ¶åñÿ²åñÿ¯äðÿ«ãïÿ¨âïÿ¤âîÿ¡áíÿžàíÿŒÎÜÿf£´ÿ@w‹ÿ?Pÿ"+ÿINRÿòòòÿzzzÿiiiÿùùùÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúúúÿv}}ÿ>]]÷//˜5VVÿ¨¾ÇÿÍëöÿÊêõÿÇéõÿÃèôÿÀèóÿ¼çóÿ¹æòÿ¶åñÿ²åñÿ¯äðÿ«ãïÿ¨âïÿ¤âîÿ¡áíÿžàíÿŒÎÜÿf£´ÿ@xŒÿNdÿ.Cÿ*5ÿjjjÿbbbÿÔÔÔÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿùùùÿ{ÿIjjÿ --£'FFÿ»ÓÝÿÍëöÿÊêõÿÇéõÿÃèôÿÀèóÿ¼çóÿ¹æòÿ¶åñÿ²åñÿ¯äðÿ«ãïÿ¨âïÿ¤âîÿ¡áíÿžàíÿŒÎÜÿf£´ÿ@xŒÿNdÿ/Eÿ!+ÿWWWÿ–––ÿþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷÷÷ÿrzzÿ5ZZÿ'(§56ÿÄÝçÿÍëöÿÊêõÿÇéõÿÃèôÿÀèóÿ¼çóÿ¹æòÿ¶åñÿ²åñÿ¯äðÿ«ãïÿ¨âïÿ¤âîÿ¡áíÿžàíÿŒÎÜÿf£´ÿ@xŒÿNdÿ/Eÿ=FJÿŠŠŠÿËËËÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõõõÿeixÿ1HûOÿÿÿð<Ÿÿÿÿð8ÿÿÿøxÿÿà<@à€€€€€€€€€€€€€€€?ÿÿÿÿÿÿÿÿÿ( @ €J€§ <DŸCD’S± §/f7)kz/«^W ¶ µR[\Q&&&ç)))ªÜxbpugy z’š‰¨±4Œ°¹S|Ÿ¨n‘¾É€†µ¿|a†^Prx49RV444ˆFFFšššXÁÁÁ–XXXÕÈÈÈèóóóÖòòòÔðððÏçççÇÏÏϵÕÕÕ¡ÑÑÑ€“““Z7ddd @i}ƒ§Ãßêä¿àìò¾äðü¼çóÿ·æòÿ²åñÿ­ãðÿ¨âïÿ£áîÿÞëÿqª¸ä&?H¶#')Ä›œœÿûûûÿ†††ÿàààÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìììùGMM_!FFÞ”«²ÿÌêõÿÇéõÿÁèôÿ¼çóÿ·æòÿ²åñÿ­ãðÿ¨âïÿ£áîÿßìÿy¸Èÿ@xŒÿ6IÿOÿ07Qÿ*1Oÿ%+Qÿ!'Sþ#Sý NùEù9þbf„ÿeeþKK‰ý==‹ý11Œþ33þ>>‘þLL—ÿTT™ÿ]]œÿtt¤ÿ„„§ÿ™™­ÿ¬¬´ÿµµµÿ›¢¢ÿ%%Î11å'9:Ò =œUFd`YKFi=\6C)a#P-+/)F=Z!RS[!a#;%D*Y4j=PFm\Yl VŠ6PTÙTmmä  ''ÿñÿÿÿðÿÿÿðþÿÿò|ÿðxÿøpÿÿøpÿÿüaÿà€?ÿÿü(  @5pD5AEb, CSWfƒŠp—Ÿ HejV)))‡555¢'''<ÅÅÅ···||| ##{“™•µÔßȱÚåâ­ßêñ¥Þêó‘ÏÜß7T]£ƒ…‡é©©©÷øøøþþþþþýýýýûûûùõõõíÐÑÑÆ 12L’­µÿÇéõÿ¼çóÿ²åñÿ¨âïÿ™ÛèÿAxŒÿ1CMÿÛÛÛÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÒÔÖÿ]F²ÈÿÇèôÿ¢ÁÊÿ–¹ÁÿžÐÜÿ™ÛèÿAxŒÿjxÿåååÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÊÊØÿaR£¹ÊÿÂãîÿ“­´ÿ¬Üçÿ–ÂÌÿ€ª²ÿAxŒÿª²ÿåååÿýýýÿvvvÿÔÔÔÿÿÿÿÿÿÿÿÿÉÉ×ÿae¥¼ËÿÇéõÿ¼çóÿ²åñÿ¦ßëÿl{ÿ@wŠÿ­¸¾ÿïïïÿþþþÿ‚‚‚ÿïïïÿÿÿÿÿÿÿÿÿÈÈÖÿZt¦½ËÿÆèôÿ—°·ÿvƒ†ÿmvxÿw–ÿAxŒÿ®ºÀÿåååÿôôôÿ‚‚‚ÿïïïÿÿÿÿÿÿÿÿÿÅÅÔÿd†¦¾Êÿ·ÔÝÿ|‹Žÿ¢ÌÖÿ£Ùåÿ™ÚçÿAxŒÿ®¹ÀÿñññÿÚÚÚÿ‚‚‚ÿïïïÿÿÿÿÿÿÿÿÿÂÂÐÿ`š§¾ÊÿÀàëÿŽ¥¬ÿ¯àìÿ—ÄÎÿ–ÕâÿAxŒÿ§³ºÿïïïÿäääÿÿïïïÿáááÿÿÿÿÿÁÁÐÿg«§¾ÊÿÇéõÿ³Úåÿ“´¼ÿ‰­µÿ˜ÚçÿAxŒÿ£°·ÿóóóÿ———ÿŽŽŽÿªªªÿ«««ÿÿÿÿÿÁÁÐÿiŧ¿ÊÿÇéõÿ»æòÿ°ãïÿ¦àíÿ˜ÙæÿAxŒÿ¢¯¶ÿÿÿÿÿþþþÿýýýÿýýýÿþþþÿÿÿÿÿÂÃÏÿ &7ð`nuýVbuôFSmí:Fgæ3@fß. 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ContentSpanFinder.h" #include "imageproc/SlicedHistogram.h" #include "Span.h" #include #include #include #include namespace Tests { using namespace boost::lambda; using namespace imageproc; BOOST_AUTO_TEST_SUITE(ContentSpanFinderTestSuite); BOOST_AUTO_TEST_CASE(test_empty_input) { ContentSpanFinder span_finder; std::vector spans; void (std::vector::*push_back) (const Span&) = &std::vector::push_back; span_finder.find( SlicedHistogram(), boost::lambda::bind(push_back, var(spans), _1) ); 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; void (std::vector::*push_back) (const Span&) = &std::vector::push_back; span_finder.find(hist, boost::lambda::bind(push_back, var(spans), _1)); 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; void (std::vector::*push_back) (const Span&) = &std::vector::push_back; span_finder.find(hist, boost::lambda::bind(push_back, var(spans), _1)); 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; void (std::vector::*push_back) (const Span&) = &std::vector::push_back; span_finder.find(hist, boost::lambda::bind(push_back, var(spans), _1)); // 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(); } // namespace Tests scantailor-RELEASE_0_9_12_2/tests/TestMatrixCalc.cpp000066400000000000000000000052771271170121200222270ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "MatrixCalc.h" #include #include namespace imageproc { namespace tests { BOOST_AUTO_TEST_SUITE(MatrixCalcSuite); BOOST_AUTO_TEST_CASE(test1) { static double const A[] = { 1, 1, 1, 2, 4, -3, 3, 6, -5 }; static double const B[] = { 9, 1, 0 }; static double const control[] = { 7, -1, 3 }; double x[3]; 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); } } BOOST_AUTO_TEST_CASE(test2) { static double const A[] = { 1, 1, 1, 2, 4, -3, 3, 6, -5, 3, 5, -2, 5, 10, -8 }; double B[] = { 9, 1, 0, 10, 1 }; static double const control[] = { 7, -1, 3 }; double x[3]; 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); } // 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 double const A[] = { 1, 3, 1, 1, 1, 2, 2, 3, 4 }; static double const control[] = { 2, 9, -5, 0, -2, 1, -1, -3, 2 }; double inv[9]; 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); } } BOOST_AUTO_TEST_CASE(test4) { static double const A[] = { 4, 1, 9, 6, 2, 8, 7, 3, 5, 11, 10, 12 }; static double const B[] = { 2, 9, 5, 12, 8, 10 }; static double const control[] = { 85, 138, 86, 158, 69, 149, 168, 339 }; double mul[8]; 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); } } BOOST_AUTO_TEST_SUITE_END(); } // namespace tests } // namespace imageproc scantailor-RELEASE_0_9_12_2/tests/TestSmartFilenameOrdering.cpp000066400000000000000000000046331271170121200244140ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "SmartFilenameOrdering.h" #include #include #ifndef Q_MOC_RUN #include #endif namespace Tests { BOOST_AUTO_TEST_SUITE(SmartFilenameOrderingTestSuite); BOOST_AUTO_TEST_CASE(test_same_file) { SmartFilenameOrdering const less; QFileInfo const somefile("/etc/somefile"); BOOST_CHECK(!less(somefile, somefile)); } BOOST_AUTO_TEST_CASE(test_dirs_different) { SmartFilenameOrdering const less; QFileInfo const lhs("/etc/file"); QFileInfo const 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) { SmartFilenameOrdering const less; QFileInfo const lhs("/etc/1.png"); QFileInfo const rhs("/etc/2.png"); BOOST_CHECK(less(lhs, rhs) == true); BOOST_CHECK(less(rhs, lhs) == false); } BOOST_AUTO_TEST_CASE(test_avg_case) { SmartFilenameOrdering const less; QFileInfo const lhs("/etc/a_0002.png"); QFileInfo const rhs("/etc/a_1.png"); BOOST_CHECK(less(lhs, rhs) == false); BOOST_CHECK(less(rhs, lhs) == true); } BOOST_AUTO_TEST_CASE(test_compex_case) { SmartFilenameOrdering const less; QFileInfo const lhs("/etc/a10_10.png"); QFileInfo const rhs("/etc/a010_2.png"); BOOST_CHECK(less(lhs, rhs) == false); BOOST_CHECK(less(rhs, lhs) == true); } BOOST_AUTO_TEST_CASE(test_almost_equal) { SmartFilenameOrdering const less; QFileInfo const lhs("/etc/10.png"); QFileInfo const rhs("/etc/010.png"); BOOST_CHECK(less(lhs, rhs) == false); BOOST_CHECK(less(rhs, lhs) == true); } BOOST_AUTO_TEST_SUITE_END(); } // namespace Tests scantailor-RELEASE_0_9_12_2/tests/main.cpp000066400000000000000000000016251271170121200202550ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2008 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifdef HAVE_CONFIG_H #include #endif #define BOOST_AUTO_TEST_MAIN #include scantailor-RELEASE_0_9_12_2/translations/000077500000000000000000000000001271170121200202005ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/translations/crashreporter_bg.ts000066400000000000000000000104751271170121200241120ustar00rootroot00000000000000 CrashReportDialog This file contains the internal state of the program when it crashed Този файл Ñъдържа вътрешното ÑÑŠÑтоÑние на програмата по време на Ñрива Dump file Дъмп-файл Sending ... Изпращане ... Unexpected response (code %1) from dispatcher Ðеочакван отговор (код %1) от диÑпечера Unexpected response from dispatcher Ðеочакван отговор от диÑпечера Unexpected response (code %1) from the receiving side Ðеочакван отговор (код %1) от приемащата Ñтрана Successfully sent Изпращането е уÑпешно Crash Report Отчет за Ñрива Scan Tailor crashed. That's usually caused by errors in the software. You can help us find and fix the problem by submitting this crash report. Scan Tailor блокира. Обикновено това е предизвикано от грешка в програмата. Можете да ни помогнете да поправим проблема като изпратите този отчет. Information to be sent ИнформациÑ, коÑто ще бъде изпратена If you have additional information, for example you know how to reproduce the crash, please provide it here. English or Russian please. Ðко имате допълнителна Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ - например как да Ñе възпроизведе грешката - напишете Ñ Ñ‚ÑƒÐº. МолÑ, използвайте руÑки или английÑки език. If you are willing to provide further assistance to developers to help fix this problem, please provide your email address. Ðко Ñте ÑъглаÑни да оÑигурите допълнителна помощ за разработчиците при поправÑне на проблема, молÑ, напишете адреÑа на електронната ви поща. Email [optional] Email [незадължителен] scantailor-RELEASE_0_9_12_2/translations/crashreporter_cs.ts000066400000000000000000000073041271170121200241240ustar00rootroot00000000000000 CrashReportDialog This file contains the internal state of the program when it crashed Tento soubor obsahuje stav programu, kdy doÅ¡lo k havárii. Dump file Vypsat soubor Sending ... Odesílám... Unexpected response (code %1) from dispatcher DispeÄer hlásí neoÄekávaný kód %1. Unexpected response from dispatcher NeoÄekávaná odpovÄ›Ä od dispeÄera. Unexpected response (code %1) from the receiving side NeoÄekávaná odpovÄ›Ä (kód %1) od příjemce. Successfully sent UspěšnÄ› odesláno Crash Report Hlášení o havárii Scan Tailor crashed. That's usually caused by errors in the software. You can help us find and fix the problem by submitting this crash report. Scan Tailor havaroval. To je obvykle způsobeno chybou v programu. Můžete nám pomoci chybu opravit odesláním tohoto hlášení o havárii. Information to be sent Informace k odeslání If you have additional information, for example you know how to reproduce the crash, please provide it here. English or Russian please. Pokud máte nÄ›jaké další informace (napÅ™. víte jak havárii zopakovat) napiÅ¡te to zde (Anglicky nebo Rusky). If you are willing to provide further assistance to developers to help fix this problem, please provide your email address. Pokud jste ochotni asistovat vývojářum pÅ™i opravÄ› tohoto problému, napiÅ¡te sem svoji emailovou adresu. Email [optional] Email (volitelnÄ›) scantailor-RELEASE_0_9_12_2/translations/crashreporter_de.ts000066400000000000000000000076251271170121200241150ustar00rootroot00000000000000 CrashReportDialog This file contains the internal state of the program when it crashed Diese Datei enthält den internen Status des Programms, wenn es abstürzt. Dump file Speicherauszug-Datei Sending ... Senden ... Unexpected response (code %1) from dispatcher Unerwartete Antwort (Code %1) vom für das Senden zuständigen Programm Unexpected response from dispatcher Unerwartete Antwort vom für das Senden zuständigen Programm Unexpected response (code %1) from the receiving side Unerwartete Antwort (Code %1) von der empfangenden Seite Successfully sent Erfolgreich versandt Crash Report Absturzbericht Scan Tailor crashed. That's usually caused by errors in the software. You can help us find and fix the problem by submitting this crash report. Scan Tailor ist abgestürzt. Dies wird gewöhnlich von Fehlern in der Software verursacht. Sie können uns helfen diese zu finden und zu beseitigen indem Sie uns diesen Absturzbericht zusenden. Information to be sent Zu sendende Informationen If you have additional information, for example you know how to reproduce the crash, please provide it here. English or Russian please. Wenn Sie zusätzlich Informationen haben, zum Beispiel wissen, wie der Absturz nachvollzogen werden kann, stellen Sie sie hier zur Verfügung. Bitte in Englisch oder Russisch. If you are willing to provide further assistance to developers to help fix this problem, please provide your email address. Wenn Sie bereit sind den Entwickler weitere Unterstützung zukommen zu lassen, um dieses Problem zu beheben, dann geben Sie bitte ihre E-Mail-Adresse an. Email [optional] E-Mail [optional] scantailor-RELEASE_0_9_12_2/translations/crashreporter_es.ts000066400000000000000000000074201271170121200241250ustar00rootroot00000000000000 CrashReportDialog This file contains the internal state of the program when it crashed Este archivo contiene el estado interno del programa cuando ha dejado de responder Dump file Archivo basura Sending ... Enviando ... Unexpected response (code %1) from dispatcher Respuesta inesperada (código %1) al enviar Unexpected response from dispatcher Respuesta inesperada al enviar Unexpected response (code %1) from the receiving side Respuesta inesperada (código %1) del receptor Successfully sent Enviado con éxito Crash Report Informe del incidente Scan Tailor crashed. That's usually caused by errors in the software. You can help us find and fix the problem by submitting this crash report. Scan Tailor dejó de responder. Probablemente debido por errores en el programa. Puedes ayudarnos a arreglar el problema enviándonos este informe del incidente. Information to be sent Información a ser enviada If you have additional information, for example you know how to reproduce the crash, please provide it here. English or Russian please. Si posee alguna información adicional, por ejemplo, sabe como reproducir el fallo, por favor documéntelo aqui, sea en inglés o ruso. If you are willing to provide further assistance to developers to help fix this problem, please provide your email address. Si está dispuesto a dar asistencia a los programadores para solucionar este error, por favor dénos su dirección de correo electrónico. Email [optional] Correo electrónico [opcional] scantailor-RELEASE_0_9_12_2/translations/crashreporter_fr.ts000066400000000000000000000076461271170121200241370ustar00rootroot00000000000000 CrashReportDialog This file contains the internal state of the program when it crashed Ce fichier contient la représentation de l'état interne du programme au moment ou le crash s'est-produit Dump file Fichier de vidage (Dump File) Sending ... Envoi ... Unexpected response (code %1) from dispatcher Réponse inattendue (code %1) en provenance du dispatcher Unexpected response from dispatcher Réponse inattendue en provenance du dispatcher Unexpected response (code %1) from the receiving side Réponse inattendue (code %1) du côté de la réception Successfully sent Envoi réussi Crash Report Rapport de plantage Scan Tailor crashed. That's usually caused by errors in the software. You can help us find and fix the problem by submitting this crash report. Scan Tailor a crashé. Ceci est habituellement dû à la présence d'erreurs dans le programme. Vous pouvez nous aider à trouver et fixer le problème en nous envoyant ce rapport de plantage. Information to be sent Informations à transmettre If you have additional information, for example you know how to reproduce the crash, please provide it here. English or Russian please. Si vous avez des informations complémentaires, par exemple si vous savez comment reproduire le plantage du programme, merci de l'indiquer ici. (En anglais ou en russe SVP). If you are willing to provide further assistance to developers to help fix this problem, please provide your email address. Si vous souhaitez apporter plus d'aide aux programmeurs pour régler ce problème, merci d'indiquer votre adresse email ci-dessous. Email [optional] Email [optionnel] scantailor-RELEASE_0_9_12_2/translations/crashreporter_hr.ts000066400000000000000000000073451271170121200241350ustar00rootroot00000000000000 CrashReportDialog Sending ... Slanje... Crash Report Izvješće o ruÅ¡enju Scan Tailor crashed. That's usually caused by errors in the software. You can help us find and fix the problem by submitting this crash report. Scan Tailor se sruÅ¡io. To je obiÄno uzrokovano greÅ¡kama u programu. Možete nam pomoći pronaći i rijeÅ¡iti problem slanjem ovog izvješće. Unexpected response (code %1) from the receiving side NeoÄekivani odgovor (kod %1) od strane primatelja Dump file Dump datoteka Email [optional] e-poÅ¡ta [po izboru] If you have additional information, for example you know how to reproduce the crash, please provide it here. English or Russian please. Ukoliko imate dodatne informacije, na primjer znate kako objasnitii ruÅ¡enje, molimo Vas da dodate ovdje. Engleski ili ruski molim. Unexpected response (code %1) from dispatcher NeoÄekivani odgovor (kod %1) od strane primatelja Unexpected response from dispatcher NeoÄekivani odgovor od strane primatelja Information to be sent Informacije koje će se poslati If you are willing to provide further assistance to developers to help fix this problem, please provide your email address. Ako hoćete pružiti daljnju pomoć programerima koja bi pomogla rijeÅ¡iti ovaj problem, molimo navedite vaÅ¡u email adresu. This file contains the internal state of the program when it crashed Ova datoteka sadrži unutarnje stanje programa kada se sruÅ¡io Successfully sent UspjeÅ¡no poslano scantailor-RELEASE_0_9_12_2/translations/crashreporter_it.ts000066400000000000000000000074231271170121200241350ustar00rootroot00000000000000 CrashReportDialog This file contains the internal state of the program when it crashed Questo file contiene lo stato interno del programma nel momento in cui si è chiuso Dump file Дъмп-файл Sending ... Invio in corso ... Unexpected response (code %1) from dispatcher Risposta imprevista (codice %1) dal dispatcher Unexpected response from dispatcher Risposta imprevista dal dispatcher Unexpected response (code %1) from the receiving side Risposta imprevista (codice %1) lato server Successfully sent Inviato con successo Crash Report Crash Report Scan Tailor crashed. That's usually caused by errors in the software. You can help us find and fix the problem by submitting this crash report. Scan Tailor è andato in crash. Ciò è usualmente causato da errori nel programma. Puoi aiutarci a trovare il problema e a risolverlo inviando questo rapporto sul crash. Information to be sent Informazioni da inviare If you have additional information, for example you know how to reproduce the crash, please provide it here. English or Russian please. Se hai ulteriori informazioni, ad esempio se sai come riprodurre il crash, forniscile gentilmente qui. In inglese o in russo, grazie. If you are willing to provide further assistance to developers to help fix this problem, please provide your email address. Se siete disposti a fornire ulteriore assistenza agli sviluppatori per aiutare a risolvere questo problema, fornite il vostro indirizzo e-mail. Email [optional] Email [opzionale] scantailor-RELEASE_0_9_12_2/translations/crashreporter_ja.ts000066400000000000000000000077731271170121200241230ustar00rootroot00000000000000 CrashReportDialog This file contains the internal state of the program when it crashed ã“ã®ãƒ•ァイルã¯ã‚¯ãƒ©ãƒƒã‚·ãƒ¥æ™‚ã®ãƒ—ログラムã®å†…部状態をå«ã‚“ã§ã„ã¾ã™ Dump file ダンプファイル Sending ... é€ä¿¡ä¸­... Unexpected response (code %1) from dispatcher 予期ã—ãªã„å応 (code %1) ãŒãƒ‡ã‚£ã‚¹ãƒ‘ッãƒãƒ£ã‹ã‚‰è¿”ã£ã¦ãã¾ã—ãŸã€‚ Unexpected response from dispatcher 予期ã—ãªã„å応ãŒãƒ‡ã‚£ã‚¹ãƒ‘ッãƒãƒ£ã‹ã‚‰è¿”ã£ã¦ãã¾ã—ãŸã€‚ Unexpected response (code %1) from the receiving side 予期ã—ãªã„å応 (code %1) ãŒå—ä¿¡å…ˆã‹ã‚‰è¿”ã£ã¦ãã¾ã—ãŸã€‚ Successfully sent é€ä¿¡æˆåŠŸ Crash Report クラッシュ報告 Scan Tailor crashed. That's usually caused by errors in the software. You can help us find and fix the problem by submitting this crash report. Scan Tailor ã¯ã‚¯ãƒ©ãƒƒã‚·ãƒ¥ã—ã¾ã—ãŸã€‚通常ã“れã¯ã‚½ãƒ•トウェアã®ã‚¨ãƒ©ãƒ¼ã«ã‚ˆã‚‹ã‚‚ã®ã§ã™ã€‚ã“ã®ã‚¯ãƒ©ãƒƒã‚·ãƒ¥å ±å‘Šã‚’é€ã‚‹ã“ã¨ã§å•題ã®ç©¶æ˜Žã¨è§£æ±ºã®æ‰‹åŠ©ã‘ã«ãªã‚Šã¾ã™ã€‚ Information to be sent é€ä¿¡ã•れる情報 If you have additional information, for example you know how to reproduce the crash, please provide it here. English or Russian please. ã‚‚ã—追加情報(例ãˆã°ã‚¯ãƒ©ãƒƒã‚·ãƒ¥ãŒå†ç¾ã•れる状æ³ãªã©ï¼‰ãŒã‚ã‚‹å ´åˆã¯ã“ã“ã«è¨˜å…¥ã—ã¦ãã ã•ã„。英語ã‹ãƒ­ã‚·ã‚¢èªžã§ãŠé¡˜ã„ã—ã¾ã™ã€‚ If you are willing to provide further assistance to developers to help fix this problem, please provide your email address. ã‚‚ã—å•題解決ã®ãŸã‚ã«é–‹ç™ºè€…ã¸ã®æ›´ãªã‚‹å”力をã„ãŸã ã‘ã‚‹å ´åˆã¯ã€ã‚ãªãŸã®é›»å­ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã‚’記入ã—ã¦ãã ã•ã„。 Email [optional] é›»å­ãƒ¡ãƒ¼ãƒ«ï¼ˆä»»æ„) scantailor-RELEASE_0_9_12_2/translations/crashreporter_pl.ts000066400000000000000000000072451271170121200241360ustar00rootroot00000000000000 CrashReportDialog This file contains the internal state of the program when it crashed Ten plik zawiera wewnÄ™trzny stan programu podczas awarii Dump file Zapisz plik Sending ... WysyÅ‚anie... Unexpected response (code %1) from dispatcher Nieoczekiwana odpowiedź (kod %1) Unexpected response from dispatcher Nieoczekiwana odpowiedź Unexpected response (code %1) from the receiving side Nieoczekiwana odpowiedź (kod %1) od odbiorcy Successfully sent WysyÅ‚anie powiodÅ‚o siÄ™ Crash Report Raport awarii Scan Tailor crashed. That's usually caused by errors in the software. You can help us find and fix the problem by submitting this crash report. NastÄ…piÅ‚a awaria programu. Zazwyczaj powodem awarii sÄ… błędy programu. Możesz nam pomóc znaleźć i naprawić problem wysyÅ‚ajÄ…c ten raport. Information to be sent Informacje do wysÅ‚ania If you have additional information, for example you know how to reproduce the crash, please provide it here. English or Russian please. JeÅ›li posiadasz dodatkowe informacje, na przykÅ‚ad jak wywoÅ‚ać tÄ™ awariÄ™, podaj je tutaj (po angielsku lub rosyjsku). If you are willing to provide further assistance to developers to help fix this problem, please provide your email address. JeÅ›li chcesz dodatkowo pomóc twórcom w rozwiÄ…zaniu tego problemu, podaj swój adres e-mail. Email [optional] E-mail (opcjonalnie) scantailor-RELEASE_0_9_12_2/translations/crashreporter_pt_BR.ts000066400000000000000000000072661271170121200245340ustar00rootroot00000000000000 CrashReportDialog This file contains the internal state of the program when it crashed Este arquivo contem o estado interno do programa quando este trava Dump file Arquivo de dump Sending ... Enviando ... Unexpected response (code %1) from dispatcher Resposta inesperada (codigo %1) ao enviar Unexpected response from dispatcher Resposta inesperada ao enviar Unexpected response (code %1) from the receiving side Resposta inesperada (codigo %1) do receptor Successfully sent Enviado com sucesso Crash Report Relatório de travamento Scan Tailor crashed. That's usually caused by errors in the software. You can help us find and fix the problem by submitting this crash report. Scan Tailor travou. Isso seguramente é algum erro no programa.Você pode ajudar a resolver o problema enviando este relatório. Information to be sent Informação a ser enviada If you have additional information, for example you know how to reproduce the crash, please provide it here. English or Russian please. Se você tem alguma informação adicional, por exemplo sabe como reproducir o travamento, por favor documente aqui em Inglês ou Russo. If you are willing to provide further assistance to developers to help fix this problem, please provide your email address. Se você esta disposto a dar assistencia aos programadores para solucionar o erro, por favor informe seu email. Email [optional] Email [opcional] scantailor-RELEASE_0_9_12_2/translations/crashreporter_ru.ts000066400000000000000000000135241271170121200241460ustar00rootroot00000000000000 CrashReportDialog This file contains the internal state of the program when it crashed Этот файл Ñодержит внутреннее ÑоÑтоÑние программы на момент Ð¿Ð°Ð´ÐµÐ½Ð¸Ñ Dump file Файл дампа Sending ... Отправка ... Unexpected response (code %1) from dispatcher Ðеожиданный ответ (код %1) раÑÐ¿Ñ€ÐµÐ´ÐµÐ»Ð¸Ñ‚ÐµÐ»Ñ Unexpected response from dispatcher Ðеожиданный ответ раÑÐ¿Ñ€ÐµÐ´ÐµÐ»Ð¸Ñ‚ÐµÐ»Ñ Unexpected response (code %1) from the receiving side Ðеожиданный ответ (код %1) от принимающей Ñтороны Sending failed Отправить не удалоÑÑŒ Successfully sent УÑпешно отправлено Crash Report Сообщение о падении Scan Tailor crashed. That's usually caused by errors in the software. You can help us find and fix this problem by submitting this crash report. Scan Tailor упал. Обычно Ñто результат ошибок в программе. Ð’Ñ‹ можете помочь нам найти и иÑправить Ñту проблему, отправив Ñтот отчет о падении. Scan Tailor crashed. That's usually caused by errors in the software. You can help us find and fix the problem by submitting this crash report. Scan Tailor упал. Обычно Ñто результат ошибок в программе. Ð’Ñ‹ можете помочь нам найти и иÑправить Ñту проблему, отправив Ñтот отчет о падении. Information to be sent ИнформациÑ, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ð±ÑƒÐ´ÐµÑ‚ отправлена If you have additional information, for example you know how to reproduce the crash, please provide it here. English or Russian please. ЕÑли у Ð²Ð°Ñ ÐµÑть Ð´Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ, например еÑли вы знаете, как воÑпроизвеÑти падение - пишите ее Ñюда. ПиÑать можно по-руÑÑки или по-английÑки. If you have additional information, for example you know how to reproduce the crash, please provide it here. ЕÑли у Ð²Ð°Ñ ÐµÑть Ð´Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ, например еÑли вы знаете, как воÑпроизвеÑти падение - пишите ее Ñюда. If you are willing to provide further assistance to developers to help fix this problem, please provide your email address. ЕÑли вы ÑоглаÑны оказать дополнительное ÑодейÑтвие разработчикам в поиÑке Ñтой проблемы, укажите Ñвой Ð°Ð´Ñ€ÐµÑ Ñлектронной почты. If you are willing to provide further assistance to developers to help fix this problem, plese provide your email address. ЕÑли вы ÑоглаÑны оказать дополнительное ÑодейÑтвие разработчикам в поиÑке Ñтой проблемы, укажите Ñвой Ð°Ð´Ñ€ÐµÑ Ñлектронной почты. Email [optional] Email [не обÑзательно] scantailor-RELEASE_0_9_12_2/translations/crashreporter_uk.ts000066400000000000000000000107001271170121200241300ustar00rootroot00000000000000 CrashReportDialog This file contains the internal state of the program when it crashed У цьому файлі міÑÑ‚ÑтьÑÑ Ð´Ð°Ð½Ñ– щодо Ñтану програми на Ñ‡Ð°Ñ Ð°Ð²Ð°Ñ€Ñ–Ð¹Ð½Ð¾Ð³Ð¾ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ Ñ€Ð¾Ð±Ð¾Ñ‚Ð¸ Dump file Файл дампу Sending ... ÐадÑиланнÑ… Unexpected response (code %1) from dispatcher Ðеочікувана відповідь (код %1) від диÑпетчера Unexpected response from dispatcher Ðеочікувана відповідь від диÑпетчера Unexpected response (code %1) from the receiving side Ðеочікувана відповідь (код %1) на боці отримувача Successfully sent УÑпішно надіÑлано Crash Report Звіт щодо аварії Scan Tailor crashed. That's usually caused by errors in the software. You can help us find and fix the problem by submitting this crash report. Scan Tailor завершив роботу у аварійному режимі. Зазвичай, причиною Ñ” помилки у програмі. Ви можете допомогти нам виÑвити Ñ– виправити проблему надÑиланнÑм звіту щодо вади. Information to be sent Дані, Ñкі буде надіÑлано If you have additional information, for example you know how to reproduce the crash, please provide it here. English or Russian please. Якщо у Ð²Ð°Ñ Ñ” додаткові дані, наприклад ви знаєте Ñк відтворити аварійне завершеннÑ, будь лаÑка, наведіть Ñ—Ñ… тут. СкориÑтайтеÑÑ Ð°Ð½Ð³Ð»Ñ–Ð¹Ñькою або роÑійÑькою мовами. If you are willing to provide further assistance to developers to help fix this problem, please provide your email address. Якщо ви бажаєте надати подальшу допомогу розробникам в уÑуванні вади, будь лаÑка, вкажіть адреÑу вашої електронної пошти. Email [optional] ÐдреÑа ел. пошти (необов’Ñзкова) scantailor-RELEASE_0_9_12_2/translations/crashreporter_untranslated.ts000066400000000000000000000063651271170121200262310ustar00rootroot00000000000000 CrashReportDialog This file contains the internal state of the program when it crashed Dump file Sending ... Unexpected response (code %1) from dispatcher Unexpected response from dispatcher Unexpected response (code %1) from the receiving side Successfully sent Crash Report Scan Tailor crashed. That's usually caused by errors in the software. You can help us find and fix the problem by submitting this crash report. Information to be sent If you have additional information, for example you know how to reproduce the crash, please provide it here. English or Russian please. If you are willing to provide further assistance to developers to help fix this problem, please provide your email address. Email [optional] scantailor-RELEASE_0_9_12_2/translations/crashreporter_zh_CN.ts000066400000000000000000000073101271170121200245150ustar00rootroot00000000000000 CrashReportDialog This file contains the internal state of the program when it crashed 这个文件中包å«äº†ç¨‹åºå´©æºƒæ—¶çš„内部状æ€ä¿¡æ¯ Dump file 转储文件 Sending ... 正在å‘é€â€¦â€¦ Unexpected response (code %1) from dispatcher 从调度器收到未预期的回å¤ï¼ˆä»£ç %1) Unexpected response from dispatcher ä»Žè°ƒåº¦å™¨æ”¶åˆ°æœªé¢„æœŸçš„å›žå¤ Unexpected response (code %1) from the receiving side 从接收端收到未预期的回å¤ï¼ˆä»£ç %1) Successfully sent å‘逿ˆåŠŸ Crash Report 崩溃报告 Scan Tailor crashed. That's usually caused by errors in the software. You can help us find and fix the problem by submitting this crash report. Scan Tailor已崩溃。这通常是由程åºä¸­çš„错误引起的。你å¯ä»¥é€šè¿‡æäº¤è¿™ä¸ªå´©æºƒæŠ¥å‘Šæ¥å¸®åŠ©æˆ‘ä»¬æ‰¾åˆ°å¹¶ä¿®å¤è¿™ä¸ªé—®é¢˜ã€‚ Information to be sent å°†è¦å‘é€çš„ä¿¡æ¯ If you have additional information, for example you know how to reproduce the crash, please provide it here. English or Russian please. 如果你还有更多信æ¯å¯ä»¥æä¾›ï¼Œä¾‹å¦‚怎么样æ“作å¯ä»¥é‡çŽ°è¿™ä¸ªå´©æºƒé—®é¢˜ï¼Œè¯·åœ¨æ­¤å†™å‡ºã€‚ 请使用英语或俄语。 If you are willing to provide further assistance to developers to help fix this problem, please provide your email address. å¦‚æžœä½ æ„¿æ„æä¾›æ›´å¤šçš„å¸®åŠ©ä»¥ä¾¿è®©å¼€å‘者修å¤è¿™ä¸ªé—®é¢˜ï¼Œè¯·ç•™ä¸‹ä½ çš„邮箱地å€ã€‚ Email [optional] 邮箱[å¯é€‰] scantailor-RELEASE_0_9_12_2/translations/crashreporter_zh_TW.ts000066400000000000000000000071711271170121200245540ustar00rootroot00000000000000 CrashReportDialog This file contains the internal state of the program when it crashed 檔案中包å«ç¨‹å¼ç•°å¸¸ä¸­æ–·æ™‚的內部狀態 Dump file 傾尿ª” Sending ... 傳é€ä¸­... Unexpected response (code %1) from dispatcher 分é€å‡ºç•°å¸¸å›žæ‡‰ (錯誤碼 %1) Unexpected response from dispatcher 分é€å‡ºç•°å¸¸å›žæ‡‰ Unexpected response (code %1) from the receiving side æŽ¥æ”¶ç«¯æœ‰æœªé æœŸçš„回應 (回應碼 %1) Successfully sent é€å‡ºæˆåŠŸ Crash Report 毀æå ±å‘Š Scan Tailor crashed. That's usually caused by errors in the software. You can help us find and fix the problem by submitting this crash report. Scan Tailor å·²ç¶“åœæ­¢åŸ·è¡Œ. 這å¯èƒ½æ˜¯å› ç‚ºè»Ÿé«”的錯誤所導致的. é€éŽéŒ¯èª¤å ±å‘Šçš„傳é€,您å¯ä»¥å¹«åŠ©æˆ‘å€‘ç™¼ç¾ä¸¦ä¿®å¾©é€™äº›éŒ¯èª¤. Information to be sent 將被傳é€çš„資訊 If you have additional information, for example you know how to reproduce the crash, please provide it here. English or Russian please. å‡å¦‚您有附檔, 例如如何é‡ç¾éŒ¯èª¤çš„éŽç¨‹, 請在這裡æä¾›. 請用英語或俄羅斯語. If you are willing to provide further assistance to developers to help fix this problem, please provide your email address. å¦‚æžœä½ é¡˜æ„æä¾›é€²ä¸€æ­¥çš„æ´åŠ©, 以幫助開發者解決此å•題, è«‹æä¾›æ‚¨çš„é›»å­éƒµä»¶åœ°å€. Email [optional] Email [鏿“‡æ€§] scantailor-RELEASE_0_9_12_2/translations/scantailor_bg.ts000066400000000000000000003411721271170121200233670ustar00rootroot00000000000000 AboutDialog About Scan Tailor За програмата Scan Tailor 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 recogrnition is out of scope of this project. Scan Tailor е интерактивен инÑтрумент за поÑÑ‚-обработка на Ñканирани Ñтраници. Той извършва операции като разрÑзване на Ñтраници, ÐºÐ¾Ñ€ÐµÐºÑ†Ð¸Ñ Ð½Ð° наклона, добавÑне/премахване на полета и др. Вие му подавате необработени Ñканове, а в резултат получавате Ñтраници, готови за печат или пакетиране в PDF или DJVU файлове. Сканирането и оптичното разпознаване на Ñимволи не влизат в обхвата на този проект. 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 ЙоÑиф Ðрцимович 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. Petr Kovář - Работа през ÐºÐ¾Ð¼Ð°Ð½Ð´Ð½Ð¸Ñ Ñ€ÐµÐ´. Neco Torquato - Brazilian Portuguese Svetoslav Sashkov, Mandor - Bulgarian Svetoslav Sashkov, Mandor - БългарÑки Petr Kovář - Czech Petr Kovář - Чешки Stefan Birkner - German Stefan Birkner - ÐемÑки Angelo Gemmi - Italian Angelo Gemmi - ИталианÑки Masahiro Kitagawa - Japanese Masahiro Kitagawa - ЯпонÑки Patrick Pascal - French Patrick Pascal - ФренÑки Daniel Koć - Polish Daniel Koć - ПолÑки Joseph Artsimovich - Russian Joseph Artsimovich - РуÑки Marián Hvolka - Slovak Marián Hvolka - Словашки Flavio Benelli - Spanish Flavio Benelli - ИÑпанÑки Davidson Wang - Traditional Chinese Davidson Wang - Традиционен китайÑки Yuri Chornoivan - Ukrainian Yuri Chornoivan - УкраинÑки denver 22 denver 22 <!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:8pt;">U235 - Picture auto-detection algorithm.</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:8pt;">Robert B. - First generation dewarping algorithm.</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:8pt;">Andrey Bergman - System load adjustment.</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:8pt;">Petr Kovář - Command line interface.</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:8pt;">U235 - Ðлгоритъм за автоматично откриване на картинки.</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:8pt;">Robert B. - Ðлгоритъм за премахване на геометрични изкривÑÐ²Ð°Ð½Ð¸Ñ (първо поколение).</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:8pt;">Andrey Bergman - ÐаÑтройка натоварването на ÑиÑтемата.</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:8pt;">Petr Kovář - Ð˜Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð½Ð° ÐºÐ¾Ð¼Ð°Ð½Ð´Ð½Ð¸Ñ Ñ€ÐµÐ´.</span></p></body></html> Translators Преводачи Svetoslav Sashkov, Mandor - Bulgarian Petr Kovář - Czech Stefan Birkner - German Angelo Gemmi - Italian Masahiro Kitagawa - Japanese Patrick Pascal - French Daniel Koć - Polish Joseph Artsimovich - Russian Marián Hvolka - Slovak Davidson Wang - Traditional Chinese СветоÑлав Сашков, Mandor - БългарÑки Petr Kovář - Чешки Stefan Birkner - ÐемÑки Angelo Gemmi - ИталианÑки Masahiro Kitagawa - ЯпонÑки Patrick Pascal - ФренÑки Daniel Koć - ПолÑки ЙоÑиф Ðрцимович - РуÑки Marián Hvolka - Словашки Davidson Wang - Традиционен китайÑки Svetoslav Sashkov, Mandor - Bulgarian Stefan Birkner - German Angelo Gemmi - Italian Masahiro Kitagawa - Japanese Patrick Pascal - French Joseph Artsimovich - Russian Davidson Wang - Traditional Chinese СветоÑлав Сашков, Mandor - БългарÑки Stefan Birkner - ÐемÑки Angelo Gemmi - ИталианÑки Masahiro Kitagawa - ЯпонÑки Patrick Pascal - ФренÑки ЙоÑиф Ðрцимович - РуÑки Davidson Wang - Традиционен китайÑки Contributors БлагодарноÑти U235 - Picture auto-detection algorithm. Robert B. - Dewarping algorithm. Andrey Bergman - System load adjustment. U235 - Ðлгоритъм за автоматично откриване на картинки. Robert B. - Ðлгоритъм за премахване на геометрични изкривÑваниÑ. Andrey Bergman - ÐаÑтройка натоварването на ÑиÑтемата. Documentation Ð”Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ denver 22 phaedrus Taxman denver 22 phaedrus Taxman License Лиценз BatchProcessingLowerPanel Form Form Beep when finished Звуков Ñигнал при завършване ColorPickupInteraction Click on an area to pick up its color, or ESC to cancel. Цъкнете върху облаÑÑ‚ за избор на цвета й или ESC за отказ. DeskewApplyDialog 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+Click / Shift+Click за да изберете множеÑтво Ñтраници. DeskewOptionsWidget Form Form Deskew КомпенÑÐ°Ñ†Ð¸Ñ Ð½Ð° наклона Auto Ðвтоматично Manual Ръчно Apply To ... Приложи към... DragHandler Unrestricted dragging is possible by holding down the Shift key. Чрез задържане на клавиша Shift е възможно неограничено влачене. ErrorWidget Form Form FixDpiDialog 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) Fix DPI Поправка на DPI Tab 1 Tab 1 Tab 2 Tab 2 DPI DPI Custom ПотребителÑки x x Apply Прилагане FixDpiSinglePageDialog Fix DPI Поправка на DPI DPI for %1 DPI за %1 Custom ПотребителÑки %1 (page %2) %1 (Ñтраница %2) 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 е прекалено малка за този размер в пикÑели. Такава ÐºÐ¾Ð¼Ð±Ð¸Ð½Ð°Ñ†Ð¸Ñ Ð²ÐµÑ€Ð¾Ñтно ще доведе до грешки в паметта. ImageViewBase Use the mouse wheel or +/- to zoom. When zoomed, dragging is possible. Използвайте колелцето на мишката или +/- за мащабиране. При увеличен изглед е възможно влачене. Unrestricted dragging is possible by holding down the Shift key. Чрез задържане на клавиша Shift е възможно неограничено влачене. InteractiveXSpline Click to create a new control point. Цъкнете, за да Ñъздадете нова контролна точка. This point can be dragged. Тази точка може да бъде влачена. Drag this point or delete it by pressing Del or D. ПремеÑтете тази точка или Ñ Ð¸Ð·Ñ‚Ñ€Ð¸Ð¹Ñ‚Ðµ чрез клавишите Del или D. 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. LoadFilesStatusDialog Some files failed to load ÐÑкои файлове не уÑпÑха да Ñе заредÑÑ‚ Loaded successfully: %1 УÑпешно заредени: %1 Failed to load: %1 ÐеуÑпешно заредени: %1 MainWindow Beep when finished Звуков Ñигнал при завършване Save Project Запазване на проект Save this project? Да Ñе запази ли този проект? 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. ПроектниÑÑ‚ файл е повреден. 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". ИзходниÑÑ‚ резулат вÑе още не е възможен, тъй като не Ñе знае крайниÑÑ‚ размер на Ñтраниците. За да го определите, изпълнете маÑова обработка на Ñтраниците в "Избор на Ñъдържание" или "Оформление на Ñтраница". Files to insert Skip failed files 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 "Page Layout". ИзходниÑÑ‚ резулат вÑе още не е възможен, тъй като не Ñе знае ÐºÑ€Ð°Ð¹Ð½Ð¸Ñ Ñ€Ð°Ð·Ð¼ÐµÑ€ на Ñтраниците. За да го определите, изпълнете маÑова обработка на Ñтраниците в "Избор на Ñъдържание" или "Оформление на Ñтраница". Unnamed Без име %2 - Scan Tailor %3 [%1bit] Error saving the project file! Грешка при запазване на Ð¿Ñ€Ð¾ÐµÐºÑ‚Ð½Ð¸Ñ Ñ„Ð°Ð¹Ð»! File to insert Файл за вмъкване Images not in project (%1) ИзображениÑ, които не Ñа в проекта (%1) Error opening the image file. Грешка при отварÑне на файла Ñ Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸ÐµÑ‚Ð¾. %1 (page %2) %1 (Ñтраница %2) Remove Премахване Unable to delete file: %1 Файлът не може да бъде изтрит: %1 MainWindow MainWindow Keep current page in view. Запазване на текущата Ñтраница в изгледа. Use PgUp, PgDown or Q, W to navigate between pages. Използвайте PgUp/PgDown или Q/W за Ð½Ð°Ð²Ð¸Ð³Ð°Ñ†Ð¸Ñ Ð¼ÐµÐ¶Ð´Ñƒ Ñтраниците. Use Home, End, PgUp (or Q), PgDown (or W) to navigate between pages. Използвайте Home, End, PgUp (или Q), PgDown (или W) за Ð½Ð°Ð²Ð¸Ð³Ð°Ñ†Ð¸Ñ Ð¼ÐµÐ¶Ð´Ñƒ Ñтраниците. Tools ИнÑтрументи File Файл Help Помощ Debug ПроÑледÑване на грешки Debug Mode ТеÑтов режим Ctrl+S Ctrl+S Save Project As ... Запазване на проекта като... Next Page Следваща Ñтраница PgDown PgDown Previous Page Предишна Ñтраница PgUp PgUp New Project ... Ðов проект... Ctrl+N Ctrl+N Open Project ... ОтварÑне на проект... Ctrl+O Ctrl+O Q Q W W Close Project ЗатварÑне на проект Ctrl+W Ctrl+W Quit Изход Ctrl+Q Ctrl+Q First Page Първа Ñтраница Home Home Last Page ПоÑледна Ñтраница End End About За програмата Fix DPI ... Поправка на DPI ... Relinking ... Свързване ... Stop batch processing Спиране на маÑовата обработка Settings ... ÐаÑтройки ... NewOpenProjectPanel Form Form New Project ... Ðов проект... Open Project ... ОтварÑне на проект... Recent Projects Скорошни проекти OrientationApplyDialog Fix Orientation Поправка на ориентациÑта Apply to Приложи към This page only (already applied) Само тази Ñтраница (вече е приложено) All pages Ð’Ñички Ñтраници This page and the following ones Тази и Ñледващите Ñтраници Every other page All odd or even pages, depending on the current page being odd or even. Ð’ÑÑка втора Ñтраница ÑпрÑмо текущата The current page will be included. Текущата Ñтраница ще бъде включена. Selected pages Избраните Ñтраници Use Ctrl+Click / Shift+Click to select multiple pages. Използвайте Ctrl+Click / Shift+Click за да изберете множеÑтво Ñтраници. Every other selected page Ð’ÑÑка втора избрана Ñтраница OrientationOptionsWidget Form Form Rotate Завъртане ... ... Reset Ðачално Scope Обхват Apply to ... Приложи към... OutOfMemoryDialog Out of memory ÐедоÑтиг на памет Out of Memory Situation in Scan Tailor ÐедоÑтиг на памет в 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. Поправете DPI за вÑички Ñтраници. Вижте как да <a href="http://vimeo.com/12524529">определите неизвеÑтно DPIs</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. Изненадващо, но увеличаването на RAM-а нÑма да помогне. ÐедоÑтигът на памет Ñе компенÑира от диÑÐºÐ¾Ð²Ð¸Ñ Ð±ÑƒÑ„ÐµÑ€, който Ð·Ð°Ð±Ð°Ð²Ñ Ð¸Ð·Ð¿ÑŠÐ»Ð½ÐµÐ½Ð¸ÐµÑ‚Ð¾, но държи програмата работоÑпоÑобна. СитуациÑта Ñ Ð½ÐµÐ´Ð¾Ñтига на памет означава, че е Ñвършило адреÑното проÑтранÑтво, което нÑма нищо общо Ñ Ð½Ð°Ð»Ð¸Ñ‡Ð½Ð°Ñ‚Ð° RAM. ЕдинÑтвениÑÑ‚ начин да увеличите адреÑното проÑтранÑтво е да използвате 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! Грешка при запазване на Ð¿Ñ€Ð¾ÐµÐºÑ‚Ð½Ð¸Ñ Ñ„Ð°Ð¹Ð»! 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+Click / Shift+Click за да изберете множеÑтво Ñтраници. OutputChangeDewarpingDialog Apply Dewarping Mode Прилагане режим на ÐºÐ¾Ñ€ÐµÐºÑ†Ð¸Ñ Mode Режим Off Без ÐºÐ¾Ñ€ÐµÐºÑ†Ð¸Ñ Auto (experimental) Ðвтоматично (екÑпериментален) Manual Ръчно Scope Обхват This page only Само тази Ñтраница All pages Ð’Ñички Ñтраници This page and the following ones Тази и Ñледващите Ñтраници Selected pages Избраните Ñтраници Use Ctrl+Click / Shift+Click to select multiple pages. Използвайте Ctrl+Click / Shift+Click за да изберете множеÑтво Ñтраници. OutputChangeDpiDialog Apply Output Resolution Избор на изходна разделителна ÑпоÑобноÑÑ‚ DPI DPI Scope Обхват This page only Само тази Ñтраница This page and the following ones Тази и Ñледващите Ñтраници Selected pages Избраните Ñтраници Use Ctrl+Click / Shift+Click to select multiple pages. Използвайте Ctrl+Click / Shift+Click за да изберете множеÑтво Ñтраници. All pages Ð’Ñички Ñтраници OutputOptionsWidget Form Form Output Resolution (DPI) Изходна разделителна ÑпоÑобноÑÑ‚ (DPI) Change ... Промени... Mode Режим White margins Бели граници Equalize illumination ИзравнÑване на оÑветеноÑтта Dewarping ÐšÐ¾Ñ€ÐµÐºÑ†Ð¸Ñ Ð½Ð° изкривÑÐ²Ð°Ð½Ð¸Ñ Depth perception Дълбочина No despeckling Без почиÑтване Dewarp ÐšÐ¾Ñ€ÐµÐºÑ†Ð¸Ñ Ð½Ð° деформации Apply To ... Приложи към... Despeckling ПочиÑтване на шум Cautious despeckling Предпазливо почиÑтване на шум ... ... Normal despeckling Ðормално почиÑтване на шум Aggressive despeckling ÐгреÑивно почиÑтване на шум Despeckle Премахване на шум Thinner По-тънко Thicker По-дебело PageLayoutApplyDialog Apply to Приложи към This page only (already applied) Само тази Ñтраница (вече е приложено) All pages Ð’Ñички Ñтраници This page and the following ones Тази и Ñледващите Ñтраници Every other page All odd or even pages, depending on the current page being odd or even. Ð’ÑÑка втора Ñтраница ÑпрÑмо текущата The current page will be included. Текущата Ñтраница ще бъде включена. Selected pages Избраните Ñтраници Use Ctrl+Click / Shift+Click to select multiple pages. Използвайте Ctrl+Click / Shift+Click за да изберете множеÑтво Ñтраници. Every other selected page Ð’ÑÑка втора избрана Ñтраница PageLayoutOptionsWidget Form Form Margins Граници Millimeters (mm) Милиметри (mm) Inches (in) Инчове (in) Top Отгоре ... ... Bottom Отдолу Left ОтлÑво Right ОтдÑÑно Apply To ... Приложи към... Alignment ПодравнÑване Match size with other pages ПреоразмерÑване ÑпрÑмо оÑтаналите Ñтраници Align with other pages ПодравнÑване Ñ Ð´Ñ€ÑƒÐ³Ð¸Ñ‚Ðµ Ñтраници Go to the widest page. Отиване към най-широката Ñтраница. Widest Page Ðай-широка Ñтраница Go to the tallest page. Отиване към най-Ñ‚ÑÑната Ñтраница. Tallest Page Ðай-Ñ‚ÑÑна Ñтраница PageSplitModeDialog Split Pages РазделÑне на Ñтраници Mode Режим Auto Ðвтоматично Manual Ръчно Scope Обхват This page only Само тази Ñтраница This page and the following ones Тази и Ñледващите Ñтраници Selected pages Избраните Ñтраници Use Ctrl+Click / Shift+Click to select multiple pages. Използвайте Ctrl+Click / Shift+Click за да изберете множеÑтво Ñтраници. All pages Ð’Ñички Ñтраници PageSplitOptionsWidget Form Form Page Layout Оформление на Ñтраница ? ? Change ... ПромÑна... Split Line Разделителна Ð»Ð¸Ð½Ð¸Ñ Auto Ðвтоматично Manual Ръчно ... ... PictureZonePropDialog Zone Properties СвойÑтва на зоната Subtract from all layers Изваждане от вÑички Ñлоеве Add to auto layer ДобавÑне към Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡Ð½Ð¸Ñ Ñлой Subtract from auto layer Изваждане от Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡Ð½Ð¸Ñ Ñлой ProjectFilesDialog Input Directory Входна папка Output Directory Изходна папка 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. ÐÑкои от файловете не уÑпÑха да Ñе заредÑÑ‚. Или Ñа в неподдържан формат, или Ñа повредени. ТрÑбва да ги махнете от проекта. Project Files Проектни файлове Browse Преглед 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;"> <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;">ДобавÑне на избраните файлове в проекта.</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;">Премахване на избраните Ñтраници от проекта.</p></body></html> << << Files In Project Файлове в проекта Right to left layout (for Hebrew and Arabic) Подредба отдÑÑно налÑво (за Иврит и ÐрабÑки) Fix DPIs, even if they look OK Поправка на DPI, дори и да изглежа наред ProjectOpeningContext Error Грешка Unable to interpret the project file. ПроектниÑÑ‚ файл не може да Ñе интерпретира. RelinkingDialog Relinking Свързване Undo Отказ ... ... Substitution File for %1 Substitution Directory for %1 This change would merge several files into one. RemoveFileDialog Remove File Премахване на файла Remove %1 from project? Премахване на %1 от проекта? Also delete the file from disk И изтриване на файла от диÑка RemovePagesDialog Remove Pages Премахване на Ñтраници Remove %1 page(s) from project? Премахване на %1 Ñтраница(и) от проекта? Corresponding output files will be deleted, while input files will remain. СъответÑтващите изходни файлове ще бъдат изтрити, но входните файлове ще оÑтанат. Files will remain on disk Файловете ще оÑтанат на диÑка SelectContentApplyDialog Select Content Избор на Ñъдържание Apply to Приложи към This page only (already applied) Само тази Ñтраница (вече е приложено) All pages Ð’Ñички Ñтраници This page and the following ones Тази и Ñледващите Ñтраници Every other page All odd or even pages, depending on the current page being odd or even. Ð’ÑÑка втора Ñтраница ÑпрÑмо текущата The current page will be included. Текущата Ñтраница ще бъде включена. Selected pages Избраните Ñтраници Use Ctrl+Click / Shift+Click to select multiple pages. Използвайте Ctrl+Click / Shift+Click за да изберете множеÑтво Ñтраници. Every other selected page Ð’ÑÑка втора избрана Ñтраница SelectContentOptionsWidget Form Form Content Box ОблаÑÑ‚ ÑÑŠÑ Ñъдържание Auto Ðвтоматично Manual Ръчно Scope Обхват Apply to ... Приложи към... SettingsDialog Compiled without OpenGL support. Компилирано без поддръжка на OpenGL. Your hardware / driver don't provide the necessary features. ВашиÑÑ‚ хардуер (или драйвер) нÑма нужните характериÑтики. Settings ÐаÑтройки Use 3D acceleration for user interface Използване 3D уÑкорение за потребителÑÐºÐ¸Ñ Ð¸Ð½Ñ‚Ñ€ÐµÑ„ÐµÐ¹Ñ StageListView Launch batch processing Стартиране на пакетна обработка SystemLoadWidget Form Form System load Ðатоварване на ÑиÑтемата ... ... Minimal Минимално Normal Ðормално ThumbnailSequence %1 (page %2) %1 (Ñтраница %2) ZoneContextMenuInteraction Properties СвойÑтва Delete Изтриване Delete confirmation Потвърждение за изтриване Really delete this zone? ÐаиÑтина ли да бъде изтрита тази зона? ZoneCreationInteraction Click to finish this zone. ESC to cancel. Цъкнете, за да завършите тази зона. ESC за отказ. Connect first and last points to finish this zone. ESC to cancel. Свържете първата и поÑледната точка, за да завършите тази зона. ESC за отказ. Zones need to have at least 3 points. ESC to cancel. Зоните трÑбва да имат поне 3 точки. ESC за отказ. ZoneDefaultInteraction Drag the vertex. Влачете върха. Click to create a new vertex here. Цъкнете, за да Ñъздадете нов връх тук. Right click to edit zone properties. ДÑÑно цъкане за редактиране ÑвойÑтвата на зоната. Click to start creating a new picture zone. Цъкнете, за да започнете Ñъздаването на нова зона за изображение. ZonePropertiesDialog Zone Properties СвойÑтва на зоната Subtract from all layers Изваждане от вÑички Ñлоеве Add to auto layer ДобавÑне към Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡Ð½Ð¸Ñ Ñлой Subtract from auto layer Изваждане от Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡Ð½Ð¸Ñ Ñлой ZoneVertexDragInteraction Merge these two vertices. Сливане на тези два върха. Move the vertex to one of its neighbors to merge them. ПремеÑтете върхът към един от ÑÑŠÑедите му, за да ги Ñлеете. deskew::Filter Deskew КомпенÑÐ°Ñ†Ð¸Ñ Ð½Ð° наклона deskew::ImageView Use Ctrl+Wheel to rotate or Ctrl+Shift+Wheel for finer rotation. Използвайте Ctrl+колелцето или Ctrl+Shift+колелцето за фино завъртане. Drag this handle to rotate the image. Влачете този манипулатор за да завъртите изображението. deskew::OptionsWidget Apply Deskew Приложи корекции fix_orientation::ApplyDialog Can't do: more than one group is selected. Ðе може да Ñе изпълни: избрана е повече от една група. fix_orientation::Filter Fix Orientation Поправка на ориентациÑта output::ChangeDpiDialog Custom ПотребителÑки Error Грешка DPI is not set. ÐÑма зададена ÑтойноÑÑ‚ за DPI. DPI is too low! Прекалено ниÑка ÑтойноÑÑ‚ за DPI! DPI is too high! Прекалено виÑока ÑтойноÑÑ‚ за DPI! output::FillZoneEditor Pick color Изберете цвÑÑ‚ output::Filter Output Краен резултат output::OptionsWidget Black and White Черно и бÑло Color / Grayscale Цветно / Степени на Ñивото Mixed СмеÑено Apply Despeckling Level Прилагане почиÑтването на шум Apply Depth Perception Прилагане на дълбочина Off Изключено Auto Ðвтоматично Manual Ръчно 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 ПочиÑтване на шум page_layout::ApplyDialog Can't do: more than one group is selected. Ðе може да Ñе изпълни: избрана е повече от една група. page_layout::Filter Page Layout Оформление на Ñтраница Natural order ЕÑтеÑтвено подреждане Order by increasing width Сортировка по нараÑтваща ширина Order by increasing height Сортировка по нараÑтваща виÑочина Margins Полета page_layout::ImageView Resize margins by dragging any of the solid lines. Променете границите, като теглите нÑÐºÐ¾Ñ Ð¾Ñ‚ плътните линии. page_layout::OptionsWidget Apply Margins Прилагане на граници Apply Alignment Прилагане на подравнÑване page_split::Filter Natural order ЕÑтеÑтвено подреждане Order by split type Подреди по тип разделÑне Split Pages РазделÑне на Ñтраници page_split::ImageView Drag this handle to skew the line. Завлачете този манипулатор за да наклоните линиÑта. This line can be dragged. Тази Ð»Ð¸Ð½Ð¸Ñ Ð¼Ð¾Ð¶Ðµ да бъде влачена. Drag the line or the handles. Влачете линиÑта или манипулаторите. page_split::OptionsWidget Set manually Ръчно задаване Auto detected Ðвтоматично заÑечено page_split::UnremoveButton Restore removed page. Връщане на премахната Ñтраница. select_content::ApplyDialog Can't do: more than one group is selected. Ðе може да Ñе изпълни: избрана е повече от една група. select_content::Filter Natural order ЕÑтеÑтвено подреждане Order by increasing width Сортировка по нараÑтваща ширина Order by increasing height Сортировка по нараÑтваща виÑочина Select Content Избор на Ñъдържание select_content::ImageView Use the context menu to enable / disable the content box. Използвайте контекÑтното меню за включване и изключване облаÑтта ÑÑŠÑ Ñъдържание. Drag lines or corners to resize the content box. Влачете линии или ъгли за да преоразмерите облаÑтта ÑÑŠÑ Ñъдържание. Create Content Box Създаване на облаÑÑ‚ ÑÑŠÑ Ñъдържание Remove Content Box Премахване на облаÑÑ‚ ÑÑŠÑ Ñъдържание scantailor-RELEASE_0_9_12_2/translations/scantailor_cs.ts000066400000000000000000002716701271170121200234110ustar00rootroot00000000000000 AboutDialog About Scan Tailor O programu Scan Tailor About O programu 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 recogrnition is out of scope of this project. Scan Tailor je interaktivní nástroj na úpravu skenovaných stránek. Program umí rozdÄ›lovat dvoustrany, oprava natoÄení, nastavení okrajů a více. PÅ™edložíte nezpracované skeny a výsledek bude pÅ™ipraven pro vytvoÅ™ení PDF nebo DJVU souborů. Skenování a OCR nejsou cílem tohoto projektu. Authors AutoÅ™i Lead Developer Hlavní vývojář U235 - Picture auto-detection algorithm. Robert B. - First generation dewarping algorithm. Andrey Bergman - System load adjustment. Petr Kovář - Command line interface. Translators PÅ™ekladatelé Neco Torquato - Brazilian Portuguese Svetoslav Sashkov, Mandor - Bulgarian Petr Kovář - Czech Stefan Birkner - German Angelo Gemmi - Italian Masahiro Kitagawa - Japanese Patrick Pascal - French Daniel Koć - Polish Joseph Artsimovich - Russian Marián Hvolka - Slovak Flavio Benelli - Spanish Davidson Wang - Traditional Chinese Contributors PÅ™ispÄ›li Yuri Chornoivan - Ukrainian Documentation Dokumentace denver 22 License Licence 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 je interaktivní nástroj na úpravu skenovaných stránek. Program umí rozdÄ›lovat dvoustrany, oprava natoÄení, nastavení okrajů a více. PÅ™edložíte nezpracované skeny a výsledek bude pÅ™ipraven pro vytvoÅ™ení PDF nebo DJVU souborů. Skenování a OCR nejsou cílem tohoto projektu. Joseph Artsimovich BatchProcessingLowerPanel Form Beep when finished Zvuková signalizace pÅ™i ukonÄení ColorPickupInteraction Click on an area to pick up its color, or ESC to cancel. KliknÄ›te do obrázku k získání barvy nebo stiknÄ›te klávesu ESC pro zruÅ¡ení CrashReportDialog Crash Report Hlášení o chybÄ› Scan Tailor crashed. That's usually caused by errors in the software. You can help us find and fix the problem by submitting this crash report. Scan Tailor havaroval. To je vÄ›tÅ¡inou zapříÄinÄ›no chybou v programu. Můžete nám pomoci nájít a opravit problém odesláním tohoto hlášení. Information to be sent Informace k odeslání If you have additional information, for example you know how to reproduce the crash, please provide it here. English or Russian please. Pokud máte doplňující informace (například víte jak chybu zopakovat) napiÅ¡te je sem. Anglicky nebo Rusky. If you are willing to provide further assistance to developers to help fix this problem, please provide your email address. Pokud chcete, aby vás vývojáři mohli v případÄ› potÅ™eby kontaktovat, vyplňte, prosím, svoji e-mailovou adresu. Email [optional] Email [volitelné] This file contains the internal state of the program when it crashed Tento soubor obsahuje stav programu pÅ™i havárii Dump file Obsah souboru Sending ... Hlášení se odesílá... Successfully sent Hlášení bylo úspěšnÄ› odesláno DeskewApplyDialog Apply to Aplikovat na This page only (already applied) Pouze na tuto stranu (již aplikováno) All pages VÅ¡echny strany This page and the following ones Tato a následující strany Selected pages Vybrané strany Use Ctrl+Click / Shift+Click to select multiple pages. Pro výbÄ›r více stran použijte Ctr+Klik nebo Shift+Klik DeskewOptionsWidget Form Deskew NatoÄení Auto Automaticky Manual RuÄnÄ› Apply To ... Aplikovat na DragHandler Unrestricted dragging is possible by holding down the Shift key. Neomezené posouvání je možné pÅ™i stlaÄené klávese Shift. ErrorWidget Form FixDpiDialog Fix DPI Opravit DPI Tab 1 Tab 2 DPI Custom Vlastní x Apply Použít Need Fixing Nutná oprava All Pages VÅ¡echny strany DPI is too large and most likely wrong. DPI je příliÅ¡ vysoké a pravdÄ›podobnÄ› Å¡patnÄ› DPI is too small. Even if it's correct, you are not going to get acceptable results with it. DPI je příliÅ¡ nízké. Ikdyž může být správné, dostanete neakceptovatelný výsledek. DPI is too small for this pixel size. Such combination would probably lead to out of memory errors. DPI je příliÅ¡ nízké vzhledem k poÄtu pixelů a povede pravdÄ›podobnÄ› k problémům s nedostatkem pamÄ›ti. %1 (page %2) %1 (strana %2) FixDpiSinglePageDialog Fix DPI Oprava DPI DPI for %1 DPI pro %1 Custom Vlastní %1 (page %2) %+ (strana %2) DPI is too large and most likely wrong. DPI je příliÅ¡ vysoké a pravdÄ›podobnÄ› Å¡patnÄ› DPI is too small. Even if it's correct, you are not going to get acceptable results with it. DPI je příliÅ¡ nízké. Ikdyž může být správné, dostanete neakceptovatelný výsledek. DPI is too small for this pixel size. Such combination would probably lead to out of memory errors. DPI je příliÅ¡ nízké vzhledem k poÄtu pixelů a povede pravdÄ›podobnÄ› k problémům s nedostatkem pamÄ›ti. ImageViewBase Use the mouse wheel or +/- to zoom. When zoomed, dragging is possible. Pro pÅ™iblížení použijte koleÄko myÅ¡i nebo klávesy + resp. -. PÅ™i zvÄ›tÅ¡ení je možné stránkou tažením posouvat. InteractiveXSpline Click to create a new control point. Nový kontrolní bod vytvoříte kliknutím. This point can be dragged. S tímto bodem lze hýbat. Drag this point or delete it by pressing Del or D. Posuňte tento bod nebo jej smažte klávesou Del nebo D. LoadFileTask The following file could not be loaded: %1 Tento soubor nemohl být nahrán: The following file doesn't exist:<br>%1<br><br>Use the <a href="#relink">Relinking Tool</a> to locate it. LoadFilesStatusDialog Some files failed to load NÄ›které soubory se nepodaÅ™ilo naÄíst Loaded successfully: %1 ÚspěšnÄ› naÄteno: %1 Failed to load: %1 NeúspěšnÄ› naÄteno: %1 MainWindow MainWindow Keep current page in view. Ponechat zobrazenou souÄasnou stránku Use Home, End, PgUp (or Q), PgDown (or W) to navigate between pages. K navigaci mezi stránkami slouží klávesy Home, End PgUp (or Q), PgDown (or W) Tools Nástroje File Soubor Help NápovÄ›da Debug LadÄ›ní Debug Mode Ladící mód Save Project Uložit projekt Ctrl+S Save Project As ... Uložit projekt jako ... Next Page Další strana PgDown Previous Page PÅ™edchozí strana PgUp New Project ... Nový projekt ... Ctrl+N Open Project ... Otevřít projekt ... Ctrl+O Q W Close Project Zavřít projekt Ctrl+W Quit Konec Ctrl+Q Settings ... Nastavení... First Page První strana Home Last Page Poslední strana End About O programu Fix DPI ... Opravit DPI ... Relinking ... Stop batch processing Zastavit dávkové zpracování Save this project? Uložit projekt? Insert before ... Vložit pÅ™ed ... Insert after ... Vložit za ... Remove from project ... Odstranit z projektu Insert here ... Vložit sem ... Scan Tailor Projects Seznam projektů Open Project Otevřít projekt Error Chyba Unable to open the project file. Nelze otevřít soubor s projektem. The project file is broken. Soubor s projektem je poÅ¡kozen. 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". Výstup zatim není možný, protože jeÅ¡tÄ› není známa koneÄná velikost stránek. Pro urÄení velikosti stránek spusÅ¥te dávkové zpracování u "Vybrat Obsah" nebo "Okraje". Unnamed Nepojmenovaný %2 - Scan Tailor %3 [%1bit] Error saving the project file! Chyba pÅ™i ukládání souboru s projektem! Files to insert Skip failed files File to insert Soubor pro vložení Images not in project (%1) Obrázky, které nejsou v projektu (%1) Error opening the image file. Chyba pÅ™i otevírání souboru s obrázkem. Remove Vyjmout NewOpenProjectPanel Form New Project ... Nový projekt ... Open Project ... Otevřít projekt ... Recent Projects Naposledy otevÅ™ené projekty OrientationApplyDialog Fix Orientation Opravit orientaci Apply to Aplikovat na This page only (already applied) Pouze na tuto stranu (již aplikováno) All pages VÅ¡echny strany This page and the following ones Tato a následující strany Every other page All odd or even pages, depending on the current page being odd or even. Každá druhá strana The current page will be included. SouÄasná strana bude zahrnuta. Selected pages Vybrané strany Use Ctrl+Click / Shift+Click to select multiple pages. Pro výbÄ›r více stran použijte Ctr+Klik nebo Shift+Klik Every other selected page Každá druhá vybraná strana OrientationOptionsWidget Form Rotate OtoÄit ... Reset Obnovit Scope Rozsah Apply to ... Aplikovat na OutOfMemoryDialog Out of memory Nedostatek pamÄ›ti Out of Memory Situation in Scan Tailor Když dojde Scan Tailoru paměť Possible reasons Možné důvody Did you have to fix the DPI of your source images? Are you sure the values you entered were correct? Museli jste nastavit DPI u nÄ›kterých obrázků. Jste si jisti, že zadané hodnoty byly správné? 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. NÄ›kdy může být v obrázku Å¡patnÄ› uložená informace o DPI. Scan Tailor se snaží takovéto chyby detekovat, ale to není vždy snadné. Zkuste nastavit správné DPI ikdyž vypadá správnÄ› pÅ™i vytváření projektu (nebo z nabídky Nástroje) v dialogu "Opravit DPI". Is your output DPI set too high? Usually you don't need it higher than 600. Není výstupní DPI příliÅ¡ vysoké? Obvykle není potÅ™eba nastavovat hodnotu vÄ›tší než 600. What can help Co může pomoci 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. Opravte DPI. Jak urÄit <a href="http://vimeo.com/12524529">neznámé DPI</a>. If your hardware and operating system are 64-bit capable, consider switching to a 64-bit version of Scan Tailor. Jestliže váš hardware a operaÄní systém je 64-bitový, zvažte použití 64-bitové verze Scan Tailoru. 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. Pokud pracujete s obrázky v odstínech Å¡edi, ujistÄ›te se, že jsou opravdu v odstínech Å¡edi. Pokud se jedná o obrázky, které jsou barevné, pÅ™estože obsahují pouze Å¡edé odstíny, pÅ™eveÄte je do odstínu Å¡edi nÄ›jakým nástrojem pro hromadnou konverzi obrázků. To sníží paměťové nároky a zvýší výkon. 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. Jako poslední možnost, jak uÅ¡etÅ™it paměť, se ujistÄ›te, že náhlady stránek jsou pÅ™edvytvoÅ™eny. Pomalu rolujte seznamem náhledu od shora dolů. What won't help Co nepomůže 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. PÅ™ekvapivÄ› nepomůže pÅ™idání pamÄ›ti. Nedostatek pamÄ›ti je kompenzován využíváním odkládacího prostoru, což sice zpomaluje, ale program nespadne. Nedostatek pamÄ›ti zde znamená nedostatek adresového prostoru, což nemá nic spoleÄného s množstvím operaÄní pamÄ›ti. Jediný způsob jak zvýšit adresní prostor je pÅ™ejít na 64 bitový hardware, operaÄní systém a Scan Tailor. Save Project Uložit projekt Save Project As ... Uložit projekt jako ... Don't Save Neukládat Project Saved Successfully Projekt byl úspěšnÄ› uložen 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. Vemte, prosím, na vÄ›domí, že Scan Tailor vám dává příležitost uložit rozdÄ›laný projekt pÅ™i nedostatku pamÄ›ti, což není vždy možné. Jednou se to může podaÅ™it a podruhé může jen spadnout. Scan Tailor Projects Seznam projektů Error Chyba Error saving the project file! Chyba pÅ™i ukládání souboru s projektem! OutputApplyColorsDialog Apply Mode Mód aplikování Apply to Aplikovat na This page only (already applied) Pouze na tuto stranu (již aplikováno) All pages VÅ¡echny strany This page and the following ones Tato a následující strany Selected pages Vybrané strany Use Ctrl+Click / Shift+Click to select multiple pages. Pro výbÄ›r více stran použijte Ctr+Klik nebo Shift+Klik OutputChangeDewarpingDialog Apply Dewarping Mode Aplikovat mód deformace Mode Mód Off Vypnuto Auto (experimental) Automaticky (experimentální) Manual RuÄnÄ› Scope Rozsah This page only Pouze na tuto stranu All pages VÅ¡echny strany This page and the following ones Tato a následující strany Selected pages Vybrané strany Use Ctrl+Click / Shift+Click to select multiple pages. Pro výbÄ›r více stran použijte Ctr+Klik nebo Shift+Klik OutputChangeDpiDialog Apply Output Resolution Aplikovat výstupní rozliÅ¡ení DPI Scope Rozsah This page only Pouze na tuto stranu All pages VÅ¡echny strany This page and the following ones Tato a následující strany Selected pages Vybrané strany Use Ctrl+Click / Shift+Click to select multiple pages. Pro výbÄ›r více stran použijte Ctr+Klik nebo Shift+Klik OutputOptionsWidget Form Output Resolution (DPI) Výstupní rozliÅ¡ení (DPI) Change ... ZmÄ›nit ... Mode Mód White margins Bílé okraje Equalize illumination Kompenzovat osvÄ›tlení Thinner TenÄí Thicker Tlustší Apply To ... Aplikovat na ... Dewarping Deformace Depth perception Despeckling VyÄistit No despeckling Bez ÄiÅ¡tÄ›ní Cautious despeckling Opatrné ÄiÅ¡tÄ›ní ... Normal despeckling Normální ÄiÅ¡tÄ›ní Aggressive despeckling Agresivní ÄiÅ¡tÄ›ní PageLayoutApplyDialog Apply to Aplikovat na This page only (already applied) Pouze na tuto stranu (již aplikováno) All pages VÅ¡echny strany This page and the following ones Tato a následující strany Every other page All odd or even pages, depending on the current page being odd or even. Každá druhá strana The current page will be included. SouÄasná strana bude zahrnuta. Selected pages Vybrané strany Use Ctrl+Click / Shift+Click to select multiple pages. Pro výbÄ›r více stran použijte Ctr+Klik nebo Shift+Klik Every other selected page Každá druhá vybraná strana PageLayoutOptionsWidget Form Margins Okraje Millimeters (mm) Milimetry (mm) Inches (in) Palce (in) Top NahoÅ™e ... Bottom Dole Left Vlevo Right Vpravo Apply To ... Aplikovat na ... Alignment Zarovnání Match size with other pages Stejná velikost jako ostatní strany PageSplitModeDialog Split Pages RozdÄ›lit strany Mode Mód Auto Automaticky Manual RuÄnÄ› Scope Rozsah This page only Pouze na tuto stranu All pages VÅ¡echny strany This page and the following ones Tato a následující strany Selected pages Vybrané strany Use Ctrl+Click / Shift+Click to select multiple pages. Pro výbÄ›r více stran použijte Ctr+Klik nebo Shift+Klik PageSplitOptionsWidget Form Page Layout Layout strany ? Change ... ZmÄ›nit ... Split Line Řez Auto Automaticky Manual RuÄnÄ› PictureZonePropDialog Zone Properties Vlastnosti zóny Subtract from all layers Odebrat ze vÅ¡ech vrstev Add to auto layer PÅ™idat k automatické vrstvÄ› Subtract from auto layer Odebrat z automatické vrstvy ProjectFilesDialog Project Files Soubory s projekty Input Directory Vstupní adresář Browse Procházet Output Directory Výstupní adresář Files Not In Project Soubory, které nejsou v projektu Select All Vybrat vÅ¡e <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;">Remove selected files from project.</p></body></html> << Files In Project Soubory v projektu Right to left layout (for Hebrew and Arabic) Z prava do leva (HebrejÅ¡tina, ArabÅ¡tina) Fix DPIs, even if they look OK Opravit DPI, ikdyž vypadají OK Error Chyba No files in project! Projekt nemá žádné soubory! Input directory is not set or doesn't exist. Vstupní adresář není nastaven nebo neexistuje. Input and output directories can't be the same. Vstupní a výstupní adresáře musejí být různé. Create Directory? VytvoÅ™it adresář? Output directory doesn't exist. Create it? Výstupní adresář neexistuje. VytvoÅ™it? Unable to create output directory. Nelze vytvoÅ™it výstupní adresář. Output directory is not set or doesn't exist. Výstupní adresář není nastaven nebo neexistuje. 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. NÄ›které soubory se nepodaÅ™ilo nahrát. BuÄ nepodporujeme jejich formát, nebo jsou poÅ¡kozeny. VyjmÄ›te je z projektu. ProjectOpeningContext Error Chyba Unable to interpret the project file. Chyba pÅ™i Ätení souboru s projektem. RelinkingDialog Relinking Undo ZpÄ›t ... Substitution File for %1 Substitution Directory for %1 This change would merge several files into one. RemovePagesDialog Remove Pages Vyjmout strany Remove %1 page(s) from project? Vyjmout %1 stran z projektu? Corresponding output files will be deleted, while input files will remain. Odpovídající výstupní soubory budou smazány, ale vstupní zůstanou. SelectContentApplyDialog Select Content Vybrat obsah Apply to Aplikovat na This page only (already applied) Pouze na tuto stranu (již aplikováno) All pages VÅ¡echny strany This page and the following ones Tato a následující strany Every other page All odd or even pages, depending on the current page being odd or even. Každá druhá strana The current page will be included. SouÄasná strana bude zahrnuta. Selected pages Vybrané strany Use Ctrl+Click / Shift+Click to select multiple pages. Pro výbÄ›r více stran použijte Ctr+Klik nebo Shift+Klik Every other selected page Každá druhá vybraná strana SelectContentOptionsWidget Form Content Box Obsah Auto Automaticky Manual RuÄnÄ› Scope Rozsah Apply to ... Aplikovat na ... SettingsDialog Settings Nastavení Use 3D acceleration for user interface Používat 3D akceleraci pro uživatelské rozhraní Compiled without OpenGL support. Zkompilováno bez podpory OpenGL Your hardware / driver don't provide the necessary features. VaÅ¡e zařízení nemá potÅ™ebné vlastnosti. StageListView Launch batch processing Spustit dávkové zpracování SystemLoadWidget Form System load Systémová zátěž ... Minimal MinimálnÄ› Normal NormálnÄ› ThumbnailSequence %1 (page %2) %1 (strana %2) ZoneContextMenuInteraction Delete confirmation Potvrzení smazání Really delete this zone? Opravdu si pÅ™ejete smazat tuto zónu? Delete Smazat Properties Vlastnosti ZoneCreationInteraction Click to finish this zone. ESC to cancel. KliknÄ›te pro uzavÅ™ení zóny. ESC pro zruÅ¡ení. Connect first and last points to finish this zone. ESC to cancel. Spojit první a poslední bod k uzavÅ™ení zóny. ESC pro zruÅ¡ení. Zones need to have at least 3 points. ESC to cancel. Zóna musí mít aspoň 3 body. ESC ke zruÅ¡ení. ZoneDefaultInteraction Drag the vertex. Posuout vrchol Click to create a new vertex here. Kliknutím vytvoříte nový vrchol. Right click to edit zone properties. KliknÄ›te pravým tlaÄítkem pro editaci vlastností zóny. Click to start creating a new picture zone. Klikem zaÄnete vytvářet novou zónu pro orbrázek. ZoneVertexDragInteraction Merge these two vertices. SlouÄit vrcholy. Move the vertex to one of its neighbors to merge them. SlouÄit vrchol s jedním z jeho sousedů. deskew::Filter Deskew NatoÄení deskew::ImageView Use Ctrl+Wheel to rotate or Ctrl+Shift+Wheel for finer rotation. OtáÄení: Ctrl+KoleÄko nebo Ctr+Shift+KoleÄko pro jemné natoÄení. Drag this handle to rotate the image. TáhnÄ›te tímto madlem pro natoÄení obrázku deskew::OptionsWidget Apply Deskew Aplikovat otoÄení fix_orientation::ApplyDialog Can't do: more than one group is selected. Nelze provést: je vybrána více než jedna skupina. fix_orientation::Filter Fix Orientation Opravit orientaci output::ChangeDpiDialog Custom Vlastní Error Chyba DPI is not set. DPI není nastavené. DPI is too low! DPI je příliÅ¡ nízké! DPI is too high! DPI je příliÅ¡ vysoké! output::FillZoneEditor Pick color Vyberte barvu output::Filter Output Výstup output::OptionsWidget Black and White ÄŒerná a Bílá Color / Grayscale BarevnÄ› / StupnÄ› Å¡edé Mixed SmíšenÄ› Apply Despeckling Level Aplikovat úroveň ÄiÅ¡tÄ›ní Apply Depth Perception Off Vypnuto Auto Automaticky Manual RuÄnÄ› output::Task::UiUpdater Picture zones are only available in Mixed mode. Zóny s obrázky jsou možné pouze ve smíšeném módu. Despeckling can't be done in Color / Grayscale mode. ÄŒiÅ¡tÄ›ní nelze provést v barevném / Äernobílém módu. Output Výstup Picture Zones Zóny s obrázky Fill Zones Prázdné zóny Dewarping Deformace Despeckling VyÄistit page_layout::ApplyDialog Can't do: more than one group is selected. Nelze provést: je vybrána více než jedna skupina. page_layout::Filter Natural order PÅ™irozené Å™azení Order by increasing width Řadit podle rostoucí šířky Order by increasing height Řadit podle rosoucí výšky Margins Okraje page_layout::ImageView Resize margins by dragging any of the solid lines. Změňte okraje tažením za libovolný okraj. page_layout::OptionsWidget Apply Margins Aplikovat okraje Apply Alignment Aplikovat zarovnání page_split::Filter Natural order PÅ™irozené Å™azení Order by split type Řadit podle typu Å™ezu Split Pages Řezat strany page_split::ImageView Drag the line or the handles. TáhnÄ›te za okraj nebo za madlo. page_split::OptionsWidget Set manually Nastavit ruÄnÄ› Auto detected Detekovat page_split::UnremoveButton Restore removed page. Obnovit vyjmutou stranu. select_content::ApplyDialog Can't do: more than one group is selected. Nelze provést: je vybrána více než jedna skupina. select_content::Filter Natural order PÅ™irozené Å™azení Order by increasing width Řadit podle rostoucí šířky Order by increasing height Řadit podle rosoucí výšky Select Content Vybrat obsah select_content::ImageView Use the context menu to enable / disable the content box. Použijte kontextové menu pro zapnutí nebo vypnutí obsahu. Drag lines or corners to resize the content box. Obsahový rámec zmÄ›níte táhnutím za okraje nebo rohy. Create Content Box VytvoÅ™it obsahový rámec Remove Content Box Odstranit obsahový rámec scantailor-RELEASE_0_9_12_2/translations/scantailor_de.ts000066400000000000000000002750161271170121200233720ustar00rootroot00000000000000 AboutDialog About Scan Tailor Über Scan Tailor About Über 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 recogrnition is out of scope of this project. Scan Tailor ist ein interaktives Werkzeug zur Bildnachverarbeitung gescannter Seiten. Es führt Operationen wie das Aufteilen von Seiten, Ausrichten, Hinzufügen/Entfernen von Stegen (Rändern), usw. durch. Sie übergeben dem Programm unbearbeitete Scans und erhalten Seiten zurück, die zum Drucken oder Zusammensetzen einer PDF- oder DJVU-Datei aufbereitet sind. Das Scannen selbst und Texterkennung (OCR) liegen außerhalb des Rahmens dieses Projekts. 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 Autoren Lead Developer Chefentwickler Joseph Artsimovich Joseph Artsimovich U235 - Picture auto-detection algorithm. U235 - Automatischer Bilderkennungs-Algorithmus. Robert B. - First generation dewarping algorithm. Robert B. - Erste Generation des Dewarping-Algorithmus. Andrey Bergman - System load adjustment. Andrey Bergman - Systemlastanpassung. Petr Kovář - Command line interface. Petr Kovář - Kommandozeileninterface. Neco Torquato - Brazilian Portuguese Neco Torquato - Brasilianisches Portugiesisch Svetoslav Sashkov, Mandor - Bulgarian Svetoslav Sashkov, Mandor - Bulgarisch Petr Kovář - Czech Petr Kovář - Tschechisch Stefan Birkner - German Stefan Birkner - Deutsch Angelo Gemmi - Italian Angelo Gemmi - Italienisch Masahiro Kitagawa - Japanese Masahiro Kitagawa - Japanisch Patrick Pascal - French Patrick Pascal - Französisch Daniel Koć - Polish Daniel Koć - Polnisch Joseph Artsimovich - Russian Joseph Artsimovich - Russisch Marián Hvolka - Slovak Marián Hvolka - Slowakisch Flavio Benelli - Spanish Flavio Benelli - Spanisch Davidson Wang - Traditional Chinese Davidson Wang - Chinesisch (Langzeichen) Yuri Chornoivan - Ukrainian Yuri Chornoivan - Ukrainisch denver 22 denver 22 Translators Übersetzer Svetoslav Sashkov, Mandor - Bulgarian Stefan Birkner - German Angelo Gemmi - Italian Masahiro Kitagawa - Japanese Patrick Pascal - French Joseph Artsimovich - Russian Davidson Wang - Traditional Chinese Svetoslav Sashkov, Mandor - Bulgarisch Stefan Birkner - Deutsch Angelo Gemmi - Italienisch Masahiro Kitagawa - Japanisch Patrick Pascal - Französisch Joseph Artsimovich - Russisch Davidson Wang - Chinesisch (Langzeichen) Contributors Mitwirkende U235 - Picture auto-detection algorithm. Robert B. - Dewarping algorithm. Andrey Bergman - System load adjustment. U235 - Algorithmus zur automatischen Bilderkennung. Robert B. - Dewarping-Algorithmus. Andrey Bergman - Einstellen der Systemauslastung. Documentation Dokumentation denver 22 phaedrus Taxman denver 22 phaedrus Taxman License Lizenz BatchProcessingLowerPanel Form Projekt auswählen Beep when finished Piepen, wenn fertig ColorPickupInteraction Click on an area to pick up its color, or ESC to cancel. Klicken Sie in einen Bereich, um dessen Farbe auszuwählen, oder drücken Sie ESC zum Abbrechen. DeskewApplyDialog Apply to Anwenden auf This page only (already applied) nur diese Seite (schon angewandt) All pages alle Seiten This page and the following ones diese und die folgenden Seiten Selected pages ausgewählte Seiten Use Ctrl+Click / Shift+Click to select multiple pages. Verwenden Sie Strg+Mausklick/Shift+Mausklick zur Auswahl mehrerer Seiten. DeskewOptionsWidget Form Projekt auswählen Deskew Ausrichten Auto Automatisch Manual Manuell Apply To ... Anwenden auf ... DragHandler Unrestricted dragging is possible by holding down the Shift key. Durch Drücken der Umschalttaste ist ein unbeschränktes Ziehen möglich. ErrorWidget Form Projekt auswählen FixDpiDialog Need Fixing Korrektur notwendig All Pages Alles Seiten DPI is too large and most likely wrong. Dpi sind zu groß und sehr wahrscheinlich falsch. DPI is too small. Even if it's correct, you are not going to get acceptable results with it. Dpi sind zu niedrig. Sogar wenn sie richtig sind, werden Sie damit kein annehmbares Ergebnis erhalten. DPI is too small for this pixel size. Such combination would probably lead to out of memory errors. Dpi sind zu niedrig für diese Pixelgröße. Ein solche Kombination führt wahrscheinlich zu Zuwenig-Speicher-Fehlern. %1 (page %2) %1 (Seite %2) Fix DPI Dpi korrigieren Tab 1 Tab 2 DPI dpi Custom Eigener Wert x x Apply Übernehmen FixDpiSinglePageDialog Fix DPI Dpi korrigieren DPI for %1 Dpi für %1 Custom Eigener Wert x x %1 (page %2) %1 (Seite %2) DPI is too large and most likely wrong. Dpi sind zu groß und sehr wahrscheinlich falsch. DPI is too small. Even if it's correct, you are not going to get acceptable results with it. Dpi sind zu niedrig. Sogar wenn sie richtig sind, werden Sie damit kein annehmbares Ergebnis erhalten. DPI is too small for this pixel size. Such combination would probably lead to out of memory errors. Dpi sind zu niedrig für diese Pixelgröße. Ein solche Kombination führt wahrscheinlich zu Zuwenig-Speicher-Fehlern. ImageViewBase Use the mouse wheel or +/- to zoom. When zoomed, dragging is possible. Verwenden Sie das Mausrad oder +/- zum Zoomen. InteractiveXSpline Click to create a new control point. This point can be dragged. Drag this point or delete it by pressing Del or D. LoadFileTask The following file could not be loaded: %1 Die folgende Datei konnte nicht geladen werden: %1 The following file doesn't exist:<br>%1<br><br>Use the <a href="#relink">Relinking Tool</a> to locate it. LoadFilesStatusDialog Some files failed to load Manche Dateien konnten nicht geladen werden Loaded successfully: %1 Erfolgreich geladen: %1 Failed to load: %1 Nicht geladen: %1 MainWindow Save Project Projekt speichern Save this project? Dieses Projekt speichern? Insert before ... Davor einfügen ... Insert after ... Danach einfügen ... Remove from project ... Aus dem Projekt entfernen ... Insert here ... Hier einfügen ... Scan Tailor Projects Scan-Tailor-Projekte Open Project Projekt öffnen Error Fehler Unable to open the project file. Die Projektdatei kann nicht geöffnet werden. The project file is broken. Die Projektdatei ist beschädigt. 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". Ausgabe ist noch nicht möglich, da die schlußendliche Größe der Seiten noch nicht bekannt ist. Um diese zu ermitteln, starten Sie bitte die Stapelverarbeitung unter „Inhalt auswählen“ oder „Stege (Ränder)“. Unnamed Unbenannt %2 - Scan Tailor %3 [%1bit] %2 - Scan Tailor %3 [%1bit] Files to insert Skip failed files %1 - Scan Tailor %2 %1 - Scan Tailor %2 Error saving the project file! Beim Speichern der Projektdatei ist ein Fehler aufgetreten! File to insert Einzufügende Datei Images not in project (%1) Bilder befinden sich nicht im Projekt (%1) Error opening the image file. Beim Öffnen der Bilddatei ist ein Fehler aufgetreten. Remove Entfernen MainWindow Hauptfenster Keep current page in view. Aktuelle Seite in der Anzeige belassen. Use Home, End, PgUp (or Q), PgDown (or W) to navigate between pages. Verwenden Sie Pos1, Ende, BildAuf (oder Q), BildAb (oder W) um zwischen den Seiten umzuschalten. Tools Werkzeuge File Datei Help Hilfe Debug Debuggen Debug Mode Ctrl+S Ctrl+S Save Project As ... Projekt speichern unter ... Next Page Nächste Seite PgDown PgDown Previous Page Vorherige Seite PgUp PgUp New Project ... Neues Projekt ... Ctrl+N Ctrl+N Open Project ... Projekt öffnen ... Ctrl+O Ctrl+O Q Q W W Close Project Projekt schließen Ctrl+W Ctrl+W Quit Beenden Ctrl+Q Ctrl+Q First Page Erste Seite Home Pos1 Last Page Letzte Seite End Ende About Über Fix DPI ... DPI korrigieren ... Relinking ... Stop batch processing Stapelverarbeitung beenden Settings ... Einstellungen ... NewOpenProjectPanel Form Projekt auswählen New Project ... Neues Projekt ... Open Project ... Projekt öffnen ... Recent Projects Zuletzt bearbeitete Projekte OrientationApplyDialog Fix Orientation Seite drehen Apply to Anwenden auf This page only (already applied) nur diese Seiten (schon angewandt) All pages alle Seiten This page and the following ones diese und die folgenden Seiten Every other page All odd or even pages, depending on the current page being odd or even. Alle geraden oder ungeraden Seiten, abhängig davon, ob die aktuelle Seite gerade oder ungerade ist. jede zweite Seite The current page will be included. Die aktuelle Seite mit eingeschlossen. Selected pages ausgewählte Seiten Use Ctrl+Click / Shift+Click to select multiple pages. Verwenden Sie Strg+Mausklick/Shift+Mausklick zur Auswahl mehrerer Seiten. Every other selected page jede zweite ausgewählte Seite OrientationOptionsWidget Form Projekt auswählen Rotate Drehen ... ... Reset Zurücksetzen Scope Geltungsbereich Apply to ... Anwenden auf ... 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 Projekt speichern Save Project As ... Projekt speichern unter ... 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 Scan-Tailor-Projekte Error Fehler Error saving the project file! Beim Speichern der Projektdatei ist ein Fehler aufgetreten! OutputApplyColorsDialog Apply Mode Methode anwenden Apply to Anwenden auf This page only (already applied) nur diese Seiten (schon angewandt) All pages alle Seiten This page and the following ones diese und die folgenden Seiten Selected pages ausgewählte Seiten Use Ctrl+Click / Shift+Click to select multiple pages. Verwenden Sie Strg+Mausklick/Shift+Mausklick zur Auswahl mehrerer Seiten. OutputChangeDewarpingDialog Apply Dewarping Mode Seitenkrümmung entfernen Mode Methode Off Aus Auto (experimental) Automatisch (experimentell) Manual Manuell Scope Geltungsbereich This page only nur diese Seite All pages alle Seiten This page and the following ones diese und die folgenden Seiten Selected pages ausgewählte Seiten Use Ctrl+Click / Shift+Click to select multiple pages. Verwenden Sie Strg+Mausklick/Shift+Mausklick zur Auswahl mehrerer Seiten. OutputChangeDpiDialog Apply Output Resolution Auflösung für Ausgabedatei übernehmen DPI dpi Scope Geltungsbereich This page only nur diese Seite This page and the following ones diese und die folgenden Seiten Selected pages ausgewählte Seiten Use Ctrl+Click / Shift+Click to select multiple pages. Verwenden Sie Strg+Mausklick/Shift+Mausklick zur Auswahl mehrerer Seiten. All pages alle Seiten OutputOptionsWidget Form Projekt auswählen Output Resolution (DPI) Auflösung der Ausgabedatei (dpi) Change ... Ändern ... Mode Methode White margins Weiße Stege (Ränder) Equalize illumination Helligkeit angleichen Dewarping Depth perception No despeckling Kein Despeckling Dewarp Dewarp Apply To ... Anwenden auf ... Despeckling Despeckling Cautious despeckling Behutsames Despeckling ... ... Normal despeckling Normales Despeckling Aggressive despeckling Offensives Despeckling Thinner dünner Thicker dicker PageLayoutApplyDialog Apply to Anwenden auf This page only (already applied) nur diese Seite (schon angewandt) All pages alle Seiten This page and the following ones diese und die folgenden Seiten Every other page All odd or even pages, depending on the current page being odd or even. jede zweite Seite The current page will be included. Die aktuelle Seite mit eingeschlossen. Selected pages ausgewählte Seiten Use Ctrl+Click / Shift+Click to select multiple pages. Verwenden Sie Strg+Mausklick/Shift+Mausklick zur Auswahl mehrerer Seiten. Every other selected page jede zweite ausgewählte Seite PageLayoutOptionsWidget Form Projekt auswählen Margins Stege (Ränder) Millimeters (mm) Millimeter (mm) Inches (in) Inch (in) Top Oben ... ... Bottom Unten Left Links Right Rechts Apply To ... Anwenden auf ... Alignment Ausrichtung Match size with other pages Größe an andere Seiten anpassen PageSplitModeDialog Split Pages Seiten aufteilen Mode Methode Auto Automatisch Manual Manuell Scope Geltungsbereich This page only nur diese Seite This page and the following ones diese und die folgenden Seiten Selected pages ausgewählte Seiten Use Ctrl+Click / Shift+Click to select multiple pages. Verwenden Sie Strg+Mausklick/Shift+Mausklick zur Auswahl mehrerer Seiten. All pages alle Seiten PageSplitOptionsWidget Form Projekt auswählen Page Layout Seitenlayout ? ? Change ... Ändern ... Split Line Trennlinie Auto Automatisch Manual Manuell PictureZonePropDialog Zone Properties Bereichseigenschaften Subtract from all layers Von allen Ebenen subtrahieren Add to auto layer Zur automatischen Ebene hinzufügen Subtract from auto layer Von der automatischen Ebene subtrahieren ProjectFilesDialog Input Directory Eingabeverzeichnis Output Directory Ausgabeverzeichnis Error Fehler No files in project! Es sind keine Dateien im Projekt! Input directory is not set or doesn't exist. Das Eingabeverzeichnis wurde nicht angegeben oder es existiert nicht. Input and output directories can't be the same. Ein- und Ausgabeverzeichnis dürfen nicht übereinstimmen. Create Directory? Verzeichnis erstellen? Output directory doesn't exist. Create it? Das Ausgabeverzeichnis existiert nicht. Soll es erstellt werden? Unable to create output directory. Ausgabeverzeichnis kann nicht erstellt werden. Output directory is not set or doesn't exist. Das Ausgabeverzeichnis wurde nicht angegeben oder es existiert nicht. 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. Einige der Dateien konnten nicht geladen werden. Entweder wird ihr Format nicht unterstützt oder sie sind beschädigt. Sie sollten sie aus dem Projekt entfernen. Project Files Projektdateien Browse Durchsuchen Files Not In Project Dateien nicht im Projekt Select All Alle auswählen <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;">Ausgewählte Dateien zum Projekt hinzufügen.</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;">Ausgewählte Dateien aus dem Projekt entfernen.</p></body></html> << << Files In Project Dateien im Projekt Right to left layout (for Hebrew and Arabic) Schreibrichtung von rechts nach link (für Hebräisch und Arabisch) Fix DPIs, even if they look OK Dpi korrigieren, auch wenn sie aussehen, als wären sie richtig ProjectOpeningContext Error Fehler Unable to interpret the project file. Die Projektdatei kann nicht ausgewertet werden. RelinkingDialog Relinking Undo ... ... Substitution File for %1 Substitution Directory for %1 This change would merge several files into one. RemovePagesDialog Remove Pages Seiten entfernen Remove %1 page(s) from project? %1 Seite(n) aus dem Projekt entfernen? Corresponding output files will be deleted, while input files will remain. Die entsprechenden Ausgabedateien werden gelöscht, während die Eingabedatei erhalten bleibt. SelectContentApplyDialog Select Content Inhalt auswählen Apply to Anwenden auf This page only (already applied) nur diese Seiten (schon angewandt) All pages alle Seiten This page and the following ones diese und die folgenden Seiten Every other page All odd or even pages, depending on the current page being odd or even. jede zweite Seite The current page will be included. Die aktuelle Seite mit eingeschlossen. Selected pages ausgewählte Seiten Use Ctrl+Click / Shift+Click to select multiple pages. Verwenden Sie Strg+Mausklick/Shift+Mausklick zur Auswahl mehrerer Seiten. Every other selected page jede zweite ausgewählte Seite SelectContentOptionsWidget Form Projekt auswählen Content Box Content-Box Auto Automatisch Manual Manuell Scope Geltungsbereich Apply to ... Anwenden auf ... SettingsDialog Compiled without OpenGL support. Ohne OpenGL-Unterstützung kompiliert. Your hardware / driver don't provide the necessary features. Ihre Hardware bzw. ihr Treiber verfügt nicht über die notwendigen Eigenschaften. Settings Einstellungen Use 3D acceleration for user interface 3D-Beschleunigung für die Benutzeroberläche verwenden StageListView Launch batch processing Stapelverarbeitung starten SystemLoadWidget Form Projekt auswählen System load Systemauslastung ... ... Minimal Minimal Normal Normal ThumbnailSequence %1 (page %2) %1 (Seite %2) ZoneContextMenuInteraction Properties Eigenschaften Delete Löschen Delete confirmation Löschbestätigung Really delete this zone? Diesen Bereich wirklich löschen? ZoneCreationInteraction Click to finish this zone. ESC to cancel. Klicken um diesen Bereich fertigzustellen. Esc um abzubrechen. Connect first and last points to finish this zone. ESC to cancel. Verbinden Sie den ersten und letzten Punkt um diese Zone fertigzustellen. Esc zum Abbrechen. Zones need to have at least 3 points. ESC to cancel. Bereichen benötigen wenigstens drei Punkte. Esc zum Abbrechen. ZoneDefaultInteraction Drag the vertex. Ziehen Sie den Eckpunkt. Click to create a new vertex here. Klicken Sie um hier einen neuen Eckpunkt zu erstellen. Right click to edit zone properties. Rechtsklick zum Bearbeiten der Bereichseigenschaften. Click to start creating a new picture zone. Klicken, um das Erstellen einers neuen Bildbereichs zu beginnen. ZoneVertexDragInteraction Merge these two vertices. Diese zwei Eckpunkte vereinen. Move the vertex to one of its neighbors to merge them. Bewegen sie den Eckpunkt zu einem seiner Nachbarn, um ihn mit diesem zu vereinen. deskew::Filter Deskew Ausrichten deskew::ImageView Use Ctrl+Wheel to rotate or Ctrl+Shift+Wheel for finer rotation. Verwenden Sie Strg+Mausrad zum Drehen oder Strg+Shift+Mausrad für feinere Drehungen. Drag this handle to rotate the image. Ziehen Sie diesen Griff, um das Bild zu drehen. deskew::OptionsWidget Apply Deskew fix_orientation::ApplyDialog Can't do: more than one group is selected. Ausführung nicht möglich, da mehr als eine Gruppe ausgewählt ist. fix_orientation::Filter Fix Orientation Seiten drehen output::ChangeDpiDialog Custom Eigener Wert Error Fehler DPI is not set. Dpi sind nicht gesetzt. DPI is too low! Dpi sind zu gering! DPI is too high! Dpi sind zu hoch! output::FillZoneEditor Pick color Farbe wählen output::Filter Output Ausgabe output::OptionsWidget Black and White Schwarzweiß Color / Grayscale Farbe/Graustufen Mixed Gemischt Apply Despeckling Level Despeckling-Grad anwenden Apply Depth Perception Off Aus Auto Automatisch Manual Manuell output::Task::UiUpdater Picture zones are only available in Mixed mode. Bildbereiche stehen nur im Gemischten Modus zur Verfügung. Despeckling can't be done in Color / Grayscale mode. Despeckling kann im Farb-/Graustufenmodes nicht durchgeführt werden. Output Ausgabe Picture Zones Bildbereiche Fill Zones Bereiche füllen Dewarping Despeckling Despeckling page_layout::ApplyDialog Can't do: more than one group is selected. Ausführung nicht möglich, da mehr als eine Gruppe ausgewählt ist. page_layout::Filter Natural order Natürliche Reihenfolge Order by increasing width Nach Breite aufsteigend sortieren Order by increasing height Nach Höhe aufsteigend sortieren Margins Stege (Ränder) page_layout::ImageView Resize margins by dragging any of the solid lines. Verändern Sie die Größe der Stege indem Sie eine der durchgehenden Linien ziehen. page_layout::OptionsWidget Apply Margins Ränder übernehmen Apply Alignment Ausrichtung übernehmen page_split::Filter Natural order Natürliche Reihenfolge Order by split type Split Pages Seiten aufteilen page_split::ImageView Drag the line or the handles. Ziehen Sie die Linie oder die Griffe. page_split::OptionsWidget Set manually Manuell setzen Auto detected Automatisch erkennen page_split::UnremoveButton Restore removed page. Gelöschte Seite wiederherstellen select_content::ApplyDialog Can't do: more than one group is selected. Ausführung nicht möglich, da mehr als eine Gruppe ausgewählt ist. select_content::Filter Natural order Natürliche Reihenfolge Order by increasing width Nach Breite aufsteigend sortieren Order by increasing height Nach Höhe aufsteigend sortieren Select Content Inhalt auswählen select_content::ImageView Use the context menu to enable / disable the content box. Verwenden Sie das Kontextmenü, um die Content-Box zu aktivieren/deaktivieren. Drag lines or corners to resize the content box. Ziehen Sie an den Kanten oder Ecken um die Größe der Content-Box zu ändern. Create Content Box Content-Box erzeugen Remove Content Box Context-Box entfernen scantailor-RELEASE_0_9_12_2/translations/scantailor_es.ts000066400000000000000000002717101271170121200234060ustar00rootroot00000000000000 AboutDialog About Scan Tailor Acerca de Scan Tailor About Acerca 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 post procesado para imágenes escaneadas. Lleva a cabo operaciones tales como dividir paginas, corrección de inclinación, agregar/eliminar márgenes y otros.Usted le proporciona la imagen escaneada y obtiene imágenes lista para ser impresas o para crear un archivo PDF o DJVU. El escaneo y el reconocimiento óptico de caracteres OCR están fuera de la mira de este proyecto. Authors Autores Lead Developer Desarrollador Principal Joseph Artsimovich Joseph Artsimovich U235 - Picture auto-detection algorithm. U235 - Algoritmo de detección automática de imágenes. Robert B. - First generation dewarping algorithm. Robert B. - Primera generación de corrección de deformación automática de imágenes. Andrey Bergman - System load adjustment. Andrey Bergman - Ajuste de carga del sistema. Petr Kovář - Command line interface. Petr Kovář - Interface de línea de comando. Neco Torquato - Brazilian Portuguese Svetoslav Sashkov, Mandor - Bulgarian Svetoslav Sashkov, Mandor - Búlgaro Petr Kovář - Czech Petr Kovář - Checo Stefan Birkner - German Stefan Birkner - Alemán Angelo Gemmi - Italian Angelo Gemmi - Italiano Masahiro Kitagawa - Japanese Masahiro Kitagawa - Japonés Patrick Pascal - French Patrick Pascal - Francés Daniel Koć - Polish Daniel Koć - Polaco Joseph Artsimovich - Russian Joseph Artsimovich - Ruso Marián Hvolka - Slovak Marián Hvolka - Eslovaco Flavio Benelli - Spanish Flavio Benelli - Español Davidson Wang - Traditional Chinese Davidson Wang - Chino Tradicional Yuri Chornoivan - Ukrainian Yuri Chornoivan - Ucraniano denver 22 denver 22 Translators Traductores Contributors Colaboradores Documentation Documentación License Licencia BatchProcessingLowerPanel Form Formulario Beep when finished Pitido al finalizar ColorPickupInteraction Click on an area to pick up its color, or ESC to cancel. Haga clic en un área para elegir color o ESC para cancelar. DeskewApplyDialog Apply to Aplicar a This page only (already applied) Solamente esta página (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. Usar Ctrl+Clic / Shift+Clic para seleccionar múltiples páginas. DeskewOptionsWidget Form Formulario Deskew Alineación Auto Auto Manual Manual Apply To ... Aplicar a... DragHandler Unrestricted dragging is possible by holding down the Shift key. i need a context, i'm not shure or its this translation. El arrastre sin restricciones es posible, mantenga pulsada la tecla Shift. ErrorWidget Form Formulario ImageLabel why doesn't have space? EtiquetaImagen TextLabel why doesn't have space? EtiquetaTexto FixDpiDialog Need Fixing Necesita corrección All Pages Todas las páginas DPI is too large and most likely wrong. El número de DPI es muy grande y posiblemente esté equivocado. DPI is too small. Even if it's correct, you are not going to get acceptable results with it. El número de DPI es muy pequeño. Incluso si fuera correcto, no va a conseguir resultados aceptables. DPI is too small for this pixel size. Such combination would probably lead to out of memory errors. El número de DPI es demasiado pequeño para este tamaño de píxeles. Tal combinación probablemente llevará a problemas con la memoria. %1 (page %2) %1 (página %2) Fix DPI Corregir DPI Tab 1 Pestaña 1 Tab 2 Pestaña 2 DPI DPI Custom Personalizar x x Apply Aplicar FixDpiSinglePageDialog Fix DPI Corregir DPI DPI for %1 i need context DPI para %1 Custom Personalizar x x %1 (page %2) %1 (página %2) DPI is too large and most likely wrong. El número de DPI es muy grande y posiblemente esté equivocado. DPI is too small. Even if it's correct, you are not going to get acceptable results with it. El número de DPI es muy pequeño. Incluso si fuera correcto, no va a conseguir resultados aceptables. DPI is too small for this pixel size. Such combination would probably lead to out of memory errors. El número de DPI es demasiado pequeño para este tamaño de píxeles. Tal combinación probablemente llevará a problemas con la memoria. ImageViewBase Use the mouse wheel or +/- to zoom. When zoomed, dragging is possible. Use la rueda del ratón o +/- para ampliar. Cuando está ampliado, 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. Este punto puede ser arrastrado. Drag this point or delete it by pressing Del or D. Arrastre este punto o bórrelo presionando Del o D. LoadFileTask The following file could not be loaded: %1 El siguiente archivo no pudo ser abierto: %1 The following file doesn't exist:<br>%1<br><br>Use the <a href="#relink">Relinking Tool</a> to locate it. LoadFilesStatusDialog Some files failed to load Algunos archivos fallaron al cargar Loaded successfully: %1 Carga correcta:%1 Failed to load: %1 Falla al cargar:%1 MainWindow Save Project Guardar Proyecto Save this project? ¿Desea guardar este 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. Imposible abrir el archivo del proyecto. The project file is broken. Este proyecto está corrupto. 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". Salida no es aun posible debido a que el tamaño final de la página aun es desconocida. Para determinarlo, ejecute el proceso de "Seleccionar Contenido" o "Márgenes". Unnamed Sin nombre %2 - Scan Tailor %3 [%1bit] Files to insert Skip failed files %1 - Scan Tailor %2 %1 - Scan Tailor %2 Error saving the project file! Se produjo un error al intentar guardar el proyecto! File to insert Archivo a insertar Images not in project (%1) Imagenes que no estan en el proyecto (%1) Error opening the image file. Se produjo un error al abrir el archivo de imagen. Remove Eliminar MainWindow why doesn't have any space between Main and Windows? VentanaPrincipal Keep current page in view. Mantener la visualización de la presente ventana. Use Home, End, PgUp (or Q), PgDown (or W) to navigate between pages. Use Home, End, PgUp (o Q), PgDown (o W) para navegar entre las páginas. Tools Herramientas File Archivo Help Ayuda Debug Depurar Debug Mode Modo Depuración Ctrl+S Ctrl+S Save Project As ... Guardar proyecto como... Next Page Página siguiente PgDown PgDown Previous Page Página anterior PgUp PgUp New Project ... Nuevo proyecto... Ctrl+N Ctrl+N Open Project ... Abrir proyecto... Ctrl+O Ctrl+O Q Q W W Close Project Cerrar proyecto Ctrl+W Ctrl+W Quit Salir Ctrl+Q Ctrl+Q First Page Primera página Home Inicio Last Page Ultima página End Final About Acerca Fix DPI ... Corregir DPI... Relinking ... Revincular... Stop batch processing Detener el procesamiento por lotes Settings ... Ajustes... NewOpenProjectPanel Form Formulario New Project ... Nuevo proyecto... Open Project ... Abrir proyecto... Recent Projects Proyectos recientes OrientationApplyDialog Fix Orientation Corregir orientación Apply to Aplicar a This page only (already applied) Solamente a esta página (ya aplicado) All pages Todas las páginas This page and the following ones Esta página y las siguientes Every other page All odd or even pages, depending on the current page being odd or even. Todas las demás The current page will be included. La página actual será incluida. Selected pages Página seleccionada Use Ctrl+Click / Shift+Click to select multiple pages. Usar Ctrl+Click / Shift+Click para seleccionar múltiples páginas. Every other selected page Todas las demás seleccionadas OrientationOptionsWidget Form Formulario Rotate Rotar ... ... Reset Restablecer Scope Extensión Apply to ... Aplicar a... OutOfMemoryDialog Out of memory Sin memoria disponible Out of Memory Situation in Scan Tailor Sin memoria disponible 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? ¿Tienes que corregir los DPI de tu imagen original? ¿Estás seguro que los valores ingresados son 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. Algunas veces las imágenes pueden tener un número de DPI equivocado desde su origen. Scan Tailor intenta detectarlos, pero no es siempre una tarea fácil. Quizás necesites tildar "Corregir DPI aun si parece estar bien" cuando creas un proyecto y miras en la pestaña de "Todas las páginas" en el dialogo de "DPI fijo", 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. ¿Es muy grande el DPI de salida? Normalmente no necesitas más que 600. What can help Que puede ayudar 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 DPI. Aprenda aqui <a href="http://vimeo.com/12524529">como estimar DPI desconocido</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 su sistema operativo son 64-bits, considere cambiarse a la version de Scan Tailor de 64-bits. 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. Cuando trabaje con imágenes en escala de grises, asegurese que son realmente grises. Si son imágenes a color que casualmente parecen grises, conviértalas a escala de grises usando algún tipo de conversor en lote. Esto ahorrara memoria e incrementará 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. En ultima instancia, puede ahorrar algo de memoria asegurándose que las vistas en miniatura son pre-creadas en vez de ser creadas automáticamente. Esto puede lograrse desplazando lentamente la lista de miniaturas de arriba hacia abajo antes de comenzar con algún trabajo intensivo. What won't help Que no ayudará 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 ayudara aquí. La falta de RAM es compensada con el procedimiento de intercambio que genera una memoria virtual en el disco duro y hace que todo vaya más lento, pero mantiene al programa funcionando. Una situación de falta de memoria significa que nos quedamos sin direcciones de memoria disponibles, que no tiene nada que ver con la cantidad de RAM que tenga. La única manera de incrementar las direcciones de memoria es pasarse a un hardware de 64-bits, un sistema operativo de 64-bits y el Scan Tailor de 64-bits. Save Project Guardar proyecto Save Project As ... Guardar el proyecto como... Don't Save No guardar Project Saved Successfully Proyecto guardado satisfactoriamente 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. Por favor note que mientras que Scan Tailor intenta capturar algo de memoria y le da la oportunidad de salvar su proyecto, esto no es siempre posible. Esta vez ha resultado con éxito, pero puede que la próxima no sea así. Scan Tailor Projects Proyectos de Scan Tailor Error Error Error saving the project file! Se produjo un error al intentar guardar el proyecto! OutputApplyColorsDialog Apply Mode Modo de aplicar Apply to Aplicar a This page only (already applied) Solamente esta página (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. Usar Ctrl+Click / Shift+Click para seleccionar múltiples páginas. OutputChangeDewarpingDialog Apply Dewarping Mode Aplicar modo de antideformación Mode Modo Off Apagado Auto (experimental) Automático (experimental) Manual Manual Scope Extensión This page only Solamente esta página 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. Usar Ctrl+Clic / Shift+Clic para seleccionar múltiples páginas. OutputChangeDpiDialog Apply Output Resolution Aplicar resolución de salida DPI DPI Scope Extensión This page only Solamente esta página 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. Usar Ctrl+Clic / Shift+Clic para seleccionar múltiples páginas. All pages Todas las páginas OutputOptionsWidget Form Formulario Output Resolution (DPI) Resolución de salida (DPI) Change ... Cambiar... Mode Modo White margins Márgenes blancos Equalize illumination Emparejar iluminación Dewarping Antideformación Depth perception Percepción de profundidad No despeckling No eliminar manchas Apply To ... Aplicar a... Despeckling Eliminar manchas Cautious despeckling Eliminado de manchas cuidadoso ... ... Normal despeckling Eliminado de manchas normal Aggressive despeckling Eliminado de manchas agresivo Thinner Más fino Thicker Más grueso PageLayoutApplyDialog Apply to Aplicar a This page only (already applied) Solamente esta página (ya aplicado) All pages Todas las páginas This page and the following ones Esta página y las siguientes Every other page All odd or even pages, depending on the current page being odd or even. Todas las páginas pares o impares, dependiendo de la página actual es par o impar The current page will be included. La página actual será inluida. Selected pages Página seleccionada Use Ctrl+Click / Shift+Click to select multiple pages. Usar Ctrl+Clic / Shift+Clic para seleccionar múltiples páginas. Every other selected page Cada otra página seleccionada PageLayoutOptionsWidget Form Formulario Margins Márgenes Millimeters (mm) Milimetros (mm) Inches (in) Pulgadas (in) Top Arriba ... ... Bottom Abajo Left Izquierda Right Derecha Apply To ... Aplicar a... Alignment Alineación Match size with other pages Igualar en tamaño con las demás páginas PageSplitModeDialog Split Pages Dividir páginas Mode Modo Auto Auto Manual Manual Scope Extensión This page only Solamente esta página 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. Usar Ctrl+Clic / Shift+Clic para seleccionar múltiples páginas. All pages Todas las páginas PageSplitOptionsWidget Form Formulario Page Layout Diseño de página ? ? Change ... Cambiar... Split Line need context Línea divisoria Auto Auto Manual Manual PictureZonePropDialog Zone Properties Propiedades de zona Subtract from all layers Restar de todas las capas Add to auto layer Agregar a la capa automática Subtract from auto layer Restar de la capa automática ProjectFilesDialog Input Directory Carpeta de entrada Output Directory Carpeta de salida Error Error No files in project! No hay archivos en el proyecto! Input directory is not set or doesn't exist. La carpeta de entrada no está configurado o no existe. Input and output directories can't be the same. Las carpetas de entrada y de salida no pueden ser las mismas. Create Directory? ¿Crear carpeta? Output directory doesn't exist. Create it? La carpeta de salida no existe. ¿Desea crearla? Unable to create output directory. Imposible crear la carpeta de salida. Output directory is not set or doesn't exist. La carpeta de salida no está configurada 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. Algunos de los archivos fallaron al cargar. O es un formato no soportado, o están corruptos. Debería quitarlos del proyecto. Project Files Archivos del proyecto Browse Navegar Files Not In Project Archivos 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> i think here is a problem. >> >> <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> i think here is a problem << << Files In Project Archivos en proyecto Right to left layout (for Hebrew and Arabic) Distribución de derecha a izquierda (para idiomas Hebreo y Arabe) Fix DPIs, even if they look OK Corregir DPI, incluso si se ve bien ProjectOpeningContext Error Error Unable to interpret the project file. Imposible de interpretar el archivo del proyecto. RelinkingDialog Relinking Revincular Undo Deshacer ... ... Substitution File for %1 Substitution Directory for %1 This change would merge several files into one. RemovePagesDialog Remove Pages Quitar páginas Remove %1 page(s) from project? ¿Eliminar %1 página(s) del proyecto? Corresponding output files will be deleted, while input files will remain. Los archivos correspondientes de salida serán borrados, mientras que los archivos de entrada se mantendrán. SelectContentApplyDialog Select Content Seleccione contenido Apply to Aplicar a This page only (already applied) Solo esta página (ya aplicado) All pages Todas las páginas This page and the following ones Esta página y las siguientes Every other page All odd or even pages, depending on the current page being odd or even. what should i translate? Todas las pares o impares The current page will be included. La página actual será incluida. Selected pages Páginas seleccionadas Use Ctrl+Click / Shift+Click to select multiple pages. Usar Ctrl+Clic / Shift+Clic para seleccionar múltiples páginas. Every other selected page Todas las demás seleccionadas SelectContentOptionsWidget Form Formulario Content Box Caja de contenido Auto Auto Manual Manual Scope Extensión Apply to ... Aplicar a... SettingsDialog Compiled without OpenGL support. Compilado sin soporte OpenGL. Your hardware / driver don't provide the necessary features. Su Hardware / driver no califican con las caracteristicas necesarias. Settings Ajustes Use 3D acceleration for user interface Usar aceleración 3D para la interfase de usuario StageListView Launch batch processing Comenzar procesado de archivo por lotes SystemLoadWidget Form Formulario System load Carga del sistema ... ... Minimal Mínimo Normal Normal ThumbnailSequence %1 (page %2) %1 (página %2) ZoneContextMenuInteraction Properties Propiedades Delete Eliminar Delete confirmation Confirmación de eliminación Really delete this zone? ¿Realmente desea eliminar esta zona? ZoneCreationInteraction Click to finish this zone. ESC to cancel. Haga clic para finalizar esta zona. ESC para cancelar. Connect first and last points to finish this zone. ESC to cancel. Conecte el primer y último punto para terminar esta zona. ESC para cancelar. Zones need to have at least 3 points. ESC to cancel. Las zonas necesitan al menos 3 puntos. ESC para cancelar. ZoneDefaultInteraction Drag the vertex. Arrastre el vértice. Click to create a new vertex here. Haga clic para crear un nuevo vértice. Right click to edit zone properties. Haga clic derecho para editar las propiedades de la zona. Click to start creating a new picture zone. Haga clic para crear una nueva zona de imagen. ZoneVertexDragInteraction Merge these two vertices. Unir estos dos vértices. Move the vertex to one of its neighbors to merge them. Mueva el vértice hacia el vértice más próximo para unirlos. deskew::Filter Deskew Alineación deskew::ImageView Use Ctrl+Wheel to rotate or Ctrl+Shift+Wheel for finer rotation. Use Ctrl+Rueda para girar o Ctrl+Shift+Rueda para una rotación más precisa. Drag this handle to rotate the image. Arrastre esta asa rotar la imagen. deskew::OptionsWidget Apply Deskew Aplicar alineación fix_orientation::ApplyDialog Can't do: more than one group is selected. No se puede: has seleccionado más de un grupo. fix_orientation::Filter Fix Orientation Corregir orientación output::ChangeDpiDialog Custom Personalizar Error Error DPI is not set. No se ha configurado el valor del DPI. DPI is too low! DPI es muy pequeño! DPI is too high! DPI es muy grande! output::FillZoneEditor Pick color Elegir color output::Filter Output Salida output::OptionsWidget Black and White Blanco y negro Color / Grayscale Color / Escala de grises Mixed Combinado Apply Despeckling Level Aplicar nivel de eliminación de manchas Apply Depth Perception Aplicar profundidad de percepción Off Apagado Auto Auto Manual Manual output::Task::UiUpdater Picture zones are only available in Mixed mode. Las zona de imágenes solo están disponibles en el modo Combinado. Despeckling can't be done in Color / Grayscale mode. La eliminación de manchas no funciona en el modo Color / Escala de grises. Output Salida Picture Zones Zonas de imagen Fill Zones Zonas de relleno Dewarping Antideformación Despeckling Eliminar manchas page_layout::ApplyDialog Can't do: more than one group is selected. No se puede: has seleccionado más de un grupo. page_layout::Filter Natural order Orden natural Order by increasing width Ordenar por incremento de ancho Order by increasing height Ordenar por incremento de alto Margins Márgenes page_layout::ImageView Resize margins by dragging any of the solid lines. Modifique los márgenes arrastrando cualquiera de las líneas. page_layout::OptionsWidget Apply Margins Aplicar márgenes Apply Alignment Aplicar alineación page_split::Filter Natural order Orden natural Order by split type Ordenar por tipo de división Split Pages Dividir páginas page_split::ImageView Drag the line or the handles. Arrastre la línea o las asas. page_split::OptionsWidget Set manually Configurar manualmente Auto detected Detección automática page_split::UnremoveButton Restore removed page. Restaurar página eliminada. select_content::ApplyDialog Can't do: more than one group is selected. No se puede: has seleccionado más de un grupo. select_content::Filter Natural order Orden natural Order by increasing width Ordenar por incremento de ancho Order by increasing height Ordenar por incremento de alto Select Content Seleccione contenido select_content::ImageView Use the context menu to enable / disable the content box. Use el menu contextual para habilitar / deshabilitar la caja de contenido. Drag lines or corners to resize the content box. Arrastre las líneas o esquinas para modificar la caja de contenido. Create Content Box Crear Caja de contenido Remove Content Box Eliminar Caja de contenido scantailor-RELEASE_0_9_12_2/translations/scantailor_fr.ts000066400000000000000000002733201271170121200234050ustar00rootroot00000000000000 AboutDialog About Scan Tailor À propos de Scan Tailor About À propos 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 recogrnition is out of scope of this project. Scan Tailor est un logiciel de post-traitement de pages scannées. Il permet de scinder les pages, de les redresser, d'ajouter ou supprimer des marges, etc. Vous chargez des scans bruts et vous obtenez des pages prêtes à être imprimées ou assemblées en un fichier PDF ou DJVU. La numérisation des pages et l'OCR ne font pas parti de ce projet. 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 Auteurs Lead Developer Programmeur Principal Joseph Artsimovich Joseph Artsimovich U235 - Picture auto-detection algorithm. Robert B. - First generation dewarping algorithm. Andrey Bergman - System load adjustment. Petr Kovář - Command line interface. Neco Torquato - Brazilian Portuguese Svetoslav Sashkov, Mandor - Bulgarian Petr Kovář - Czech Stefan Birkner - German Angelo Gemmi - Italian Masahiro Kitagawa - Japanese Patrick Pascal - French Daniel Koć - Polish Joseph Artsimovich - Russian Marián Hvolka - Slovak Flavio Benelli - Spanish Davidson Wang - Traditional Chinese Yuri Chornoivan - Ukrainian denver 22 Translators Traducteurs Svetoslav Sashkov, Mandor - Bulgarian Stefan Birkner - German Angelo Gemmi - Italian Masahiro Kitagawa - Japanese Patrick Pascal - French Joseph Artsimovich - Russian Davidson Wang - Traditional Chinese Svetoslav Sashkov, Mandor - version bulgare Stefan Birkner - German Angelo Gemmi - Italian Masahiro Kitagawa - version japonaise Patrick Pascal - version française Joseph Artsimovich - version russe Davidson Wang - Traditional Chinese Contributors Contributeurs U235 - Picture auto-detection algorithm. Robert B. - Dewarping algorithm. Andrey Bergman - System load adjustment. U235 - Picture auto-detection algorithm. Robert B. - Dewarping algorithm. Andrey Bergman - System load adjustment. Documentation Documentation denver 22 phaedrus Taxman denver 22 phaedrus Taxman License Licence BatchProcessingLowerPanel Form Form Beep when finished Bipper quand la tâche est accomplie ColorPickupInteraction Click on an area to pick up its color, or ESC to cancel. Cliquer sur un espace pour obtenir sa couleur, ou ECHAP pour quitter. DeskewApplyDialog Apply to Appliquer à This page only (already applied) Cette page uniquement (déjà appliqué) All pages Toutes les pages This page and the following ones Cette page et les suivantes Selected pages Les pages sélectionnées Use Ctrl+Click / Shift+Click to select multiple pages. DeskewOptionsWidget Form Form Deskew Redresser Auto Auto Manual Manuel Apply To ... Appliquer à ... DragHandler Unrestricted dragging is possible by holding down the Shift key. Un glissement sans restrictions est possible en pressant la touche majuscule. ErrorWidget Form Form ImageLabel ImageLabel TextLabel TextLabel FixDpiDialog Need Fixing Réglage Nécessaire All Pages Toutes les pages DPI is too large and most likely wrong. DPI trop élevé et probablement mauvais. DPI is too small. Even if it's correct, you are not going to get acceptable results with it. DPI top faible. Même si c'est correcte, vous n'allez pas obtenir un résultat satisfaisant. DPI is too small for this pixel size. Such combination would probably lead to out of memory errors. DPI trop faible pour cette taille de pixel. Une telle combinaison va probablement aboutir à des erreurs de mémoire. %1 (page %2) %1 (page %2) Fix DPI Fixer le DPI Tab 1 Tab 1 Tab 2 Tab 2 DPI DPI Custom Personnaliser x x Apply Appliquer FixDpiSinglePageDialog Fix DPI Fixer le DPI DPI for %1 DPI pour %1 Custom Personnaliser x x %1 (page %2) %1 (page %2) DPI is too large and most likely wrong. DPI trop élevé et probablement mauvais. DPI is too small. Even if it's correct, you are not going to get acceptable results with it. DPI top faible. Même si c'est correcte, vous n'allez pas obtenir un résultat satisfaisant. DPI is too small for this pixel size. Such combination would probably lead to out of memory errors. DPI trop faible pour cette taille de pixel. Une telle combinaison va probablement aboutir à des erreurs de mémoire. ImageViewBase Use the mouse wheel or +/- to zoom. When zoomed, dragging is possible. Utilisez la molette de la souris ou + / - pour zoomer. Une fois le zoom effectué, vous pourrez glisser-déplacer l'image. InteractiveXSpline Click to create a new control point. This point can be dragged. Drag this point or delete it by pressing Del or D. LoadFileTask The following file could not be loaded: %1 Le fichier suivant ne peut être chargé: %1 The following file doesn't exist:<br>%1<br><br>Use the <a href="#relink">Relinking Tool</a> to locate it. LoadFilesStatusDialog Some files failed to load Loaded successfully: %1 Failed to load: %1 MainWindow Save Project Sauvegarder le Projet Save this project? Sauvegarder ce projet ? Insert before ... Insérer avant ... Insert after ... Insérer après ... Remove from project ... Retirer du projet ... Insert here ... Insérer ici ... Scan Tailor Projects Projets Scan Tailor Open Project Ouvrir un projet Error Erreur Unable to open the project file. Impossible d'ouvrir le.fichier du projet. The project file is broken. Le fichier du projet est endommagé. 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". La sortie n'est pas encore possible, car la taille final des pages n'est pas connue. Pour la déterminer, lancez le processus de traitement à l'étape "Sélectionner le contenu" ou "Marges". Unnamed Sans Nom %2 - Scan Tailor %3 [%1bit] Files to insert Skip failed files Remove %1 - Scan Tailor %2 %1 - Scan Tailor %2 Error saving the project file! Erreur lors de la sauvegarde du fichier du projet ! File to insert Fichier à insérer Images not in project (%1) Images non présente dans le projet (%1) Error opening the image file. Erreur à l'ouverture du fichier de l'image. MainWindow MainWindow Keep current page in view. Maintenir l'affichage de la page courante. Use Home, End, PgUp (or Q), PgDown (or W) to navigate between pages. Utiliser Home, Fin, PgUP (ou Q), PgDown (ou W) pour naviguer parmi les pages. Tools Outils File Fichier Help Aide Debug Debug Debug Mode Ctrl+S Ctrl+S Save Project As ... Enregistrer le projet sous ... Next Page Page suivante PgDown PgDown Previous Page Page précédente PgUp PgUp New Project ... Nouveau Projet ... Ctrl+N Ctrl+N Open Project ... Ouvrir un Projet ... Ctrl+O Ctrl+O Q Q W W Close Project Fermer le Projet Ctrl+W Ctrl+W Quit Quitter Ctrl+Q Ctrl+Q First Page Première Page Home Home Last Page Dernière Page End Fin About À propos Fix DPI ... Relinking ... Stop batch processing Arrêter le traitement Settings ... Paramètres ... NewOpenProjectPanel Form Form New Project ... Nouveau Projet ... Open Project ... Ouvrir un Projet ... Recent Projects Projets récents OrientationApplyDialog Fix Orientation Fixer l'orientation Apply to Appliquer à This page only (already applied) Cette page uniquement (déjà appliqué) All pages Toutes les pages This page and the following ones Cette page et les suivantes Every other page All odd or even pages, depending on the current page being odd or even. Toutes les pages paires ou impaires, suivant que la page courante soit paire ou impaire Toutes les autres pages The current page will be included. Page courante incluse. Selected pages Les pages sélectionnées Use Ctrl+Click / Shift+Click to select multiple pages. Utilisez Ctrl+Clic / MAJ+Click pour sélectionner plusieurs pages. Every other selected page Toutes les autres pages sélectionnées OrientationOptionsWidget Form Form Rotate Rotation ... ... Reset Réinitialiser Scope Cible Apply to ... Appliquer à ... 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 Sauvegarder le Projet Save Project As ... Enregistrer le projet sous ... 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 Projets Scan Tailor Error Erreur Error saving the project file! Erreur lors de la sauvegarde du fichier du projet ! OutputApplyColorsDialog Apply Mode Mode d'application Apply to Appliquer à This page only (already applied) Cette page uniquement (déjà appliqué) All pages Toutes les pages This page and the following ones Cette page et les suivantes Selected pages Les pages sélectionnées Use Ctrl+Click / Shift+Click to select multiple pages. Utilisez Ctrl+Clic / MAJ+Click pour sélectionner plusieurs pages. OutputChangeDewarpingDialog Apply Dewarping Mode Mode Mode Off Auto (experimental) Manual Manuel Scope Cible This page only Cette page uniquement All pages Toutes les pages This page and the following ones Cette page et les suivantes Selected pages Les pages sélectionnées Use Ctrl+Click / Shift+Click to select multiple pages. OutputChangeDpiDialog Apply Output Resolution Appliquer la résolution de sortie DPI DPI Scope Cible This page only Cette page uniquement This page and the following ones Cette page et les suivantes Selected pages Les pages sélectionnées Use Ctrl+Click / Shift+Click to select multiple pages. Utilisez Ctrl+Clic / MAJ+Click pour sélectionner plusieurs pages. All pages Toutes les pages OutputOptionsWidget Form FORM Output Resolution (DPI) Résolution de sortie (DPI) Change ... Changer ... Mode White margins Marges blanches Equalize illumination Régler l'éclairage Dewarping Depth perception No despeckling Pas d'élimination du bruit Dewarp Dewarp Apply To ... Appliquer à ... Despeckling Élimination du bruit Cautious despeckling Légère élimination du bruit ... ... Normal despeckling Élimination du bruit standard Aggressive despeckling Élimination aggressive du bruit Thinner Éclaircir Thicker Épaissir PageLayoutApplyDialog Apply to Appliquer à This page only (already applied) Cette page uniquement (déjà appliqué) All pages Toutes les pages This page and the following ones Cette page et les suivantes Every other page All odd or even pages, depending on the current page being odd or even. Toutes les autres pages The current page will be included. Page courante incluse. Selected pages Les pages sélectionnées Use Ctrl+Click / Shift+Click to select multiple pages. Utilisez Ctrl+Clic / MAJ+Clic pour sélectionner plusieurs pages. Every other selected page Toutes les autres pages sélectionnées PageLayoutOptionsWidget Form Form Margins Définir les Marges Millimeters (mm) Millimètres (mm) Inches (in) Pouces (in) Top Haut ... ... Bottom Bas Left Gauche Right Droite Apply To ... Appliquer à ... Alignment Alignement Match size with other pages Faire correspondre la taille avec les autres les pages PageSplitModeDialog Split Pages Scinder les pages Mode Mode Auto Auto Manual Manuel Scope Cible This page only Cette page uniquement This page and the following ones Cette page et les suivantes Selected pages Les pages sélectionnées Use Ctrl+Click / Shift+Click to select multiple pages. Utilisez Ctrl+Clic / MAJ+Click pour sélectionner plusieurs pages. All pages Toutes les pages PageSplitOptionsWidget Form Form Page Layout Mise en page ? ? Change ... Modifier ... Split Line Ligne de découpe Auto Auto Manual Manuel PictureZonePropDialog Zone Properties Propriétés de la zone Subtract from all layers Soustraire de toutes les couches Add to auto layer Ajouter aux couches auto Subtract from auto layer Soustraire des couches auto ProjectFilesDialog Input Directory Répertoire d'entrée Output Directory Répertoire de sortie Error Erreur No files in project! Aucun fichier dans le projet ! Input directory is not set or doesn't exist. Le répertoire d'entrée n'est pas défini ou n'existe pas. Input and output directories can't be the same. Le répertoire d'entrée et de sortie ne peuvent être le même. Create Directory? Créer un Dossier ? Output directory doesn't exist. Create it? Le répertoire de sortie n'existe pas. Le créer ? Unable to create output directory. Impossible de créer le répertoire de sortie. Output directory is not set or doesn't exist. Le répertoire de sortie n'est pas défini ou n'existe pas. 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. L'importation de certains fichiers a échouée. Soit leurs formats n'est pas supporté soit ils sont corrompus. Vous devrier les retirer du projet. Project Files Fichiers du Projet Browse Explorer Files Not In Project Fichiers hors projet Select All Tout sélectionner <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;">Ajouter les fichiers sélectionnés au Projet.</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;">Supprimer les fichiers sélectionnés du projet.</p></body></html> << << Files In Project Fichiers du projet Right to left layout (for Hebrew and Arabic) Pagination de Droite à Gauche (pour l'Hébreux et l'Arabe) Fix DPIs, even if they look OK Fixer les DPI, même si cela semble OK ProjectOpeningContext Error Erreur Unable to interpret the project file. Impossible d'interprêter le fichier projet. RelinkingDialog Relinking Undo ... ... Substitution File for %1 Substitution Directory for %1 This change would merge several files into one. RemovePagesDialog Remove Pages Retirer des pages Remove %1 page(s) from project? Retirer %1 page(s) du projet ? Corresponding output files will be deleted, while input files will remain. Les fichiers de sortie correspondants vont être effacés, les fichiers d'entrée seront conservés. SelectContentApplyDialog Select Content Sélectionner le contenu Apply to Appliquer à This page only (already applied) Cette page uniquement (déjà appliqué) All pages Toutes les pages This page and the following ones Cette page et les suivantes Every other page All odd or even pages, depending on the current page being odd or even. Toutes les autres pages The current page will be included. Page courante incluse. Selected pages Les pages sélectionnées Use Ctrl+Click / Shift+Click to select multiple pages. Every other selected page Toutes les autres pages sélectionnées SelectContentOptionsWidget Form Form Content Box Boite de contenu Auto Auto Manual Manuel Scope Cible Apply to ... Appliquer à ... SettingsDialog Compiled without OpenGL support. Compilé sans le support OpenGL. Your hardware / driver don't provide the necessary features. Votre matériel / driver n'a pas les caractéristiques requises. Settings Paramètres Use 3D acceleration for user interface Utilise l'accélération 3D pour l'interface StageListView Launch batch processing Lancer le traitement automatique SystemLoadWidget Form Form System load Charge système ... ... Minimal Minimal Normal Normal ThumbnailSequence %1 (page %2) %1 (page %2) ZoneContextMenuInteraction Properties Propriétés Delete Supprimer Delete confirmation Confirmer la suppression Really delete this zone? Vraiment supprimer cette zone ? ZoneCreationInteraction Click to finish this zone. ESC to cancel. Cliquer pour finir cette zone. ECHAP pour quitter. Connect first and last points to finish this zone. ESC to cancel. Connecter le premier et le dernier point pour finir cette zone. ECHAP pour quitter. Zones need to have at least 3 points. ESC to cancel. Les Zones doivent avoir obligatoirement 3 points. ECHAP pour quitter. ZoneDefaultInteraction Drag the vertex. Faites glisser le "Sommet (Vertex)". Click to create a new vertex here. Cliquer ici pour créer un nouveau "sommet (vertex)". Right click to edit zone properties. Clic-droit pour éditer les propriétés de la zone. Click to start creating a new picture zone. Cliquer pour débuter la création d'une nouvelle zone image. ZoneVertexDragInteraction Merge these two vertices. Fusionner ces deux "sommets (vertices)". Move the vertex to one of its neighbors to merge them. Déplacez le "sommet (vertex)" vers l'un de ces voisins pour les fusionner. deskew::Filter Deskew Redresser deskew::ImageView Use Ctrl+Wheel to rotate or Ctrl+Shift+Wheel for finer rotation. Utiliser Ctrl+molette souris pour effectuer la rotation ou Ctrl+MAJ+molette souris pour une rotation plus fine. Drag this handle to rotate the image. Faites glisser cette poignée pour faire pivoter l'image. deskew::OptionsWidget Apply Deskew fix_orientation::ApplyDialog Can't do: more than one group is selected. Impossible d'appliquer : plus d'un groupe est sélectionné. fix_orientation::Filter Fix Orientation Fixer l'Orientation output::ChangeDpiDialog Custom Personnaliser Error Erreur DPI is not set. Le DPI n'est pas défini. DPI is too low! DPI trop faible ! DPI is too high! DPI trop élevé ! output::FillZoneEditor Pick color Choisir la couleur output::Filter Output Sortie output::OptionsWidget Black and White Noir et blanc Color / Grayscale Couleur / Niveaux de gris Mixed Mixte Apply Despeckling Level Appliquer le réglage d'élimination du bruit Apply Depth Perception Off Auto Auto Manual Manuel output::Task::UiUpdater Picture zones are only available in Mixed mode. Les zones images sont uniquement disponnible dans le mode Mixte. Despeckling can't be done in Color / Grayscale mode. l'élimination du bruit ne peut être effectuée en mode Couleur / Niveau de gris. Output Sortie Picture Zones Zone images Fill Zones Zones de remplissage Dewarping Despeckling Élimination du bruit page_layout::ApplyDialog Can't do: more than one group is selected. Impossible d'appliquer : plus d'un groupe est sélectionné. page_layout::Filter Natural order Ordre naturel Order by increasing width Trier par largeur croissante Order by increasing height Trier par hauteur croissante Margins Définir les Marges page_layout::ImageView Resize margins by dragging any of the solid lines. Redimensionnez les marges en faisant glisser les traits pleins. page_layout::OptionsWidget Apply Margins Appliquer les Marges Apply Alignment Appliquer l'Alignement page_split::Filter Natural order Ordre naturel Order by split type Split Pages Scinder les Pages page_split::ImageView Drag the line or the handles. Faites glisser la ligne de découpe ou les poignées. page_split::OptionsWidget Set manually Définir manuellement Auto detected Détection automatique page_split::UnremoveButton Restore removed page. Rétablir la page supprimée. select_content::ApplyDialog Can't do: more than one group is selected. Impossible d'appliquer : plus d'un groupe est sélectionné. select_content::Filter Natural order Ordre naturel Order by increasing width Trier par largeur croissante Order by increasing height Trier par hauteur croissante Select Content Sélectionner le contenu select_content::ImageView Use the context menu to enable / disable the content box. Utilisez le menu contextuel (clic droit) pour créer / supprimer la "boite de contenu". Drag lines or corners to resize the content box. Faites glisser les lignes ou les coins pour redimensionner la "boite de contenu". Create Content Box Créer une "boite de contenu" Remove Content Box Supprimer la "boite de contenu" scantailor-RELEASE_0_9_12_2/translations/scantailor_hr.ts000066400000000000000000002631431271170121200234110ustar00rootroot00000000000000 AboutDialog About Scan Tailor O Scan Tailor-u About O programu 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 je interaktivni post-processing alat za obradu skeniranih stranica. On izvodi operacije kao Å¡to su dijeljenje duplih stranice, ispravljanje iskrivljenja i dodavanje / uklanjanje margine, i drugo. Možete mu dati sirovu skeniranu stranicu, i dobiti ćete stranice spremne za ispis ili stavljanje u PDF ili DJVU datoteke. Skeniranje i optiÄko prepoznavanje znakova ne dolazi u okviru ovog projekta. Authors Autori Lead Developer Glavni programer Joseph Artsimovich Joseph Artsimovich U235 - Picture auto-detection algorithm. U234 - Auto-detekcijski algoritam slika. Robert B. - First generation dewarping algorithm. Robert B. -Prva generacija dewarping algoritama. Andrey Bergman - System load adjustment. Andrey Bergman - PodeÅ¡avanje opterećenja sustava. Petr Kovář - Command line interface. Petr Kovář - SuÄelje komandne linije. Neco Torquato - Brazilian Portuguese Neco Torquato - Brazilski Portugalski Svetoslav Sashkov, Mandor - Bulgarian Svetoslav Sashkov, Mandor - Bugarski Petr Kovář - Czech Petr Kovář - ÄŒeÅ¡ki Stefan Birkner - German Marián Hvolka - SlovaÄki Angelo Gemmi - Italian Angelo Gemmi - Talijanski Masahiro Kitagawa - Japanese Masahiro Kitagawa - Japanski Patrick Pascal - French Patrick Pascal - Francuski Daniel Koć - Polish Daniel Koć - Poljski Joseph Artsimovich - Russian Joseph Artsimovich - Ruski Marián Hvolka - Slovak Marián Hvolka - SlovaÄki Flavio Benelli - Spanish Flavio Benelli - Å panjolski Davidson Wang - Traditional Chinese Davidson Wang - Kineski Tradicionalni Yuri Chornoivan - Ukrainian Yuri Chornoivan - Ukrajinski denver 22 denver 22 Translators Prevodioci Contributors Suradnici Documentation Dokumentacija License Dozvola BatchProcessingLowerPanel Form Oblik Beep when finished ZvuÄni signal kada je gotovo ColorPickupInteraction Click on an area to pick up its color, or ESC to cancel. Klikni na željeno mjesto da izbereÅ¡ njenu boju, ili ESC za otkazivanje. DeskewApplyDialog Apply to Primijeni na This page only (already applied) Samo ova stranica (već primijenjeno) All pages Sve stranice This page and the following ones Ova stranica i koje slijede Selected pages Odbrane stranice Use Ctrl+Click / Shift+Click to select multiple pages. Koristite Ctrl+Click / Shift+Click za izbor viÅ¡e stranica. DeskewOptionsWidget Form Oblik Deskew Poravnanje Auto Auto Manual RuÄno Apply To ... Primijeni na... DragHandler Unrestricted dragging is possible by holding down the Shift key. NeograniÄeno povlaÄenje je moguće držeći pritisnutu tipku Shift. ErrorWidget Form Oblik FixDpiDialog Fix DPI Popravi DPI Tab 1 Tablica 1 Tab 2 Tablica 2 DPI DPI Custom PrilagoÄ‘eno x x Apply Prihvati Need Fixing Potrebno popraviti All Pages Sve stranice DPI is too large and most likely wrong. DPI je preveliki, izgleda kao da je krivi. DPI is too small. Even if it's correct, you are not going to get acceptable results with it. DPI je premali. ÄŒak i ako je ispravan, s njim nećete dobiti prihvatljivi rezultat. DPI is too small for this pixel size. Such combination would probably lead to out of memory errors. DPI je premali za ovu veliÄinu piksela. Takva kombinacija najvjerojatnije će uzrokovati stanje izvan memorije. %1 (page %2) %1 (stranica %2) ImageViewBase Use the mouse wheel or +/- to zoom. When zoomed, dragging is possible. Koristite kotaÄić miÅ¡a ili + / - da bi povećali ili smanjili. Kad zumiranja, moguće je povlaÄenje. InteractiveXSpline Click to create a new control point. Kliknite za stvaranje nove toÄke. This point can be dragged. Ova toÄka se može povlaÄitii. Drag this point or delete it by pressing Del or D. Povucite ovu toÄku ili ju možete ukloniti pritiskom na tipku Del ili D. LoadFileTask The following file could not be loaded: %1 Sljedeću datoteku nije moguće uÄitati: %1 The following file doesn't exist:<br>%1<br><br>Use the <a href="#relink">Relinking Tool</a> to locate it. Sljedeća datoteka ne postoji:<br>%1<br><br>Koristite <a href="#relink"> Alat Ponovno pronaÄ‘i</a> kako bi ga pronaÅ¡li. LoadFilesStatusDialog Some files failed to load Neke datoteke se nisu uÄitale Loaded successfully: %1 UÄitano uspjeÅ¡no:%1 Failed to load: %1 Nije uÄitano:%1 MainWindow Save Project Spremi projekt MainWindow Glavni prozor Keep current page in view. Zadrži pogled na trenutnoj stranici. Use Home, End, PgUp (or Q), PgDown (or W) to navigate between pages. Koristite Home, End, PgUp (or Q), PgDown (or W) za kretanje po stranicama. Tools Alati File Datoteka Help Pomoć Debug Mode Debug naÄin Ctrl+S Ctrl+S Save Project As ... Spremi projekt kao... Next Page Slijedeća stranica PgDown Dolje Previous Page Prethodna stranica PgUp Gore New Project ... Novi projekt... Ctrl+N Ctrl+N Open Project ... Otvori projekt... Ctrl+O Ctrl+O Q Q W W Close Project Zatvori projekt Ctrl+W Ctrl+W Quit Izlaz Ctrl+Q Ctrl+Q First Page Prva stranica Home PoÄetak Last Page Zadnja stranica End Kraj About O programu Fix DPI ... Popravi DPI ... Relinking ... Ponovno pronaÄ‘i... Settings ... Postavke... Stop batch processing Zaustavite skupne obrade Save this project? Spremiti ovaj projekt? Insert before ... Umetni prije... Insert after ... Umetni poslije... Remove from project ... Ukloni iz projekta... Insert here ... Umetni ovdije... Scan Tailor Projects Scan Tailor projekti Open Project Otvori projekt Error GreÅ¡ka Unable to open the project file. Ne mogu otvoriti projekt datoteku. The project file is broken. Datoteka projekta je neispravna. 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". Izlaz joÅ¡ nije moguć jer konaÄna veliÄina stranice nije poznata. Da bi to odredili, pokrenite skupne obrade u "Odredi sadržaja" ili "Margine". Unnamed Nema imena %2 - Scan Tailor %3 [%1bit] %2 - Scan Tailor %3 [%1bit] Error saving the project file! GreÅ¡ka spremanja projekt datpteke! Files to insert Datoteke ze umetnuti Images not in project (%1) Slike koje nisu u projektu (%1) Skip failed files PreskoÄi neispravne datoteke Remove Ukloni NewOpenProjectPanel Form Oblik New Project ... Novi projekt... Open Project ... Otvori projekt... Recent Projects Nedavni projekt OrientationApplyDialog Fix Orientation Orijentacija Apply to Primijeni na This page only (already applied) Samo ova stranica (već primijenjeno) All pages Sve stranice This page and the following ones Ova stranica i sve koje slijede Every other page All odd or even pages, depending on the current page being odd or even. sve parane ili neparane stranice ovise o trenutnoj stranici, je lii parana ili neparana Sve druge stranice The current page will be included. Trenutna stranica će biti ukljuÄena. Selected pages Odbrane stranice Use Ctrl+Click / Shift+Click to select multiple pages. Koristite Ctrl+Click / Shift+Click za izbor viÅ¡e stranica. Every other selected page Sve druge odabrane stranice OrientationOptionsWidget Form Oblik Rotate Rotiraj ... ... Reset Vrati Scope PodruÄje Apply to ... Primijeni na... OutOfMemoryDialog Out of memory Izvan memorije Out of Memory Situation in Scan Tailor Scan Tailor izvan memorije Possible reasons Mogući razlozi Did you have to fix the DPI of your source images? Are you sure the values you entered were correct? Jeste li popraviti DPI svojih izvornih slika? Jeste li sigurni da su vrijednosti koje ste unijeli je ispravane? 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. Ponekad vaÅ¡e izvorne slike mogu imati krivo ugraÄ‘en DPI. Scan Tailor ih pokuÅ¡ava pronaći, ali to nije uvijek lako izvesti. Možda ćete morati potvrditi "Fix DPI, Äak i ako oni izgledaju normalno" prilikom izrade projekta i potvrditi "sve stranice" na kartici "Popravite DPI", koji je takoÄ‘er dostupan na izborniku Alati. Is your output DPI set too high? Usually you don't need it higher than 600. Je li vaÅ¡ izlazni DPI postavljen previsoko? ObiÄno ne trebate veći od 600. What can help Å to može pomoći 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. Popravi DPI. NauÄi kako <a href="http://vimeo.com/12524529">procijeniti nepoznatu DPI</a>. If your hardware and operating system are 64-bit capable, consider switching to a 64-bit version of Scan Tailor. Ako vaÅ¡ hardver i operativni sustav su 64-bitni, razmislite da prijeÄ‘ete na 64-bitnu verziju Scan Tailor-a. 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. U radu sa slikama u sivim tonovima, uvjerite se da su stvarno slike usivim tonovima. Ako su oni zapravo slike u boji koje samo izgledaju u sivim tonovima, treba ih pretvoriti u sive tonove koristeći neku vrstu pretvaraÄa slikea. Time će se uÅ¡tediti memorija i povećati uÄinkovitost. 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. Kao posljednji izbor, možete uÅ¡tedjeti neÅ¡to memorije tako da koristite minijature unaprijed izraÄ‘ene, nego da se stvaraju na zahtjev. To možete napraviti sporim pomicanjem po minijaturama popisa sve od vrha do dona prije poÄetka bilo kojeg rada. What won't help Ono Å¡to neće pomoći 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. IznenaÄ‘ujuće nadogradnja RAM-a vam neće pomoći ovdje.Nedostatak RAM-a nadomjeÅ¡ta swap mehanizma, Å¡to sve usporava, ali drži program da radi. Stanje nedostatka memorije znaÄi da je ponestalo memorijskog adresnog prostora, Å¡to nema nikakve veze s koliÄinom RAM-a koju imate. Jedini naÄin da se poveća prostor memorijske adrese je da ide 64-bitni hardver, 64-bitni operacijski sustav i 64-bitne Scan Tailor. Save Project Spremi projekt Save Project As ... Spremi projekt kao... Don't Save Ne spramaj Project Saved Successfully Projekt je uspjeÅ¡no spremljen 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. Imajte na umu, Scan Tailor pokuÅ¡ava uhvatiti stanje prije stanja nedostatka memorije i dati vam priliku da spremite svoj ​​projekt, to nije uvijek moguće. Ovaj put je to uspio, ali idući put se može sruÅ¡iti. Scan Tailor Projects Scan Tailor projekti Error GreÅ¡ka Error saving the project file! GreÅ¡ka spremanja projekt datpteke! OutputApplyColorsDialog Apply Mode Primijena Apply to Primijeni na This page only (already applied) Samo ova stranica (već primijenjeno) All pages Sve stranice This page and the following ones Ova stranica i koje slijede Selected pages Odbrane stranice Use Ctrl+Click / Shift+Click to select multiple pages. Koristite Ctrl+Click / Shift+Click za izbor viÅ¡e stranica. OutputChangeDewarpingDialog Apply Dewarping Mode Prihvati naÄin ispravljanja Mode NaÄin Off IskljuÄeno Auto (experimental) Auto (probno) Manual RuÄno Scope PodruÄje This page only Samo ova stranica All pages Sve stranice This page and the following ones Ova stranica i sve koje slijede Selected pages Odabrane stranice Use Ctrl+Click / Shift+Click to select multiple pages. Koristite Ctrl+Click / Shift+Click za izbor viÅ¡e stranica. OutputChangeDpiDialog Apply Output Resolution Prihvati izlaznu rezoluciju DPI DPI Scope PodruÄje This page only Samo ova stranica This page and the following ones Ova stranica i sve koje slijede Selected pages Odbrane stranice Use Ctrl+Click / Shift+Click to select multiple pages. Koristite Ctrl+Click / Shift+Click za izbor viÅ¡e stranica. All pages Sve stranice OutputOptionsWidget Form Oblik Output Resolution (DPI) Izlazna rezolucija (DPI) Change ... Promijeni... Mode NaÄin White margins Bijele margine Equalize illumination IzjednaÄi osvjetljenje Dewarping Poravnanje (Warp) Depth perception Dubina percepcije No despeckling Bez Äišćenja Apply To ... Primijeni na... Despeckling ÄŒišćenje Cautious despeckling ÄŒišćenje - oprezno ... ... Normal despeckling ÄŒišćenje - normalno Aggressive despeckling ÄŒišćenje - jako Thinner Tanje Thicker Deblje PageLayoutApplyDialog Apply to Primijeni na This page only (already applied) Samo ova stranica (već primijenjeno) All pages Sve stranice This page and the following ones Ova stranica i koje slijede Every other page All odd or even pages, depending on the current page being odd or even. Sve parne i neparne stranice ovise o tome je li trenutna stranica parna ili neparna. Sve druge stranice The current page will be included. Trenutna stranica će biti ukljuÄena. Selected pages Odbrane stranice Use Ctrl+Click / Shift+Click to select multiple pages. Koristite Ctrl+Click / Shift+Click za izbor viÅ¡e stranica. Every other selected page Sve druge odabrane stranice PageLayoutOptionsWidget Form Oblik Margins Margine Millimeters (mm) Milimetara (mm) Inches (in) InÄa (in) Top Vrh ... ... Bottom Dno Left Lijevo Right Desno Apply To ... Primijeni na... Alignment Poravnanje Match size with other pages Prilagodi veliÄinu ostalim stranicama PageSplitModeDialog Split Pages Podijela stranica Mode NaÄin Auto Auto Manual RuÄno Scope PodruÄje This page only Samo ova stranica This page and the following ones Ova stranica i koje slijede Selected pages Izabrane stranice Use Ctrl+Click / Shift+Click to select multiple pages. Koristite Ctrl+Click / Shift+Click za izbor viÅ¡e stranica. All pages Sve stranice PageSplitOptionsWidget Form Oblik Page Layout Izgled stranice ? ? Change ... Promijeni... Split Line Crta podjele Auto Auto Manual RuÄno PictureZonePropDialog Zone Properties Svojstva zone Subtract from all layers Oduzmi od svih slojeva Add to auto layer Dodaj u automatski sloj Subtract from auto layer Oduzmi od automatskog sloja ProjectFilesDialog Input Directory Ulazna putanja Output Directory Izlazna putanja Project Files Datoteke projekta Browse Pretraži Files Not In Project Datoteke koje nisu u projektu Select All Izaberi sve <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;">Dodaj izabrane datoteke u projekt.</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;">Ukloni izabrane datoteke iz projekta.</p></body></html> << << Files In Project Datoteke u projektu Right to left layout (for Hebrew and Arabic) Desno-lijevo raspored (za Hebrejski i Arapski) Fix DPIs, even if they look OK Popravi DPI, iako izgledaju OK Error GreÅ¡ka No files in project! Nema datoteka u projektu! Input directory is not set or doesn't exist. Izlazna putanja nije izabrana ili ne postoji. Input and output directories can't be the same. Izlazna i ulazna putanja ne mogu biti iste. Create Directory? Stvoriti putanju? Output directory doesn't exist. Create it? Izlazna putanja ne postoji. Stvoriti ju? Unable to create output directory. Ne mogu stvoriti izlaznu putanj. Output directory is not set or doesn't exist. Izlazna putanja nije izabrana ili ne postoji. 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. Neke datoteke nisu uÄitane. Ili mi ne podržavamo njihov format, ili su neispravne. Trebali bi ih uklonili iz projekta. ProjectOpeningContext Error GreÅ¡ka Unable to interpret the project file. Ne mogu interpretirati projekt datoteku. RelinkingDialog Relinking Ponovo pronaÄ‘i Undo PoniÅ¡ti ... ... Substitution File for %1 Zamjena datoteke za %1 Substitution Directory for %1 Zamjena putanje za %1 This change would merge several files into one. Ova promijena će spojiti nekoliko datoteka u jednu. RemovePagesDialog Remove Pages Ukloni stranice Remove %1 page(s) from project? Ukloniti %1 stranicu(e) iz projekta? Corresponding output files will be deleted, while input files will remain. Odgovarajuće izlazne datoteke biti će izbrisane, dok će ulazne datoteke ostati. SelectContentApplyDialog Select Content Odaberi sadržaj Apply to Primijeni na This page only (already applied) Samo ova stranica (već primijenjeno) All pages Sve stranice This page and the following ones Ova stranica i sve koje slijede Every other page All odd or even pages, depending on the current page being odd or even. Sve parne ili neparne stranice ovise je li ova stranica parna ili neparna Sve druge stranice The current page will be included. Trenutna stranica će biti ukljuÄena. Selected pages Odbrane stranice Use Ctrl+Click / Shift+Click to select multiple pages. Koristite Ctrl+Click / Shift+Click za izbor viÅ¡e stranica. Every other selected page Sve druge odabrane stranice SelectContentOptionsWidget Form Oblik Content Box Sadržajni okvir Auto Auto Manual RuÄno Scope PodruÄje Apply to ... Primijeni na... SettingsDialog Settings Postavke Use 3D acceleration for user interface Koristitite 3D ubrzanje za korisniÄko suÄelje Compiled without OpenGL support. Sastavljen bez OpenGL podrÅ¡ke. Your hardware / driver don't provide the necessary features. VaÅ¡ hardware / driver ne osigurava potrebne znaÄajke. StageListView Launch batch processing Pokrenite skupne obrade SystemLoadWidget Form Oblik System load Opterećenja sustava ... ... Minimal Minimalno Normal Normalno ThumbnailSequence %1 (page %2) %1 (stranica %2) ZoneContextMenuInteraction Properties Svojstva Delete ObriÅ¡i Delete confirmation Potvrda brisanja Really delete this zone? Stvarno izbrisati ovu zonu? ZoneCreationInteraction Click to finish this zone. ESC to cancel. Klikni za zavrÅ¡etak zone. ESC za otkazati. Connect first and last points to finish this zone. ESC to cancel. Spojite prvu i zadnju toÄku za zavrÅ¡etak zone. ESC za otkazati. Zones need to have at least 3 points. ESC to cancel. Zona mora imati najmanje tri toÄke. ESC za otkazati. ZoneDefaultInteraction Drag the vertex. Povucite vrh. Click to create a new vertex here. Kliknite da bi stvorili novi vrh ovdje. Right click to edit zone properties. Desni klik za ureÄ‘ivanje svojstava zone. Click to start creating a new picture zone. Kliknite za poÄetak stvaranja nove zone slike. ZoneVertexDragInteraction Merge these two vertices. Spojite ta dva vrha. Move the vertex to one of its neighbors to merge them. Pomaknite vrh do najbližeg da bi se spojili. deskew::Filter Deskew Poravnanje deskew::ImageView Use Ctrl+Wheel to rotate or Ctrl+Shift+Wheel for finer rotation. Koristite Ctrl + KotaÄić za okretanje ili Ctrl + Shift +KotaÄić za joÅ¡ osjetljivije okretanje. Drag this handle to rotate the image. Povucite toÄku za rotaciju slike. deskew::OptionsWidget Apply Deskew Prihvati poravnanje fix_orientation::ApplyDialog Can't do: more than one group is selected. Ne mogu napraviti: izabrano je viÅ¡e od jedne grupe. fix_orientation::Filter Fix Orientation Orijentacija output::ChangeDpiDialog Custom PrilagoÄ‘eno Error GreÅ¡ka DPI is not set. DPI nije postavljen. DPI is too low! DPI je premali! DPI is too high! DPI je preveliki! output::FillZoneEditor Pick color Izaberi boju output::Filter Output Izlaz output::OptionsWidget Black and White Crno - bijelo Color / Grayscale Kolor / Sivi tonovi Mixed MijeÅ¡ano Apply Despeckling Level Prihvati razinu Äišćenja Apply Depth Perception Prihvati dubinu percepcije Off IskljuÄeno Auto Auto Manual RuÄno output::Task::UiUpdater Picture zones are only available in Mixed mode. Zone slike su jedino moguće u mjeÅ¡anom naÄinu. Despeckling can't be done in Color / Grayscale mode. ÄŒišćenje se ne može koristiti kod slika u boji / u sivim tonovima. Output Izlaz Picture Zones Zone slike Fill Zones Popuni zone Dewarping Poravnanje Despeckling ÄŒišćenje page_layout::ApplyDialog Can't do: more than one group is selected. Ne mogu napraviti: izabrano je viÅ¡e od jedne grupe. page_layout::Filter Natural order Prirodni redoslijed Order by increasing width Poredaj po povećanju Å¡irine Order by increasing height Poredaj po povećanju visine Margins Margine page_layout::ImageView Resize margins by dragging any of the solid lines. Promijeni veliÄinu margina povlaÄenjem bilo koje pune linije. page_layout::OptionsWidget Apply Margins Prihvati margine Apply Alignment Prihvati poravnanje page_split::Filter Natural order Prirodni redoslijed Order by split type Redoslijed po naÄinu dijeljenja Split Pages Podijela stranica page_split::ImageView Drag the line or the handles. Povuci crtu ili toÄke. page_split::OptionsWidget Set manually Postavi ruÄno Auto detected Automatsko otkrivanje page_split::UnremoveButton Restore removed page. Vrati uklonjene stranice. select_content::ApplyDialog Can't do: more than one group is selected. Ne mogu napraviti: izabrano je viÅ¡e od jedne grupe. select_content::Filter Natural order Prirodni redoslijed Order by increasing width Poredaj po povećanju Å¡irine Order by increasing height Poredaj po povećanju visine Select Content Odredi sadržaj select_content::ImageView Use the context menu to enable / disable the content box. Upotrijebite kontekstni izbornik za ukljuÄivanje / iskljuÄivanje la sadržajnog okvira. Drag lines or corners to resize the content box. Povucite crte ili kutove za promjenu veliÄine sadržajnog okvira. Create Content Box Stvori sadržajni okvir Remove Content Box Ukloni sadržajni okvir scantailor-RELEASE_0_9_12_2/translations/scantailor_it.ts000066400000000000000000003007071271170121200234120ustar00rootroot00000000000000 AboutDialog About Scan Tailor a proposito di Scan Tailor About a proposito di... 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 recogrnition is out of scope of this project. Scan Tailor è uno strumento di post-elaborazione per pagine scansionate. Effettua operazioni quali la suddivisione a metà delle pagine, la correzione dell'angolatura, l'aggiunta/rimozione dei margini, ed altro. Voi gli fornite una scansione grezza, e ne ottenete pagine pronte per essere assemblate in un PDF o in un file DJVU. La scansione ed il riconoscimento ottico dei caratteri esulano dallo scopo di questo progetto. 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 Autori Lead Developer Sviluppatore Capo Joseph Artsimovich Joseph Artsimovich U235 - Picture auto-detection algorithm. Robert B. - First generation dewarping algorithm. Andrey Bergman - System load adjustment. Petr Kovář - Command line interface. Neco Torquato - Brazilian Portuguese Svetoslav Sashkov, Mandor - Bulgarian Petr Kovář - Czech Stefan Birkner - German Angelo Gemmi - Italian Masahiro Kitagawa - Japanese Patrick Pascal - French Daniel Koć - Polish Joseph Artsimovich - Russian Marián Hvolka - Slovak Flavio Benelli - Spanish Davidson Wang - Traditional Chinese Yuri Chornoivan - Ukrainian denver 22 Translators Traduttori Svetoslav Sashkov, Mandor - Bulgarian Stefan Birkner - German Angelo Gemmi - Italian Masahiro Kitagawa - Japanese Patrick Pascal - French Joseph Artsimovich - Russian Davidson Wang - Traditional Chinese Svetoslav Sashkov, Mandor - Bulgarian Stefan Birkner - German Angelo Gemmi - Italian Masahiro Kitagawa - Japanese Patrick Pascal - French Joseph Artsimovich - Russian Davidson Wang - Traditional Chinese Contributors Contributori U235 - Picture auto-detection algorithm. Robert B. - Dewarping algorithm. Andrey Bergman - System load adjustment. U235 - Algoritmo di autorilevamento dell'immagine. Robert B. - Algoritmo di correzione deformazioni. Andrey Bergman - regolazione carico di sistema. Documentation Documentazione denver 22 phaedrus Taxman denver 22 phaedrus Taxman License Licenza BatchProcessingLowerPanel Form Beep when finished Segnale sonoro ad operazione ultimata ColorPickupInteraction Click on an area to pick up its color, or ESC to cancel. Clicca su di un'area per prelevarne il suo proprio colore, o ESC per cancellare. DeskewApplyDialog Apply to Applica a This page only (already applied) Solo questa pagina (già applicato) All pages This page and the following ones Selected pages Pagine selezionate Use Ctrl+Click / Shift+Click to select multiple pages. Usa Ctrl+Click / Shift+Click per selezionare più pagine. DeskewOptionsWidget Form Deskew Raddrizza Auto Automatico Manual Manuale Apply To ... Applica a... DragHandler Unrestricted dragging is possible by holding down the Shift key. Il trascinamento libero è possibile tenendo premuto il tasto shift. ErrorWidget Form FixDpiDialog Need Fixing Necessita correzione All Pages Tutte le pagine DPI is too large and most likely wrong. il DPI è eccessivo e probabilmente errato. DPI is too small. Even if it's correct, you are not going to get acceptable results with it. Il DPI è troppo basso. Anche se fosse corretto,non otterrete un risultato accettabile con un tale valore. DPI is too small for this pixel size. Such combination would probably lead to out of memory errors. Il DPI è troppo basso per pixel di queste dimensioni. Una simile combinazione potrebbe con tutta probabilità condurre ad errori di memoria esaurita. %1 (page %2) %1 (pagina %2) Fix DPI Correggi DPI Tab 1 Tab 2 DPI Custom Personalizzato x Apply Applica FixDpiSinglePageDialog Fix DPI Correggi DPI DPI for %1 DPI per %1 Custom Personalizzato %1 (page %2) %1 (pagina %2) DPI is too large and most likely wrong. Il DPI è eccessivo e probabilmente errato. DPI is too small. Even if it's correct, you are not going to get acceptable results with it. Il DPI è troppo basso. Anche se fosse corretto,non otterrete un risultato accettabile con un tale valore. DPI is too small for this pixel size. Such combination would probably lead to out of memory errors. Il DPI è troppo basso per pixel di queste dimensioni. Una simile combinazione potrebbe con tutta probabilità condurre ad errori di memoria esaurita. ImageViewBase Use the mouse wheel or +/- to zoom. When zoomed, dragging is possible. Usa la rotellina del mouse o i tasti +/- per lo zoom. In fase di zoom, il trascinamento è possibile. Unrestricted dragging is possible by holding down the Shift key. Il trascinamento libero è possibile tenendo premuto il tasto shift. InteractiveXSpline Click to create a new control point. This point can be dragged. Drag this point or delete it by pressing Del or D. LoadFileTask The following file could not be loaded: %1 Impossibile caricare il seguente file: %1 The following file doesn't exist:<br>%1<br><br>Use the <a href="#relink">Relinking Tool</a> to locate it. LoadFilesStatusDialog Some files failed to load Loaded successfully: %1 Failed to load: %1 MainWindow Beep when finished Segnale sonoro ad operazione ultimata Save Project Salva progetto Save this project? Salvo questo progetto? Insert before ... Inserisci prima... Insert after ... Inserisci dopo... Remove from project ... Rimuovi dal progetto... Insert here ... Inserisci qui ... Scan Tailor Projects Progetto Scan Tailor Open Project Apri progetto Error Errore Unable to open the project file. Impossibile aprire il file del progetto. The project file is broken. Il file del progetto è danneggiato. 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". Non è ancora possibile produrre i file di output, in quanto la dimensione finale delle pagine non è ancora nota. Per determinarla, avvia l'elaborazione automatica a "Seleziona contenuto" o "Margini". Files to insert Skip failed files 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 "Page Layout". Non è ancora possibile produrre i file di output, in quanto la dimensione finale delle pagine non è ancora nota. Per determinarla, avvia l'elaborazione automatica a " o " Conformazione pagina". Unnamed Senza nome %2 - Scan Tailor %3 [%1bit] Error saving the project file! Errore nel salvataggio del file del progetto! File to insert File da inserire Images not in project (%1) Immagini non nel progetto (%1) Error opening the image file. Errore nell'apertura del file immagine. %1 (page %2) %1 (pagina %2) Remove Rimuovi Unable to delete file: %1 Impossibile eliminare il file: %1 MainWindow Keep current page in view. Tieni la pagina correnta in vista. Use PgUp, PgDown or Q, W to navigate between pages. Usa PgUp/PgDown o i tasti Q/W per navigare tra le pagine. Use Home, End, PgUp (or Q), PgDown (or W) to navigate between pages. Usa Home, End, PgUp (o Q), PgDown (o W) per navigare tra le pagine. Tools Strumenti File File Help Aiuto Debug Debug Debug Mode Ctrl+S Save Project As ... Salva il progetto come... Next Page Prossima pagina PgDown Previous Page Pagina precedente PgUp New Project ... Nuovo progetto... Ctrl+N Open Project ... Apri progetto... Ctrl+O Q W Close Project Chiudi progetto Ctrl+W Quit Esci Ctrl+Q First Page Prima pagina Home Last Page Ultima pagina End About A proposito di... Fix DPI ... Relinking ... Stop batch processing Arresta l'elaborazione automatica Settings ... Impostazioni ... NewOpenProjectPanel Form New Project ... Nuovo progetto... Open Project ... Apri progetto... Recent Projects Progetti recenti OrientationApplyDialog Fix Orientation Correggi orientamento Apply to Applica a This page only (already applied) Solo questa pagina (già applicato) All pages Tutte le pagine This page and the following ones Questa pagina e le seguenti Every other page All odd or even pages, depending on the current page being odd or even. Ogni altra pagina The current page will be included. La pagina corrente sarà inmclusa. Selected pages Pagine selezionate Use Ctrl+Click / Shift+Click to select multiple pages. Usa Ctrl+Click / Shift+Click per selezionare più pagine. Every other selected page Ogni altra pagina selezionata OrientationOptionsWidget Form Rotate Ruota ... ... Reset Resetta Scope Apply to ... Applica a... 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 Salva progetto Save Project As ... Salva il progetto come... 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 Progetto Scan Tailor Error Errore Error saving the project file! Errore nel salvataggio del file del progetto! OutputApplyColorsDialog Apply Mode Modalità applica Apply to Applica a This page only (already applied) Solo questa pagina (già applicato) All pages Tutte le pagine This page and the following ones Questa pagina e le successive Selected pages Pagine selezionate Use Ctrl+Click / Shift+Click to select multiple pages. Usa Ctrl+Click / Shift+Click per selezionare più pagine. OutputChangeDewarpingDialog Apply Dewarping Mode Mode Modalità Off Auto (experimental) Manual Manuale Scope This page only Solo questa pagina All pages This page and the following ones Selected pages Pagine selezionate Use Ctrl+Click / Shift+Click to select multiple pages. Usa Ctrl+Click / Shift+Click per selezionare più pagine. OutputChangeDpiDialog Apply Output Resolution Applica risoluzione di uscita DPI Scope This page only Solo questa pagina This page and the following ones Questa pagina e le successive Selected pages Pagine selezionate Use Ctrl+Click / Shift+Click to select multiple pages. Usa Ctrl+Click / Shift+Click per selezionare più pagine. All pages Tutte le pagine OutputOptionsWidget Form Output Resolution (DPI) Risoluzione di uscita (DPI) Change ... Cambia... Mode Modalità White margins Margini bianchi Equalize illumination Equalizza illuminazione Dewarping Depth perception No despeckling No despeckling Dewarp Elimina curvatura Apply To ... Applica a... Despeckling Despeckling Cautious despeckling Despeckling prudente ... ... Normal despeckling ÐDespeckling normale Aggressive despeckling Despeckling agressivo Despeckle Despeckle Thinner Più sottile Thicker Più spesso PageLayoutApplyDialog Apply to Applica a This page only (already applied) Solo questa pagina (già applicato) All pages Tute le pagine This page and the following ones Questa pagina e le successive Every other page All odd or even pages, depending on the current page being odd or even. Ogni altra pagina The current page will be included. La pagina corrente sarà inmclusa. Selected pages Pagine selezionate Use Ctrl+Click / Shift+Click to select multiple pages. Usa Ctrl+Click / Shift+Click per selezionare più pagine. Every other selected page Ogni altra pagina selezionata PageLayoutOptionsWidget Form Margins Margini Millimeters (mm) Millimetri (mm) Inches (in) Pollici (in) Top Sopra ... ... Bottom Sotto Left Sinistra Right Destra Apply To ... Applica a... Alignment Allineamento Match size with other pages Riscontra dimensione con le altre pagine Align with other pages Allinea con le altre pagine Go to the widest page. Vai alla pagina più larga. Widest Page pagina più larga Go to the tallest page. Vai alla pagina più alta. Tallest Page Pagina più alta PageSplitModeDialog Split Pages Dividi pagine Mode Modalità Auto Automatico Manual Manuale Scope This page only Solo questa pagina This page and the following ones Questa pagina e le successive Selected pages Pagine selezionate Use Ctrl+Click / Shift+Click to select multiple pages. Usa Ctrl+Click / Shift+Click per selezionare più pagine. All pages Tutte le pagine PageSplitOptionsWidget Form Page Layout Conformazione pagina ? ? Change ... Cambia... Split Line Linea di taglio Auto Automatico Manual Manuale ... ... PictureZonePropDialog Zone Properties Proprietà zona Subtract from all layers Sottrai da tutti i livelli Add to auto layer Aggiungi al livello automatico Subtract from auto layer Sottrai al livello automatico ProjectFilesDialog Input Directory Cartella sorgente Output Directory Cartella di destinazione Error Errore No files in project! Nessun file nel progetto! Input directory is not set or doesn't exist. La cartella sorgente non è impostata o non esiste. Input and output directories can't be the same. Le cartelle sorgenti e di destinazione, non possono essere le stesse. Create Directory? Creo cartella? Output directory doesn't exist. Create it? La cartella di destinazione non esiste. Devo crearla? Unable to create output directory. Impossibile creare la cartella di destinazione. Output directory is not set or doesn't exist. la cartella di destinazione non è impostata o non esiste. 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. Alcuni dei file non si è riusciti a caricarli. O il loro formato non è supportato oppure sono danneggiati. È necessario rimuoverli dal progetto. Project Files File del progetto Browse Sfoglia Files Not In Project File non nel progetto Select All Seleziona tutto <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;">Aggiungi i file selezionati al progetto.</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;">Rimuovi i file selezionati dal progetto.</p></body></html> << << Files In Project File nel progetto Right to left layout (for Hebrew and Arabic) Da destra a sinistra (per Ebraico e Arabo) Fix DPIs, even if they look OK Correggi il DPI, anche se appare a posto ProjectOpeningContext Error Errore Unable to interpret the project file. Impossibile interpretare il file del progetto. RelinkingDialog Relinking Undo ... ... Substitution File for %1 Substitution Directory for %1 This change would merge several files into one. RemoveFileDialog Remove File Rimuovi file Remove %1 from project? Rimuovo %1 dal progetto? Also delete the file from disk Eliminali anche dal disco RemovePagesDialog Remove Pages Rimuovi pagine Remove %1 page(s) from project? Rimuovo %1 pagina(e) dal progetto? Corresponding output files will be deleted, while input files will remain. I corrispondenti file risultanti saranno eliminati, mantenuti invece quelli sorgenti. Files will remain on disk I file rimarrano sul disco SelectContentApplyDialog Select Content Seleziona contenuto Apply to Applica a This page only (already applied) Solo questa pagina (già applicato) All pages This page and the following ones Every other page All odd or even pages, depending on the current page being odd or even. Ogni altra pagina The current page will be included. La pagina corrente sarà inmclusa. Selected pages Pagine selezionate Use Ctrl+Click / Shift+Click to select multiple pages. Usa Ctrl+Click / Shift+Click per selezionare più pagine. Every other selected page Ogni altra pagina selezionata SelectContentOptionsWidget Form Content Box Riquadro contenuto Auto Automatico Manual Manuale Scope Apply to ... Applica a... SettingsDialog Compiled without OpenGL support. Compilato con supporto OpenGL. Your hardware / driver don't provide the necessary features. Il tuo hardware/driver non supporta le caratteristiche necessarie. Settings Impostazioni Use 3D acceleration for user interface Utilizza accelerazione 3D per l'interfaccia utente StageListView Launch batch processing Lancia elaborazione automatica SystemLoadWidget Form System load Carico di sistema ... Minimal Minimale Normal Normale ThumbnailSequence %1 (page %2) %1 (pagina %2) ZoneContextMenuInteraction Properties Proprietà Delete Elimina Delete confirmation Conferma di eliminazione Really delete this zone? Eliminare davvero questa zona? ZoneCreationInteraction Click to finish this zone. ESC to cancel. Clicca per terminare questa zona. ESC per cancellare. Connect first and last points to finish this zone. ESC to cancel. Unisci il primo e l'ultimo punto per completare la zona. ESC per cancellare. Zones need to have at least 3 points. ESC to cancel. Le zone devono avere almeno tre punti. ESC per cancellare. ZoneDefaultInteraction Drag the vertex. Trascina il vertice. Click to create a new vertex here. Clicca per creare un nuovo vertice qui. Right click to edit zone properties. Click destro per modificare le proprietà della zona. Click to start creating a new picture zone. Clicca per avviare la creazione di una nuova zona. ZonePropertiesDialog Zone Properties Proprietà zona Subtract from all layers Sottrai da tutti ilivelli Add to auto layer Aggiungi al livello automatico Subtract from auto layer Sottrai al livello automatico ZoneVertexDragInteraction Merge these two vertices. Fondi questi due vertici. Move the vertex to one of its neighbors to merge them. Sposta il vertice ad uno dei suoi vicini per fonderli. deskew::Filter Deskew Raddrizza deskew::ImageView Use Ctrl+Wheel to rotate or Ctrl+Shift+Wheel for finer rotation. Usa Ctrl+rotellina per ruotare Ctrl+Shift+rotellina per una rotazione più accurata. Drag this handle to rotate the image. Trascina questo per ruotare l'immagine. deskew::OptionsWidget Apply Deskew fix_orientation::ApplyDialog Can't do: more than one group is selected. Impossibile effettuare l'azione: più di un gruppo selezionato. fix_orientation::Filter Fix Orientation Correggi orientamento output::ChangeDpiDialog Custom Personalizzato Error Errore DPI is not set. ÐIl DPI non è impostato. DPI is too low! Il DPI è troppo basso! DPI is too high! Il DPI è troppo alto! output::FillZoneEditor Pick color Seleziona colore output::Filter Output Output output::OptionsWidget Black and White Bianco e nero Color / Grayscale Colore / Scala di grigi Mixed Misto Apply Despeckling Level Applica livello di despeckling Apply Depth Perception Off Auto Automatico Manual Manuale output::Task::UiUpdater Picture zones are only available in Mixed mode. Le zone immagini sonop disponibili solo nella modalità mista. Despeckling can't be done in Color / Grayscale mode. Il despeckling non può essere attuato in modalità colore / scala di grigi. Output Output Picture Zones Zone immagini Fill Zones Zone di riempimento Dewarping Despeckling Despeckling page_layout::ApplyDialog Can't do: more than one group is selected. Impossibile effettuare l'azione: più di un gruppo selezionato. page_layout::Filter Page Layout Conformazione pagina Natural order Ordine naturale Order by increasing width Ordina per larghezza crescente Order by increasing height Ordina per altezza crescente Margins Margini page_layout::ImageView Resize margins by dragging any of the solid lines. Ridimensiona margini utilizzando una delle linee continue. page_layout::OptionsWidget Apply Margins Applica margini Apply Alignment Applica allineamento page_split::Filter Natural order Ordine naturale Order by split type Split Pages Dividi pagine page_split::ImageView Drag this handle to skew the line. Trascina questa maniglia per deformare la linea. This line can be dragged. Questa linea può essere trascinata. Drag the line or the handles. Trascinare la linea o le maniglie. page_split::OptionsWidget Set manually Imposta manualmente Auto detected Rilevato automaticamente page_split::UnremoveButton Restore removed page. Ripristina pagina rimossa. select_content::ApplyDialog Can't do: more than one group is selected. Impossibile effettuare l'azione: più di un gruppo selezionato. select_content::Filter Natural order Ordine naturale Order by increasing width Ordina per larghezza crescente Order by increasing height Ordina per altezza crescente Select Content Seleziona contenuto select_content::ImageView Use the context menu to enable / disable the content box. Usa il menu contestuale per abilitar/disabilitare il riquadro contenuto. Drag lines or corners to resize the content box. Trascina le linee o o bordi per ridimensionare il riquadro contenuto. Create Content Box Crea riquadro contenuto Remove Content Box Rimuovi riquadro contenuto scantailor-RELEASE_0_9_12_2/translations/scantailor_ja.ts000066400000000000000000003232521271170121200233700ustar00rootroot00000000000000 AboutDialog About Scan Tailor Scan Tailor ã«ã¤ã„㦠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 recogrnition is out of scope of this project. Scan Tailor ã¯ã‚¹ã‚­ãƒ£ãƒ³ã•れãŸãƒšãƒ¼ã‚¸ã‚’対話的ã«ãƒã‚¹ãƒˆãƒ—ロセッシングã™ã‚‹ãƒ„ールã§ã™ã€‚ページ分割・傾ã補正・余白ã®è¿½åŠ ï¼é™¤åŽ»ãªã©ã®æ“作ãŒã§ãã¾ã™ã€‚スキャンã•れãŸç”Ÿãƒ‡ãƒ¼ã‚¿ã‹ã‚‰ã€å°åˆ·ã—ãŸã‚Š PDF ã‚„ DJVU ファイルã«ã¾ã¨ã‚ãŸã‚Šã™ã‚‹ã®ã«å‘ã„ãŸãƒšãƒ¼ã‚¸ã‚’作るã“ã¨ãŒã§ãã¾ã™ã€‚スキャン処ç†ãŠã‚ˆã³å…‰å­¦æ–‡å­—èªè­˜ã¯ã“ã®ãƒ—ロジェクトã®å¯¾è±¡ç¯„囲外ã§ã™ã€‚ 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 U235 - Picture auto-detection algorithm. U235 - ç”»åƒè‡ªå‹•é¸æŠžã‚¢ãƒ«ã‚´ãƒªã‚ºãƒ  Robert B. - First generation dewarping algorithm. Robert B. - 第1ä¸–ä»£ã®æ­ªã¿è£œæ­£ã‚¢ãƒ«ã‚´ãƒªã‚ºãƒ  Andrey Bergman - System load adjustment. Andrey Bergman - システム負è·èª¿æ•´ Petr Kovář - Command line interface. Petr Kovář - コマンドラインインタフェース Neco Torquato - Brazilian Portuguese Svetoslav Sashkov, Mandor - Bulgarian Svetoslav Sashkov, Mandor - ブルガリア語 Petr Kovář - Czech Petr Kovář - ãƒã‚§ã‚³èªž Stefan Birkner - German Stefan Birkner - ドイツ語 Angelo Gemmi - Italian Angelo Gemmi - イタリア語 Masahiro Kitagawa - Japanese 北å·é›…裕 - 日本語 Patrick Pascal - French Patrick Pascal - フランス語 Daniel Koć - Polish Daniel Koć - ãƒãƒ¼ãƒ©ãƒ³ãƒ‰èªž Joseph Artsimovich - Russian Joseph Artsimovich - ロシア語 Marián Hvolka - Slovak Marián Hvolka - スロãƒã‚­ã‚¢èªž Flavio Benelli - Spanish Flavio Benelli - スペイン語 Davidson Wang - Traditional Chinese Davidson Wang - ç¹ä½“字中国語 Yuri Chornoivan - Ukrainian Yuri Chornoivan - ウクライナ語 denver 22 denver 22 <!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:8pt;">U235 - Picture auto-detection algorithm.</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:8pt;">Robert B. - First generation dewarping algorithm.</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:8pt;">Andrey Bergman - System load adjustment.</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:8pt;">Petr Kovář - Command line interface.</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:8pt;">U235 - ç”»åƒè‡ªå‹•é¸æŠžã‚¢ãƒ«ã‚´ãƒªã‚ºãƒ </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:8pt;">Robert B. - æ­ªã¿è£œæ­£ã‚¢ãƒ«ã‚´ãƒªã‚ºãƒ </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:8pt;">Andrey Bergman - システム負è·èª¿æ•´</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:8pt;">Petr Kovář - コマンドラインインタフェース</span></p></body></html> Translators 翻訳者 Svetoslav Sashkov, Mandor - Bulgarian Petr Kovář - Czech Stefan Birkner - German Angelo Gemmi - Italian Masahiro Kitagawa - Japanese Patrick Pascal - French Daniel Koć - Polish Joseph Artsimovich - Russian Marián Hvolka - Slovak Davidson Wang - Traditional Chinese Svetoslav Sashkov, Mandor - ブルガリア語 Petr Kovář - ãƒã‚§ã‚³èªž Stefan Birkner - ドイツ語 Angelo Gemmi - イタリア語 北å·é›…裕 - 日本語 Patrick Pascal - フランス語 Daniel Koć - ãƒãƒ¼ãƒ©ãƒ³ãƒ‰èªž Joseph Artsimovich - ロシア語 Marián Hvolka - スロãƒã‚­ã‚¢èªž Davidson Wang - ç¹ä½“字中国語 Svetoslav Sashkov, Mandor - Bulgarian Stefan Birkner - German Angelo Gemmi - Italian Masahiro Kitagawa - Japanese Patrick Pascal - French Daniel Koć - Polish Joseph Artsimovich - Russian Davidson Wang - Traditional Chinese Svetoslav Sashkov, Mandor - ブルガリア語 Stefan Birkner - ドイツ語 Angelo Gemmi - イタリア語 北å·é›…裕 - 日本語 Patrick Pascal - フランス語 Joseph Artsimovich - ロシア語 Davidson Wang - ç¹ä½“字中国語 Svetoslav Sashkov, Mandor - Bulgarian Stefan Birkner - German Angelo Gemmi - Italian Masahiro Kitagawa - Japanese Patrick Pascal - French Joseph Artsimovich - Russian Davidson Wang - Traditional Chinese Svetoslav Sashkov, Mandor - ブルガリア語 Stefan Birkner - ドイツ語 Angelo Gemmi - イタリア語 北å·é›…裕 - 日本語 Patrick Pascal - フランス語 Joseph Artsimovich - ロシア語 Davidson Wang - ç¹ä½“字中国語 Contributors 貢献者 U235 - Picture auto-detection algorithm. Robert B. - Dewarping algorithm. Andrey Bergman - System load adjustment. U235 - ç”»åƒè‡ªå‹•é¸æŠžã‚¢ãƒ«ã‚´ãƒªã‚ºãƒ  Robert B. - æ­ªã¿è£œæ­£ã‚¢ãƒ«ã‚´ãƒªã‚ºãƒ  Andrey Bergman - システム負è·èª¿æ•´ Documentation ドキュメント化 License ライセンス BatchProcessingLowerPanel Form Beep when finished 終了時ã«ãƒ“ープ音を鳴ら㙠ColorPickupInteraction Click on an area to pick up its color, or ESC to cancel. クリックã™ã‚‹ã¨ãã®éƒ¨åˆ†ã®è‰²ã‚’抽出ã—ã¾ã™ã€‚ESC を押ã™ã¨ã‚­ãƒ£ãƒ³ã‚»ãƒ«ã—ã¾ã™ã€‚ DeskewApplyDialog 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+クリック ã§è¤‡æ•°ãƒšãƒ¼ã‚¸ã‚’é¸æŠžã§ãã¾ã™ã€‚ DeskewOptionsWidget Form Deskew 傾ãを修正 Auto 自動 Manual 手動 Apply To ... é©ç”¨å…ˆ... DragHandler Unrestricted dragging is possible by holding down the Shift key. ã©ã“ã¾ã§ã‚‚ドラッグã™ã‚‹ã«ã¯ Shift キーを押ã—ãŸã¾ã¾ã«ã—ã¾ã™ã€‚ ErrorWidget ImageLabel ç”»åƒãƒ©ãƒ™ãƒ« TextLabel 文章ラベル Form FixDpiDialog 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 ページ) Fix DPI DPI を訂正 Tab 1 タブ 1 Tab 2 タブ 2 DPI DPI Custom カスタム x x Apply é©ç”¨ FixDpiSinglePageDialog Fix DPI DPI を訂正 DPI for %1 %1 ã® DPI Custom カスタム x x %1 (page %2) %1 (%2 ページ) 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 ãŒå°ã•ã™ãŽã¾ã™ã€‚ãã®ã‚ˆã†ãªçµ„åˆã›ã¯ãƒ¡ãƒ¢ãƒªä¸è¶³ã‚¨ãƒ©ãƒ¼ã®åŽŸå› ã¨ãªã‚‹ã“ã¨ãŒã‚りã¾ã™ã€‚ ImageViewBase Use the mouse wheel or +/- to zoom. When zoomed, dragging is possible. マウスホイールã¾ãŸã¯ +/- キーã§ã‚ºãƒ¼ãƒ ã§ãã¾ã™ã€‚ズームã—ã¦ã„ã‚‹å ´åˆã€ãƒ‰ãƒ©ãƒƒã‚°ã§ç”»åƒã‚’å‹•ã‹ã›ã¾ã™ã€‚ InteractiveXSpline Click to create a new control point. クリックã™ã‚‹ã¨æ–°ã—ã„コントロールãƒã‚¤ãƒ³ãƒˆã‚’作æˆã§ãã¾ã™ã€‚ This point can be dragged. ã“ã®ãƒã‚¤ãƒ³ãƒˆã¯ãƒ‰ãƒ©ãƒƒã‚°ã§ãã¾ã™ã€‚ Drag this point or delete it by pressing Del or D. ã“ã®ãƒã‚¤ãƒ³ãƒˆã¯ãƒ‰ãƒ©ãƒƒã‚°ã§ãã¾ã™ã€‚Del ã¾ãŸã¯ D を押ã™ã¨å‰Šé™¤ã§ãã¾ã™ã€‚ 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. LoadFilesStatusDialog Some files failed to load ã„ãã¤ã‹ã®ãƒ•ァイルã®èª­ã¿è¾¼ã¿ã«å¤±æ•—ã—ã¾ã—ãŸã€‚ Loaded successfully: %1 読ã¿è¾¼ã¿æˆåŠŸ: %1 Failed to load: %1 読ã¿è¾¼ã¿å¤±æ•—: %1 MainWindow Beep when finished 終了時ã«ãƒ“ープ音を鳴ら㙠Save Project プロジェクトをä¿å­˜ã™ã‚‹ Save this project? プロジェクトをä¿å­˜ã—ã¾ã™ã‹ï¼Ÿ 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. プロジェクトファイルã¯ç ´æã—ã¦ã„ã¾ã™ã€‚ 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". ãƒšãƒ¼ã‚¸ã®æœ€çµ‚ã‚µã‚¤ã‚ºãŒæœªç¢ºå®šã®ãŸã‚ã€ã¾ã å‡ºåŠ›ã§ãã¾ã›ã‚“。確定ã™ã‚‹ã«ã¯ã€ã€Œç‰ˆé¢ã‚’é¸æŠžã€ã¾ãŸã¯ã€Œä½™ç™½ã€ã‹ã‚‰ä¸€æ‹¬å‡¦ç†ã‚’実行ã—ã¦ãã ã•ã„。 %2 - Scan Tailor %3 [%1bit] Files to insert Skip failed files 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 "Page Layout". ãƒšãƒ¼ã‚¸ã®æœ€çµ‚ã‚µã‚¤ã‚ºãŒæœªç¢ºå®šã®ãŸã‚ã€ã¾ã å‡ºåŠ›ã§ãã¾ã›ã‚“。確定ã™ã‚‹ã«ã¯ã€ã€Œç‰ˆé¢ã‚’é¸æŠžã€ã¾ãŸã¯ã€Œãƒšãƒ¼ã‚¸é…ç½®ã€ã‹ã‚‰ä¸€æ‹¬å‡¦ç†ã‚’実行ã—ã¦ãã ã•ã„。 Unnamed åå‰ãªã— %1 - Scan Tailor %2 %1 - Scan Tailor %2 Error saving the project file! プロジェクトファイルをä¿å­˜ã§ãã¾ã›ã‚“ï¼ File to insert 挿入ã™ã‚‹ãƒ•ァイル Images not in project (%1) プロジェクトã«å«ã¾ã‚Œãªã„ç”»åƒ (%1) Error opening the image file. ç”»åƒãƒ•ァイルを開ã‘ã¾ã›ã‚“ï¼ %1 (page %2) %1 (%2 ページ) Remove å–り除ã Unable to delete file: %1 ファイルを削除ã§ãã¾ã›ã‚“: %1 MainWindow メインウインドウ Keep current page in view. ç¾åœ¨ã®ãƒšãƒ¼ã‚¸ã‚’常ã«è¡¨ç¤ºã—ã¾ã™ã€‚ Use PgUp, PgDown or Q, W to navigate between pages. PgUp, PgDown ã¾ãŸã¯ Q, W キーã§ãƒšãƒ¼ã‚¸ã‚’移れã¾ã™ã€‚ Use Home, End, PgUp (or Q), PgDown (or W) to navigate between pages. Home, End, PgUp(ã¾ãŸã¯ Q), PgDown(ã¾ãŸã¯ W)キーã§ãƒšãƒ¼ã‚¸ã‚’移れã¾ã™ã€‚ Tools ツール File ファイル Help ヘルプ Debug デãƒãƒƒã‚° Debug Mode デãƒãƒƒã‚°ãƒ¢ãƒ¼ãƒ‰ Ctrl+S Ctrl+S Save Project As ... プロジェクトを別åã§ä¿å­˜â€¦ Next Page 次ページ PgDown PgDown Previous Page å‰ãƒšãƒ¼ã‚¸ PgUp PgUp New Project ... æ–°è¦ãƒ—ロジェクト... Ctrl+N Ctrl+N Open Project ... プロジェクトを開ã... Ctrl+O Ctrl+O Q Q W W Close Project プロジェクトを閉ã˜ã‚‹â€¦ Ctrl+W Ctrl+W Quit 終了 Ctrl+Q Ctrl+Q First Page 最åˆã®ãƒšãƒ¼ã‚¸ Home Last Page 最後ã®ãƒšãƒ¼ã‚¸ End About Fix DPI ... DPI を訂正... Relinking ... å†ãƒªãƒ³ã‚¯... Stop batch processing 一括処ç†ã‚’åœæ­¢ Settings ... 設定... NewOpenProjectPanel Form New Project ... æ–°è¦ãƒ—ロジェクト... Open Project ... プロジェクトを開ã... Recent Projects 最近ã®ãƒ—ロジェクト OrientationApplyDialog Fix Orientation å‘ãを訂正 Apply to é©ç”¨å…ˆ... This page only (already applied) ã“ã®ãƒšãƒ¼ã‚¸ã®ã¿ï¼ˆé©ç”¨æ¸ˆï¼‰ All pages 全ページ This page and the following ones ã“ã®ãƒšãƒ¼ã‚¸ä»¥é™ Every other page All odd or even pages, depending on the current page being odd or even. 1ページãŠã The current page will be included. ç¾åœ¨ã®ãƒšãƒ¼ã‚¸ã¯å«ã¾ã‚Œã¾ã™ã€‚ Selected pages é¸æŠžã•れãŸãƒšãƒ¼ã‚¸ Use Ctrl+Click / Shift+Click to select multiple pages. Ctrl+クリック ã‚„ Shift+クリック ã§è¤‡æ•°ãƒšãƒ¼ã‚¸ã‚’é¸æŠžã§ãã¾ã™ã€‚ Every other selected page é¸æŠžã•れãŸãƒšãƒ¼ã‚¸ã§1ページãŠã OrientationOptionsWidget Form Rotate 回転 ... ... Reset リセット Scope é©ç”¨ç¯„囲 Apply to ... é©ç”¨å…ˆ... OutOfMemoryDialog Out of memory メモリä¸è¶³ Out of Memory Situation in Scan Tailor 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 を訂正ã€ãƒ€ã‚¤ã‚¢ãƒ­ã‚°ã®ã€Œå…¨ã¦ã®ãƒšãƒ¼ã‚¸ã€ã‚¿ãƒ–を調ã¹ã‚‹å¿…è¦ãŒã‚ã‚‹ã§ã—ょã†ã€‚「DPI を訂正ã€ãƒ€ã‚¤ã‚¢ãƒ­ã‚°ã¯ã€Œãƒ„ールã€ãƒ¡ãƒ‹ãƒ¥ãƒ¼ã‹ã‚‰ã‚‚é–‹ã‘ã¾ã™ã€‚ Is your output DPI set too high? Usually you don't need it higher than 600. 出力 DPI を高ãã—ã™ãŽã§ã¯ãªã„ã§ã™ã‹ï¼Ÿã€€é€šå¸¸ã¯ 600 dpi 以上ã«ã™ã‚‹å¿…è¦ã¯ã‚りã¾ã›ã‚“。 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. 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. ãƒãƒ¼ãƒ‰ã‚¦ã‚§ã‚¢ã‚‚ OS ã‚‚ 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. 驚ãã¹ãã“ã¨ã«ã€RAM を増設ã—ã¦ã‚‚解決ã«ã¯ãªã‚Šã¾ã›ã‚“。RAM ã®ä¸è¶³ã¯ã‚¹ãƒ¯ãƒƒãƒ—ã«ã‚ˆã£ã¦è£œã‚れるãŸã‚ã€å‡¦ç†ãŒé…ãã¯ãªã‚‹ã‚‚ã®ã®ã€ãƒ—ログラムã¯å‹•作ã—ã¤ã¥ã‘ã¾ã™ã€‚一方ã€ãƒ¡ãƒ¢ãƒªä¸è¶³ã¨ã¯ãƒ¡ãƒ¢ãƒªã‚¢ãƒ‰ãƒ¬ã‚¹ç©ºé–“ã‚’ä½¿ã„æžœãŸã—ãŸã“ã¨ã‚’æ„味ã—ã€RAM ã®å®¹é‡ã¨ã¯ç„¡é–¢ä¿‚ã§ã™ã€‚メモリアドレス空間を増やã™å”¯ä¸€ã®æ‰‹æ®µã¯ãƒãƒ¼ãƒ‰ã‚¦ã‚§ã‚¢ã¨ OS 㨠Scan Tailor ã¨ã‚’ 64 ビット版ã«ç§»è¡Œã™ã‚‹ã“ã¨ã§ã™ã€‚ 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! プロジェクトファイルをä¿å­˜ã§ãã¾ã›ã‚“ï¼ 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+クリック ã§è¤‡æ•°ãƒšãƒ¼ã‚¸ã‚’é¸æŠžã§ãã¾ã™ã€‚ OutputChangeDewarpingDialog Apply Dewarping Mode æ­ªã¿è£œæ­£ãƒ¢ãƒ¼ãƒ‰ã‚’é©ç”¨ Mode モード Off オフ Auto (experimental) 自動(実験的) Manual 手動 Scope é©ç”¨ç¯„囲 This page only ã“ã®ãƒšãƒ¼ã‚¸ã®ã¿ All pages 全ページ This page and the following ones ã“ã®ãƒšãƒ¼ã‚¸ä»¥é™ Selected pages é¸æŠžã•れãŸãƒšãƒ¼ã‚¸ Use Ctrl+Click / Shift+Click to select multiple pages. Ctrl+クリック ã‚„ Shift+クリック ã§è¤‡æ•°ãƒšãƒ¼ã‚¸ã‚’é¸æŠžã§ãã¾ã™ã€‚ OutputChangeDpiDialog Apply Output Resolution 出力解åƒåº¦ã‚’é©ç”¨ã™ã‚‹ DPI DPI Scope é©ç”¨ç¯„囲 This page only ã“ã®ãƒšãƒ¼ã‚¸ã®ã¿ This page and the following ones ã“ã®ãƒšãƒ¼ã‚¸ä»¥é™ Selected pages é¸æŠžã•れãŸãƒšãƒ¼ã‚¸ Use Ctrl+Click / Shift+Click to select multiple pages. Ctrl+クリック ã‚„ Shift+クリック ã§è¤‡æ•°ãƒšãƒ¼ã‚¸ã‚’é¸æŠžã§ãã¾ã™ã€‚ All pages 全ページ OutputOptionsWidget Form Output Resolution (DPI) 出力解åƒåº¦ (DPI) Change ... 変更... Mode モード White margins 余白 Equalize illumination 照明をå‡ä¸€åŒ–ã™ã‚‹ Dewarping æ­ªã¿è£œæ­£ Depth perception è·é›¢æ„Ÿ No despeckling スペックル除去ãªã— Dewarp æ­ªã¿è£œæ­£ Apply To ... é©ç”¨å…ˆ... Despeckling スペックル除去 Cautious despeckling 控ãˆã‚ãªã‚¹ãƒšãƒƒã‚¯ãƒ«é™¤åŽ» ... ... Normal despeckling 標準的ãªã‚¹ãƒšãƒƒã‚¯ãƒ«é™¤åŽ» Aggressive despeckling ç©æ¥µçš„ãªã‚¹ãƒšãƒƒã‚¯ãƒ«é™¤åŽ» Despeckle スペックル除去 Thinner è–„ã Thicker 濃ã PageLayoutApplyDialog Apply to é©ç”¨å…ˆ... This page only (already applied) ã“ã®ãƒšãƒ¼ã‚¸ã®ã¿ï¼ˆé©ç”¨æ¸ˆï¼‰ All pages 全ページ This page and the following ones ã“ã®ãƒšãƒ¼ã‚¸ä»¥é™ Every other page All odd or even pages, depending on the current page being odd or even. 1ページãŠã The current page will be included. ç¾åœ¨ã®ãƒšãƒ¼ã‚¸ã¯å«ã¾ã‚Œã¾ã™ã€‚ Selected pages é¸æŠžã•れãŸãƒšãƒ¼ã‚¸ Use Ctrl+Click / Shift+Click to select multiple pages. Ctrl+クリック ã‚„ Shift+クリック ã§è¤‡æ•°ãƒšãƒ¼ã‚¸ã‚’é¸æŠžã§ãã¾ã™ã€‚ Every other selected page é¸æŠžã•れãŸãƒšãƒ¼ã‚¸ã§1ページãŠã PageLayoutOptionsWidget Form Margins 余白 Millimeters (mm) ミリメートル (mm) Inches (in) インム(in) Top 上 ... ... Bottom 下 Left å·¦ Right å³ Apply To ... é©ç”¨å…ˆ... Alignment ä½ç½®åˆã‚ã› Match size with other pages サイズを他ã®ãƒšãƒ¼ã‚¸ã«åˆã‚ã›ã‚‹ Align with other pages ä»–ã®ãƒšãƒ¼ã‚¸ã«æƒãˆã‚‹ Go to the widest page. æ¨ªå¹…ãŒæœ€å¤§ã®ãƒšãƒ¼ã‚¸ã«ç§»å‹•ã™ã‚‹ã€‚ Widest Page æ¨ªå¹…ãŒæœ€å¤§ã®ãƒšãƒ¼ã‚¸ Go to the tallest page. ç¸¦å¹…ãŒæœ€å¤§ã®ãƒšãƒ¼ã‚¸ã«ç§»å‹•ã™ã‚‹ã€‚ Tallest Page ç¸¦å¹…ãŒæœ€å¤§ã®ãƒšãƒ¼ã‚¸ PageSplitModeDialog Split Pages ページを分割 Mode モード Auto 自動 Manual 手動 Scope é©ç”¨ç¯„囲 This page only ã“ã®ãƒšãƒ¼ã‚¸ã®ã¿ This page and the following ones ã“ã®ãƒšãƒ¼ã‚¸ä»¥é™ Selected pages é¸æŠžã•れãŸãƒšãƒ¼ã‚¸ Use Ctrl+Click / Shift+Click to select multiple pages. Ctrl+クリック ã‚„ Shift+クリック ã§è¤‡æ•°ãƒšãƒ¼ã‚¸ã‚’é¸æŠžã§ãã¾ã™ã€‚ All pages 全ページ PageSplitOptionsWidget Form Page Layout ページé…ç½® ? ? Change ... 変更... Split Line 分割線 Auto 自動 Manual 手動 ... ... PictureZonePropDialog Zone Properties 領域ã®ãƒ—ロパティ Subtract from all layers å…¨ã¦ã®ãƒ¬ã‚¤ãƒ¤ãƒ¼ã‹ã‚‰é™¤ã Add to auto layer 自動レイヤーã«åŠ ãˆã‚‹ Subtract from auto layer 自動レイヤーã‹ã‚‰é™¤ã ProjectFilesDialog Input Directory 入力ディレクトリ Output Directory 出力ディレクトリ 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. ã„ãã¤ã‹ã®ãƒ•ァイルを読ã¿è¾¼ã‚ã¾ã›ã‚“ã§ã—ãŸã€‚ ファイル形å¼ãŒã‚µãƒãƒ¼ãƒˆã•れã¦ã„ãªã„ã‹ã€ãƒ•ァイルãŒå£Šã‚Œã¦ã„ã¾ã™ã€‚ ã“れらをプロジェクトã‹ã‚‰å–り除ã„ã¦ãã ã•ã„。 Project Files プロジェクトファイル Browse å‚ç…§ 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;"> <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;">é¸æŠžã•れãŸãƒ•ァイルをプロジェクトã«åŠ ãˆã¾ã™ã€‚</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;">é¸æŠžã•れãŸãƒ•ァイルをプロジェクトã‹ã‚‰å–り除ãã¾ã™ã€‚</p></body></html> << << Files In Project プロジェクトã«å«ã¾ã‚Œã‚‹ãƒ•ァイル Right to left layout (for Hebrew and Arabic) ページをå³ã‹ã‚‰å·¦ã¸ã¨é…置(縦書ããŠã‚ˆã³ãƒ˜ãƒ–ライ語・アラビア語å‘ã‘) Fix DPIs, even if they look OK DPI ãŒæ­£ã—ãã†ãªå ´åˆã§ã‚‚訂正ã™ã‚‹ ProjectOpeningContext Error エラー Unable to interpret the project file. プロジェクトファイルを変æ›ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚ RelinkingDialog Relinking å†ãƒªãƒ³ã‚¯ Undo å…ƒã«æˆ»ã™ ... ... Substitution File for %1 Substitution Directory for %1 This change would merge several files into one. RemoveFileDialog Remove File ファイルをå–り除ã Remove %1 from project? プロジェクトã‹ã‚‰å–り除ãã¾ã™ã‹ï¼Ÿ Also delete the file from disk ディスクã‹ã‚‰ã‚‚削除ã™ã‚‹ RemovePagesDialog Remove Pages ページをå–り除ã Remove %1 page(s) from project? %1 ページをプロジェクトã‹ã‚‰å–り除ãã¾ã™ã‹ï¼Ÿ Corresponding output files will be deleted, while input files will remain. å…¥åŠ›ãƒ•ã‚¡ã‚¤ãƒ«ã¯æ®‹ã•れるもã®ã®ã€å¯¾å¿œã™ã‚‹å‡ºåŠ›ãƒ•ã‚¡ã‚¤ãƒ«ã¯å‰Šé™¤ã•れã¾ã™ã€‚ Files will remain on disk ディスク上ã®ãƒ•ã‚¡ã‚¤ãƒ«ã¯æ®‹ã•れã¾ã™ã€‚ SelectContentApplyDialog Select Content 版é¢ã‚’é¸æŠž Apply to é©ç”¨å…ˆ... This page only (already applied) ã“ã®ãƒšãƒ¼ã‚¸ã®ã¿ï¼ˆé©ç”¨æ¸ˆï¼‰ All pages 全ページ This page and the following ones ã“ã®ãƒšãƒ¼ã‚¸ä»¥é™ Every other page All odd or even pages, depending on the current page being odd or even. 1ページãŠã The current page will be included. ç¾åœ¨ã®ãƒšãƒ¼ã‚¸ã¯å«ã¾ã‚Œã¾ã™ã€‚ Selected pages é¸æŠžã•れãŸãƒšãƒ¼ã‚¸ Use Ctrl+Click / Shift+Click to select multiple pages. Ctrl+クリック ã‚„ Shift+クリック ã§è¤‡æ•°ãƒšãƒ¼ã‚¸ã‚’é¸æŠžã§ãã¾ã™ã€‚ Every other selected page é¸æŠžã•れãŸãƒšãƒ¼ã‚¸ã§1ページãŠã SelectContentOptionsWidget Form Content Box ç‰ˆé¢ Auto 自動 Manual 手動 Scope é©ç”¨ç¯„囲 Apply to ... é©ç”¨å…ˆ... SettingsDialog Compiled without OpenGL support. OpenGL サãƒãƒ¼ãƒˆç„¡ã—ã§ã‚³ãƒ³ãƒ‘イルã•れã¦ã„ã¾ã™ã€‚ Your hardware / driver don't provide the necessary features. å¿…è¦ãªæ©Ÿèƒ½ã‚’ãƒãƒ¼ãƒ‰ã‚¦ã‚§ã‚¢ã‚„ドライãƒãŒæŒã£ã¦ã„ã¾ã›ã‚“。 Settings 設定 Use 3D acceleration for user interface ユーザインターフェース㫠3D アクセラレーションを用ã„ã‚‹ StageListView Launch batch processing 一括処ç†ã‚’é–‹å§‹ SystemLoadWidget Form System load ã‚·ã‚¹ãƒ†ãƒ è² è· ... Minimal æœ€å° Normal 通常 ThumbnailSequence %1 (page %2) %1 (%2 ページ) ZoneContextMenuInteraction Properties プロパティ Delete 削除 Delete confirmation 削除ã®ç¢ºèª Really delete this zone? ã“ã®é ˜åŸŸã‚’本当ã«å‰Šé™¤ã—ã¾ã™ã‹ï¼Ÿ ZoneCreationInteraction Click to finish this zone. ESC to cancel. クリックã™ã‚‹ã¨ã“ã®é ˜åŸŸã‚’完çµã—ã¾ã™ã€‚ESC を押ã™ã¨ã‚­ãƒ£ãƒ³ã‚»ãƒ«ã—ã¾ã™ã€‚ Connect first and last points to finish this zone. ESC to cancel. 最åˆã¨æœ€å¾Œã®ç‚¹ã‚’ã¤ãªãã¨é ˜åŸŸä½œæˆã‚’完了ã—ã¾ã™ã€‚ESC を押ã™ã¨ã‚­ãƒ£ãƒ³ã‚»ãƒ«ã—ã¾ã™ã€‚ Zones need to have at least 3 points. ESC to cancel. 領域ã«ã¯3ã¤ä»¥ä¸Šã®ç‚¹ãŒå¿…è¦ã§ã™ã€‚ESC を押ã™ã¨ã‚­ãƒ£ãƒ³ã‚»ãƒ«ã—ã¾ã™ã€‚ ZoneDefaultInteraction Drag the vertex. 頂点をドラッグã—ã¾ã™ã€‚ Click to create a new vertex here. クリックã™ã‚‹ã¨æ–°ã—ã„頂点をã“ã“ã«ä½œæˆã—ã¾ã™ã€‚ Right click to edit zone properties. å³ã‚¯ãƒªãƒƒã‚¯ã™ã‚‹ã¨é ˜åŸŸã®ãƒ—ロパティを編集ã§ãã¾ã™ã€‚ Click to start creating a new picture zone. クリックã™ã‚‹ã¨æ–°ã—ã„ç”»åƒé ˜åŸŸã‚’作æˆé–‹å§‹ã—ã¾ã™ã€‚ ZonePropertiesDialog Zone Properties 領域ã®ãƒ—ロパティ Subtract from all layers å…¨ã¦ã®ãƒ¬ã‚¤ãƒ¤ãƒ¼ã‹ã‚‰é™¤ã Add to auto layer 自動レイヤーã«åŠ ãˆã‚‹ Subtract from auto layer 自動レイヤーã‹ã‚‰é™¤ã ZoneVertexDragInteraction Merge these two vertices. ã“れらãµãŸã¤ã®é ‚点をマージã—ã¾ã™ã€‚ Move the vertex to one of its neighbors to merge them. ã“ã®é ‚点をマージã™ã‚‹ãŸã‚最も近ã„頂点ã¸ã¨ç§»å‹•ã•ã›ã¾ã™ã€‚ deskew::Filter Deskew 傾ãを修正 deskew::ImageView Use Ctrl+Wheel to rotate or Ctrl+Shift+Wheel for finer rotation. Ctrl+マウスホイールã§å›žè»¢ã§ãã¾ã™ã€‚Ctrl+Shift+マウスホイールã§ã‚ˆã‚Šç´°ã‹ã回転ã§ãã¾ã™ã€‚ Drag this handle to rotate the image. ãƒãƒ³ãƒ‰ãƒ«ã‚’ドラッグã™ã‚‹ã¨ç”»åƒã‚’回転ã§ãã¾ã™ã€‚ deskew::OptionsWidget Apply Deskew 傾ã修正をé©ç”¨ fix_orientation::ApplyDialog Can't do: more than one group is selected. 実行ã§ãã¾ã›ã‚“:複数ã®ã‚°ãƒ«ãƒ¼ãƒ—ãŒé¸æŠžã•れã¦ã„ã¾ã™ã€‚ fix_orientation::Filter Fix Orientation å‘ãを訂正 output::ChangeDpiDialog Custom カスタム Error エラー DPI is not set. DPI ãŒæŒ‡å®šã•れã¦ã„ã¾ã›ã‚“。 DPI is too low! DPI ãŒå°ã•ã™ãŽã¾ã™ã€‚ DPI is too high! DPI ãŒå¤§ãã™ãŽã¾ã™ã€‚ output::DewarpingView Modifying auto-generated grid is not yet implemented. Switch to Manual mode if necessary. 自動生æˆã•れãŸã‚°ãƒªãƒƒãƒ‰ã‚’変更ã™ã‚‹æ©Ÿèƒ½ã¯ã¾ã å®Ÿè£…ã•れã¦ã„ã¾ã›ã‚“。必è¦ã«å¿œã˜ã¦æ‰‹å‹•モードã«åˆ‡ã‚Šæ›¿ãˆã¦ãã ã•ã„。 output::FillZoneEditor Pick color 色を抽出 output::Filter Output 出力 output::OptionsWidget Black and White 白黒 Color / Grayscale カラーï¼ã‚°ãƒ¬ãƒ¼ã‚¹ã‚±ãƒ¼ãƒ« Mixed ミックス Apply Despeckling Level スペックル除去レベルをé©ç”¨ã™ã‚‹ Apply Depth Perception é è¿‘感をé©ç”¨ Off オフ Auto 自動 Manual 手動 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 スペックル除去 page_layout::ApplyDialog Can't do: more than one group is selected. 実行ã§ãã¾ã›ã‚“:複数ã®ã‚°ãƒ«ãƒ¼ãƒ—ãŒé¸æŠžã•れã¦ã„ã¾ã™ã€‚ page_layout::Filter Page Layout ページé…ç½® Natural order 通常ã®é †åº Order by increasing width å¹…ã®ç‹­ã„é † Order by increasing height 高ã•ã®ä½Žã„é † Margins 余白 page_layout::ImageView Resize margins by dragging any of the solid lines. 余白ã®å¹…を変ãˆã‚‹ã«ã¯å®Ÿç·šã®ã©ã‚Œã‹ã‚’ドラッグã—ã¾ã™ã€‚ page_layout::OptionsWidget Apply Margins 余白をé©ç”¨ã™ã‚‹ Apply Alignment ä½ç½®åˆã‚ã›ã‚’é©ç”¨ã™ã‚‹ page_split::Filter Natural order 通常ã®é †åº Order by split type ページ分割ã®ç¨®é¡žé † Split Pages ページを分割 page_split::ImageView Drag the line or the handles. ç·šã¾ãŸã¯ãƒãƒ³ãƒ‰ãƒ«ã‚’ドラッグã—ã¾ã™ã€‚ page_split::OptionsWidget Set manually 手動設定 Auto detected 自動検出 page_split::UnremoveButton Restore removed page. 削除ã—ãŸãƒšãƒ¼ã‚¸ã‚’復元 select_content::ApplyDialog Can't do: more than one group is selected. 実行ã§ãã¾ã›ã‚“:複数ã®ã‚°ãƒ«ãƒ¼ãƒ—ãŒé¸æŠžã•れã¦ã„ã¾ã™ã€‚ select_content::Filter Natural order 通常ã®é †åº Order by increasing width å¹…ã®ç‹­ã„é † Order by increasing height 高ã•ã®ä½Žã„é † Select Content 版é¢ã‚’é¸æŠž select_content::ImageView Use the context menu to enable / disable the content box. コンテキストメニュー(å³ã‚¯ãƒªãƒƒã‚¯ï¼‰ã‹ã‚‰ç‰ˆé¢ã®æœ‰åйï¼ç„¡åŠ¹ã‚’åˆ‡ã‚Šæ›¿ãˆã‚‰ã‚Œã¾ã™ã€‚ Drag lines or corners to resize the content box. ç·šã¾ãŸã¯ã‚³ãƒ¼ãƒŠãƒ¼ã‚’ドラッグã™ã‚‹ã¨ç‰ˆé¢ã®å¤§ãã•を変ãˆã‚‰ã‚Œã¾ã™ã€‚ Create Content Box 版é¢ã®ä½œæˆ Remove Content Box 版é¢ã‚’å–り除ã scantailor-RELEASE_0_9_12_2/translations/scantailor_pl.ts000066400000000000000000003031751271170121200234130ustar00rootroot00000000000000 AboutDialog About Scan Tailor O Scan Tailor About O programie 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 recogrnition is out of scope of this project. Scan Tailor to interaktywne narzÄ™dzie do przetwarzania zeskanownych stron. Wykonuje takie operacje jak dzielenie stron, prostowanie, dodawanie/usuwanie marginesów i inne. Program pobiera surowe skany i zapisuje strony gotowe do druku lub zÅ‚ożenia w postaci pliku PDF lub DJVu. Skanowanie i rozpoznawanie pisma sÄ… poza zakresem dziaÅ‚ania tego programu. 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 to interaktywne narzÄ™dzie do przetwarzania zeskanownych stron. Wykonuje takie operacje jak dzielenie stron, prostowanie, dodawanie/usuwanie marginesów i inne. Program pobiera surowe skany i zapisuje strony gotowe do druku lub zÅ‚ożenia w postaci pliku PDF lub DJVu. Skanowanie i rozpoznawanie pisma sÄ… poza zakresem dziaÅ‚ania tego programu. Authors Autorzy Lead Developer Główny programista Joseph Artsimovich Joseph Artsimovich U235 - Picture auto-detection algorithm. Robert B. - First generation dewarping algorithm. Andrey Bergman - System load adjustment. Petr Kovář - Command line interface. Neco Torquato - Brazilian Portuguese Svetoslav Sashkov, Mandor - Bulgarian Petr Kovář - Czech Stefan Birkner - German Angelo Gemmi - Italian Masahiro Kitagawa - Japanese Patrick Pascal - French Daniel Koć - Polish Joseph Artsimovich - Russian Marián Hvolka - Slovak Flavio Benelli - Spanish Davidson Wang - Traditional Chinese Yuri Chornoivan - Ukrainian denver 22 Svetoslav Sashkov, Mandor - Bulgarian Petr Kovář - Czech Stefan Birkner - German Angelo Gemmi - Italian Masahiro Kitagawa - Japanese Patrick Pascal - French Daniel Koć - Polish Joseph Artsimovich - Russian Davidson Wang - Traditional Chinese Svetoslav Sashkov, Mandor - buÅ‚garski Petr Kovář - czeski Stefan Birkner - niemiecki Angelo Gemmi - wÅ‚oski Masahiro Kitagawa - japoÅ„ski Patrick Pascal - francuski Daniel Koć - polski Joseph Artsimovich - rosyjski Davidson Wang - tradycyjny chiÅ„ski <!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:8pt;">U235 - Picture auto-detection algorithm.</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:8pt;">Robert B. - First generation dewarping algorithm.</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:8pt;">Andrey Bergman - System load adjustment.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></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:8pt;">U235 - Algorytm automatycznego wykrywania grafik.</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:8pt;">Robert B. - Pierwsza generacja algorytmu prostowania stron.</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:8pt;">Andrey Bergman - Regulacja obciążenia systemu.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p></body></html> Svetoslav Sashkov, Mandor - Bulgarian Stefan Birkner - German Angelo Gemmi - Italian Masahiro Kitagawa - Japanese Patrick Pascal - French Joseph Artsimovich - Russian Davidson Wang - Traditional Chinese Svetoslav Sashkov, Mandor - buÅ‚garski Stefan Birkner - niemiecki Angelo Gemmi - wÅ‚oski Masahiro Kitagawa - japoÅ„ski Patrick Pascal - francuski Joseph Artsimovich - rosyjski Davidson Wang - tradycyjny chiÅ„ski Translators TÅ‚umacze Svetoslav Sashkov, Mandor - Bulgarian Stefan Birkner - German Angelo Gemmi - Italian Masahiro Kitagawa - Japanese Patrick Pascal - French Daniel Koć - Polish Joseph Artsimovich - Russian Davidson Wang - Traditional Chinese Svetoslav Sashkov, Mandor - buÅ‚garski Stefan Birkner - niemiecki Angelo Gemmi - wÅ‚oski Masahiro Kitagawa - japoÅ„ski Patrick Pascal - francuski Daniel Koć - polski Joseph Artsimovich - rosyjski Davidson Wang - tradycyjny chiÅ„ski Contributors Współtwórcy U235 - Picture auto-detection algorithm. Robert B. - Dewarping algorithm. Andrey Bergman - System load adjustment. U235 - Algorytm automatycznego wykrywania grafik. Robert B. - Algorytm prostowania stron. Andrey Bergman - Regulacja obciążenia systemu. Documentation Dokumentacja denver 22 phaedrus Taxman denver 22 phaedrus Taxman License Licencja BatchProcessingLowerPanel Form Formularz Beep when finished sygnalizuj zakoÅ„czenie dźwiÄ™kiem ColorPickupInteraction Click on an area to pick up its color, or ESC to cancel. Kliknij na obszar aby wybrać jego kolor lub naciÅ›nij klawisz Esc aby to pominąć. DeskewApplyDialog Apply to Zastosuj do This page only (already applied) tylko bieżąca strona (już zastosowane) All pages wszystkie strony This page and the following ones ta strona i wszystkie kolejne Selected pages wybrane strony Use Ctrl+Click / Shift+Click to select multiple pages. Użyj klawisza Ctrl z kółkiem myszki/Shift z kółkiem myszki aby wybrać wiele stron. DeskewOptionsWidget Form Formularz Deskew Obróć Auto automatycznie Manual rÄ™cznie Apply To ... Zastosuj do... DragHandler Unrestricted dragging is possible by holding down the Shift key. Swobodne przesuwanie jest dostÄ™pne poprzez przytrzymanie klawisza Shift. ErrorWidget Form Formularz ImageLabel Etykieta grafiki TextLabel Etykieta tekstu FixDpiDialog Need Fixing WymagajÄ…ce korekty All Pages wszystkie strony DPI is too large and most likely wrong. Wartość DPI jest za duża i prawdopodobnie błędna. DPI is too small. Even if it's correct, you are not going to get acceptable results with it. Wartość DPI jest za maÅ‚a. Nawet jeÅ›li jest prawidÅ‚owa, nie przyniesie sensownych wyników. DPI is too small for this pixel size. Such combination would probably lead to out of memory errors. Wartość DPI jest za maÅ‚a dla tych rozmiarów. Taka kombinacja może wywoÅ‚ać błędy pamiÄ™ci. %1 (page %2) %1 (strona %2) Fix DPI Popraw DPI Tab 1 ZakÅ‚adka 1 Tab 2 ZakÅ‚adka 2 DPI DPI Custom wÅ‚asne x x Apply Zastosuj FixDpiSinglePageDialog Fix DPI Popraw DPI DPI for %1 DPI dla strony %1 Custom WÅ‚asne x x %1 (page %2) %1 (strona %2) DPI is too large and most likely wrong. Wartość DPI jest za duża i prawdopodobnie błędna. DPI is too small. Even if it's correct, you are not going to get acceptable results with it. Wartość DPI jest za maÅ‚a. Nawet jeÅ›li jest prawidÅ‚owa, nie przyniesie sensownych wyników. DPI is too small for this pixel size. Such combination would probably lead to out of memory errors. Wartość DPI jest za maÅ‚a dla tego rozmiaru piksela. Taka kombinacja może wywoÅ‚ać błędy pamiÄ™ci. ImageViewBase Use the mouse wheel or +/- to zoom. When zoomed, dragging is possible. Użyj kółka myszki lub klawiszy +/- aby powiÄ™kszyć obraz. Po powiÄ™kszeniu możliwe jest przesuwanie. InteractiveXSpline Click to create a new control point. Kliknij aby utworzyć nowy punkt kontrolny. This point can be dragged. Ten punkt można przesuwać. Drag this point or delete it by pressing Del or D. PrzesuÅ„ ten punkt lub usuÅ„ go naciskajÄ…c klawisz Del albo D. LoadFileTask The following file could not be loaded: %1 Nie udaÅ‚o siÄ™ wczytać nastÄ™pujÄ…cgo pliku: %1 The following file doesn't exist:<br>%1<br><br>Use the <a href="#relink">Relinking Tool</a> to locate it. LoadFilesStatusDialog Some files failed to load Loaded successfully: %1 Failed to load: %1 MainWindow Save Project Zapisz projekt Save this project? Zapisać ten projekt? Insert before ... Wstaw przed... Insert after ... Wstaw po... Remove from project ... UsuÅ„ z projektu... Insert here ... Wstaw tu... Scan Tailor Projects Projekty Scan Tailor Open Project Otwórz projekt Error Błąd Unable to open the project file. Nie można otworzyć pliku projektu. The project file is broken. Plik projektu jest uszkodzony. 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". WyjÅ›cie jeszcze niedostÄ™pne, ponieważ nie jest znany ostateczny rozmiar strony. Aby go ustalić uruchom przetwarzanie wsadowe w sekcji "Wybierz zawartość" lub "Marginesy". Unnamed Bez nazwy %2 - Scan Tailor %3 [%1bit] Files to insert Skip failed files %1 - Scan Tailor %2 %1 - Scan Tailor %2 Error saving the project file! Błąd zapisywania pliku projektu! File to insert Plik do wstawienia Images not in project (%1) Plik spoza projektu (%1) Error opening the image file. Błąd otwierania pliku grafiki. Remove UsuÅ„ MainWindow Okno główne Keep current page in view. Zachowaj widok bieżącej strony. Use Home, End, PgUp (or Q), PgDown (or W) to navigate between pages. Użyj klawiszy Home, End, PgUp (lub Q), PgDown (lub W) aby poruszać siÄ™ miÄ™dzy stronami. Tools NarzÄ™dzia File Plik Help Pomoc Debug Debugowanie Debug Mode Ctrl+S Ctrl+S Save Project As ... Zapisz projekt jako... Next Page NastÄ™pna strona PgDown PgDown Previous Page Poprzednia strona PgUp PgUp New Project ... Nowy projekt... Ctrl+N Ctrl+N Open Project ... Otwórz projekt... Ctrl+O Ctrl+O Q Q W W Close Project Zamknij projekt Ctrl+W Ctrl+W Quit ZakoÅ„cz Ctrl+Q Ctrl+Q First Page Pierwsza strona Home PoczÄ…tek Last Page Ostatnia strona End Koniec About O programie Fix DPI ... Relinking ... Stop batch processing Zatrzymaj przetwarzanie wsadowe Settings ... Ustawienia... NewOpenProjectPanel Form Formularz New Project ... Nowy projekt... Open Project ... Otwórz projekt... Recent Projects Ostatnie projekty OrientationApplyDialog Fix Orientation Popraw orientacjÄ™ Apply to Zastosuj do This page only (already applied) tylko bieżąca strona (już zastosowane) All pages wszystkie strony This page and the following ones ta strona i wszystkie kolejne Every other page All odd or even pages, depending on the current page being odd or even. Wszystkie parzyste i nieparzyste strony w zależnoÅ›ci od tego, czy bieżąca strona jest parzysta czy nie. wszystkie pozostaÅ‚e strony The current page will be included. Bez bieżącej strony. Selected pages wybrane strony Use Ctrl+Click / Shift+Click to select multiple pages. Użyj klawisza Ctrl z kółkiem myszki/Shift z kółkiem myszki aby wybrać wiele stron. Every other selected page wszystkie pozostaÅ‚e wybrane strony OrientationOptionsWidget Form Formularz Rotate Obróć ... ... Reset Przywróć ustawienia poczÄ…tkowe Scope Zakres Apply to ... Zastosuj do... 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 Zapisz projekt Save Project As ... Zapisz projekt jako... 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 Projekty Scan Tailor Error Błąd Error saving the project file! Błąd zapisywania pliku projektu! OutputApplyColorsDialog Apply Mode Zastosuj tryb Apply to Zastosuj do This page only (already applied) tylko bieżąca strona (już zastosowane) All pages wszystkie strony This page and the following ones ta strona i wszystkie kolejne Selected pages wybrane strony Use Ctrl+Click / Shift+Click to select multiple pages. Użyj klawisza Ctrl z kółkiem myszki/Shift z kółkiem myszki aby wybrać wiele stron. OutputChangeDewarpingDialog Apply Dewarping Mode Zastosuj tryb prostowania Mode Tryb Off wyłączone Auto (experimental) automatyczne (eksperymentalne) Manual rÄ™czne Scope Zakres This page only tylko ta strona All pages wszystkie strony This page and the following ones ta strona i wszystkie kolejne Selected pages wybrane strony Use Ctrl+Click / Shift+Click to select multiple pages. Użyj klawisza Ctrl z kółkiem myszki/Shift z kółkiem myszki aby wybrać wiele stron. OutputChangeDpiDialog Apply Output Resolution Zastosuj rozdzielczość wyjÅ›cia DPI DPI Scope Zakres This page only tylko ta strona This page and the following ones ta strona i wszystkie kolejne Selected pages wybrane strony Use Ctrl+Click / Shift+Click to select multiple pages. Użyj klawisza Ctrl z kółkiem myszki/Shift z kółkiem myszki aby wybrać wiele stron. All pages wszystkie strony OutputOptionsWidget Form Formularz Output Resolution (DPI) Rodzielczość wyjÅ›cia (DPI) Change ... ZmieÅ„... Mode Tryb White margins biaÅ‚e marginesy Equalize illumination wyrównaj oÅ›wietlenie Dewarping Prostowanie Depth perception Percepcja głębi No despeckling Bez oczyszczania Apply To ... Zastosuj do... Despeckling Oczyszczanie Cautious despeckling Ostrożne oczyszczanie ... ... Normal despeckling Normalne oczyszczanie Aggressive despeckling Agresywne oczyszczanie Thinner cieÅ„sze Thicker grubsze PageLayoutApplyDialog Apply to Zastosuj do This page only (already applied) tylko bieżąca strona (już zastosowane) All pages wszystkie strony This page and the following ones ta strona i wszystkie kolejne Every other page All odd or even pages, depending on the current page being odd or even. wszystkie pozostaÅ‚e strony The current page will be included. Selected pages wybrane strony Use Ctrl+Click / Shift+Click to select multiple pages. Użyj klawisza Ctrl z kółkiem myszki/Shift z kółkiem myszki aby wybrać wiele stron. Every other selected page wszystkie pozostaÅ‚e wybrane strony PageLayoutOptionsWidget Form Formularz Margins Marginesy Millimeters (mm) millimetery (mm) Inches (in) cale (in) Top górny ... ... Bottom dolny Left lewy Right prawy Apply To ... Zastosuj do... Alignment Wyrównanie Match size with other pages dopasuj rozmiar do pozostaÅ‚ych stron PageSplitModeDialog Split Pages Dziel strony Mode Tryb Auto automatyczny Manual rÄ™czny Scope Zakres This page only tylko ta strona This page and the following ones ta strona i wszystkie kolejne Selected pages wybrane strony Use Ctrl+Click / Shift+Click to select multiple pages. Użyj klawisza Ctrl z kółkiem myszki/Shift z kółkiem myszki aby wybrać wiele stron. All pages wszystkie strony PageSplitOptionsWidget Form Formularz Page Layout UkÅ‚ad strony ? ? Change ... ZmieÅ„... Split Line Linia podziaÅ‚u Auto automatycznie Manual rÄ™cznie PictureZonePropDialog Zone Properties WÅ‚aÅ›ciwoÅ›ci obszaru Subtract from all layers UsuÅ„ ze wszystkich warstw Add to auto layer Dodaj do warstwy automatycznej Subtract from auto layer UsuÅ„ z warstwy automatycznej ProjectFilesDialog Input Directory Katalog wejÅ›cia Output Directory Katalog wyjÅ›cia Error Błąd No files in project! Brak plików w projekcie! Input directory is not set or doesn't exist. Katalog wejÅ›cia nie jest wybrany lub nie istnieje. Input and output directories can't be the same. Katalogi wejÅ›cia i wyjÅ›cia muszÄ… być różne. Create Directory? Utworzyć katalog? Output directory doesn't exist. Create it? Katalog wyjÅ›cia nie istnieje. Utworzyć go? Unable to create output directory. Utworzenie katalogu wyjÅ›cia nie powiodÅ‚o siÄ™. Output directory is not set or doesn't exist. Katalog wyjÅ›cia nie jest wybrany lub nie istnieje. 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. Wczytanie części plików nie powiodÅ‚o siÄ™. Albo sÄ… w nieobsÅ‚ugiwanym formacie, albo sÄ… uszkodzone. Należy usunąć je z projektu. Project Files Pliki projektu Browse PrzeglÄ…daj Files Not In Project Pliki spoza projektu Select All Wybierz wszystko <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;">Dodaj wybrane pliki do projektu.</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;">UsuÅ„ wybrane pliki z projektu.</p></body></html> << << Files In Project Pliki w projekcie Right to left layout (for Hebrew and Arabic) UkÅ‚ad od prawej do lewej (dla jÄ™zyka hebrajskiego i arabskiego) Fix DPIs, even if they look OK Popraw DPI nawet jeÅ›li wydaje siÄ™ prawidÅ‚owe ProjectOpeningContext Error Błąd Unable to interpret the project file. Problem z analizÄ… pliku projektu. RelinkingDialog Relinking Undo ... ... Substitution File for %1 Substitution Directory for %1 This change would merge several files into one. RemovePagesDialog Remove Pages UsuÅ„ strony Remove %1 page(s) from project? Usunąć %1 stron(Ä™) z projektu? Corresponding output files will be deleted, while input files will remain. OdpowiadajÄ…ce pliki wyjÅ›cia zostanÄ… usuniÄ™te, pliki wejÅ›cia pozostanÄ… nienaruszone. SelectContentApplyDialog Select Content Wybierz zawartość Apply to Zastosuj do This page only (already applied) tylko bieżąca strona (już zastosowane) All pages wszystkie strony This page and the following ones ta strona i wszystkie kolejne Every other page All odd or even pages, depending on the current page being odd or even. Wszystkie parzyste lub nieparzyste strony, w zależnoÅ›ci czy bieżąca strona jest parzysta czy nieparzysta. wszystkie pozostaÅ‚e strony The current page will be included. Razem z bieżącÄ… stronÄ…. Selected pages wybrane strony Use Ctrl+Click / Shift+Click to select multiple pages. Użyj klawisza Ctrl z kółkiem myszki/Shift z kółkiem myszki aby wybrać wiele stron. Every other selected page wszystkie pozostaÅ‚e wybrane strony SelectContentOptionsWidget Form Formularz Content Box Obszar zawartoÅ›ci Auto automatyczny Manual rÄ™czny Scope Zakres Apply to ... Zastosuj do... SettingsDialog Compiled without OpenGL support. Skompilowane bez obsÅ‚ugi OpenGL. Your hardware / driver don't provide the necessary features. Twój sprzÄ™t/sterownik nie zapewnia wymaganych funkcji. Settings Ustawienia Use 3D acceleration for user interface Użyj akceleracji 3D do interfejsu użytkownika StageListView Launch batch processing Uruchom przetwarzanie wsadowe SystemLoadWidget Form Formularz System load Obciążenie systemu ... ... Minimal Minimalne Normal Normalne ThumbnailSequence %1 (page %2) %1 (strona %2) ZoneContextMenuInteraction Properties WÅ‚aÅ›ciwoÅ›ci Delete UsuÅ„ Delete confirmation Potwierdzenie usuniÄ™cia Really delete this zone? NaprawdÄ™ usunąć ten obszar? ZoneCreationInteraction Click to finish this zone. ESC to cancel. Kliknij aby utworzyć ten obszar. WciÅ›nij Esc aby anulować. Connect first and last points to finish this zone. ESC to cancel. Połącz pierwszy i ostatni punkt aby utworzyć ten obszar. WciÅ›nij Esc aby anulować. Zones need to have at least 3 points. ESC to cancel. Obszary muszÄ… siÄ™ skÅ‚adać z przynajmniej 3 punktów. WciÅ›nij Esc aby anulować. ZoneDefaultInteraction Drag the vertex. PrzesuÅ„ punkt. Click to create a new vertex here. Kliknij aby utworzyć tu nowy punkt. Right click to edit zone properties. Kliknij prawym klawiszem myszki aby edytować wÅ‚aÅ›ciwoÅ›ci obszaru. Click to start creating a new picture zone. Kliknij aby utworzyć nowy obszar grafiki. ZoneVertexDragInteraction Merge these two vertices. Połącz te dwa punkty. Move the vertex to one of its neighbors to merge them. PrzesuÅ„ ten punkt do jednego z sÄ…siednich punktów, aby je połączyć. deskew::Filter Deskew Obróć deskew::ImageView Use Ctrl+Wheel to rotate or Ctrl+Shift+Wheel for finer rotation. Użyj klawisza Ctrl w połączeniu z kółkiem myszki do obracania lub Ctrl+Shift z kółkiem myszki do precyzyjnego obracania. Drag this handle to rotate the image. PrzesuÅ„ ten uchwyt aby obrócić grafikÄ™. deskew::OptionsWidget Apply Deskew Zastosuj prostowanie fix_orientation::ApplyDialog Can't do: more than one group is selected. Niewykonalne: zaznaczono wiÄ™cej niż jednÄ… grupÄ™. fix_orientation::Filter Fix Orientation Popraw orientacjÄ™ output::ChangeDpiDialog Custom WÅ‚asne Error Błąd DPI is not set. Wartość DPI nie ustawiona. DPI is too low! Wartość DPI zbyt maÅ‚a! DPI is too high! Wartość DPI zbyt wysoka! output::DewarpingView Modifying auto-generated grid is not yet implemented. Switch to Manual mode if necessary. Korekta automatycznie utworzonej siatki nie jest jeszcze zaimplementowana. JeÅ›li to konieczne przełącz siÄ™ do trybu rÄ™cznego. output::FillZoneEditor Pick color Wybierz kolor output::Filter Output WyjÅ›cie output::OptionsWidget Black and White czarno-biaÅ‚y Color / Grayscale kolorowy/skala szaroÅ›ci Mixed mieszany Apply Despeckling Level Zastosuj poziom oczyszczania Apply Depth Perception Zastosuj percepcjÄ™ głębi Off wyłączone Auto automatyczne Manual rÄ™czne output::Task::UiUpdater Picture zones are only available in Mixed mode. Obszary grafiki sÄ… dostÄ™pne tylko w trybie mieszanym. Despeckling can't be done in Color / Grayscale mode. Oczyszczanie nie jest możliwe w trybie koloru/skali szaroÅ›ci. Output WyjÅ›cie Picture Zones Obszary grafiki Fill Zones Obszary wypeÅ‚nienia Dewarping Prostowanie Despeckling Oczyszczanie page_layout::ApplyDialog Can't do: more than one group is selected. Niewykonalne: zaznaczono wiÄ™cej niż jednÄ… grupÄ™. page_layout::Filter Natural order kolejność standardowa Order by increasing width rosnÄ…co wedÅ‚ug szerokoÅ›ci Order by increasing height rosnÄ…co wedÅ‚ug wysokoÅ›ci Margins Marginesy page_layout::ImageView Resize margins by dragging any of the solid lines. ZmieÅ„ szerokość marginesów przesuwajÄ…c dowolnÄ… ciÄ…głą liniÄ™. page_layout::OptionsWidget Apply Margins Zastosuj marginesy Apply Alignment Zastosuj wyrównanie page_split::Filter Natural order Order by split type Split Pages Dziel strony page_split::ImageView Drag the line or the handles. Przesuwaj liniÄ™ lub uchwyty. page_split::OptionsWidget Set manually Ustaw rÄ™cznie Auto detected Wykrywanie automatyczne page_split::UnremoveButton Restore removed page. Przywróć usuniÄ™tÄ… stronÄ™. select_content::ApplyDialog Can't do: more than one group is selected. Niewykonalne: zaznaczono wiÄ™cej niż jednÄ… grupÄ™. select_content::Filter Natural order Kolejność standardowa Order by increasing width RosnÄ…co wedÅ‚ug szerokoÅ›ci Order by increasing height RosnÄ…co wedÅ‚ug wysokoÅ›ci Select Content Wybierz zawartość select_content::ImageView Use the context menu to enable / disable the content box. Użyj menu kontekstowego aby włączyć/wyłączyć obszar zawartoÅ›ci. Drag lines or corners to resize the content box. PrzesuÅ„ linie lub rogi aby zmienić rozmiary obszaru. Create Content Box Utwórz obszar zawartoÅ›ci Remove Content Box UsuÅ„ obszar zawartoÅ›ci scantailor-RELEASE_0_9_12_2/translations/scantailor_pt_BR.ts000066400000000000000000002704271271170121200240110ustar00rootroot00000000000000 AboutDialog About Scan Tailor Sobre Scan Tailor About Sobre 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 é uma ferramenta interativa de pós-processamento para imagens escaneadas. Executa operações como dividir páginas, correção de inclinacão, agregar/eliminar margens e outras.Você fornece a imagem escaneada e obtem imagens prontas para impressão ou para criar um arquivo PDF ou DJVU. Digitalização e reconhecimento ótico de caracteres OCR estão fora do escopo deste projeto. Authors Autores Lead Developer Líder de desenvolvimento Joseph Artsimovich Joseph Artsimovich U235 - Picture auto-detection algorithm. U234 - Algorítmo de detecção automática de imagens. Robert B. - First generation dewarping algorithm. Robert B. - Primera generação de correção de deformação. Andrey Bergman - System load adjustment. Andrey Bergman - Ajuste de carga de sistema. Petr Kovář - Command line interface. Petr Kovář - Interface de linha de comando. Neco Torquato - Brazilian Portuguese Svetoslav Sashkov, Mandor - Bulgarian Svetoslav Sashkov, Mandor - Búlgaro Petr Kovář - Czech Petr Kovář - Checo Stefan Birkner - German Stefan Birkner - Alemão Angelo Gemmi - Italian Angelo Gemmi - Italiano Masahiro Kitagawa - Japanese Masahiro Kitagawa - Japonês Patrick Pascal - French Patrick Pascal - Francês Daniel Koć - Polish Daniel Koć - Polonês Joseph Artsimovich - Russian Joseph Artsimovich - Russo Marián Hvolka - Slovak Marián Hvolka - Eslovaco Flavio Benelli - Spanish Flavio Benelli - Espanhol Davidson Wang - Traditional Chinese Davidson Wang - Chinês Tradicional Yuri Chornoivan - Ukrainian Yuri Chornoivan - Ucraniano denver 22 denver 22 Translators Tradutores Contributors Contribuidores Documentation Documentação License Licença BatchProcessingLowerPanel Form Modelo Beep when finished Soar ao finalizar ColorPickupInteraction Click on an area to pick up its color, or ESC to cancel. Clique em uma área para escolher a cor ou ESC para cancelar. DeskewApplyDialog Apply to Aplicar a This page only (already applied) Somente esta página (já aplicado) All pages Todas as páginas This page and the following ones Esta página e as que seguem Selected pages Paginas selecionadas Use Ctrl+Click / Shift+Click to select multiple pages. Usar Ctrl+Click / Shift+Click para selecionar multiplas páginas. DeskewOptionsWidget Form Modelo Deskew Alinhamento Auto Auto Manual Manual Apply To ... Aplicar a... DragHandler Unrestricted dragging is possible by holding down the Shift key. É possível movimentar a imagem pressionando a tecla Shift. ErrorWidget Form Formulario ImageLabel why doesn't have space? EtiquetaImagen TextLabel why doesn't have space? EtiquetaTexto FixDpiDialog Need Fixing Necesita Correccion All Pages Todas las paginas DPI is too large and most likely wrong. El numero de DPI es muy grande y posiblemente este equivocado. DPI is too small. Even if it's correct, you are not going to get acceptable results with it. El numero de DPI es muy pequeño, si fuera correcto probablemente no obtenga resultados aceptables. DPI is too small for this pixel size. Such combination would probably lead to out of memory errors. El numero de DPI es muy pequeño para el tamaño de pixeles.De mantenerse probablemente consuma demaciada memoria. %1 (page %2) %1 (pagina %2) Fix DPI Corrigir DPI Tab 1 Tabela 1 Tab 2 Tabela 2 DPI DPI Custom Personalizar x x Apply Aplicar FixDpiSinglePageDialog Fix DPI Corregir DPI DPI for %1 i need context DPI para %1 Custom Personalizar x x %1 (page %2) %1 (pagina %2) DPI is too large and most likely wrong. El numero de DPI es muy grande y posiblemente este equivocado. DPI is too small. Even if it's correct, you are not going to get acceptable results with it. El numero de DPI es muy pequeño, si fuera correcto probablemente no obtenga resultados aceptables. DPI is too small for this pixel size. Such combination would probably lead to out of memory errors. El numero de DPI es muy pequeño para el tamaño de pixeles.De mantenerse probablemente consuma demaciada memoria. ImageViewBase Use the mouse wheel or +/- to zoom. When zoomed, dragging is possible. Use la rueda del raton o +/- para ampliar.Cuando esta ampliado es posible arrastrar. InteractiveXSpline Click to create a new control point. Clique para criar um novo ponto de controle. This point can be dragged. Este ponto pode ser arrastado. Drag this point or delete it by pressing Del or D. Arraste este ponto ou apague-o pressionando Del ou D. LoadFileTask The following file could not be loaded: %1 El siguiente archivo no pudo ser abierto: %1 The following file doesn't exist:<br>%1<br><br>Use the <a href="#relink">Relinking Tool</a> to locate it. LoadFilesStatusDialog Some files failed to load Alguns arquivos falharam ao carregar Loaded successfully: %1 Sucesso ao carregar:%1 Failed to load: %1 Falha ao carregar:%1 MainWindow Save Project Salvar Projeto Save this project? ¿Desea guardar este pryecto? Insert before ... Insertar antes... Insert after ... Insertar despues... Remove from project ... Eliminar del proyecto... Insert here ... Insertar aqui... Scan Tailor Projects Proyectos de Scan Tailor Open Project Abrir Proyecto Error Error Unable to open the project file. Imposible de abrir el archivo de el proyecto. The project file is broken. Este pryecto esta corrupto. 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". Salida no es aun posible debido a que el tamaño final de la pagina no es conocido aun. Para determinarlo, ejecute el proceso de "Seleccionar Contendio" o "Margenes". Unnamed Sin nombre %2 - Scan Tailor %3 [%1bit] Files to insert Skip failed files %1 - Scan Tailor %2 %1 - Scan Tailor %2 Error saving the project file! Se produjo un error a intentar guardar el proyecto! File to insert Archivo a insertar Images not in project (%1) Imagenes que no estan en el proyecto (%1) Error opening the image file. Se produjo un error al abrir el archivo de imagen. Remove Remover MainWindow Janela Principal Keep current page in view. Manter a janela atual. Use Home, End, PgUp (or Q), PgDown (or W) to navigate between pages. Use Home, End, PgUp (ou Q), PgDown (ou W) para navegar entre paginas. Tools Ferramentas File Arquivos Help Ajuda Debug Debug Debug Mode Modo Debug Ctrl+S Ctrl+S Save Project As ... Salvar Projeto como... Next Page Próxima Página PgDown PgDown Previous Page Pagina Anterior PgUp PgUp New Project ... Novo Projeto... Ctrl+N Ctrl+S Open Project ... Abrir Projeto... Ctrl+O Ctrl+O Q Q W W Close Project Fechar Projeto Ctrl+W Ctrl+W Quit Sair Ctrl+Q Ctrl+Q First Page Primeira Página Home Início Last Page Última Página End Final About Sobre Fix DPI ... Corrigir DPI ... Relinking ... Religar ... Stop batch processing Detener procesado Settings ... Preferencias ... NewOpenProjectPanel Form Modelo New Project ... Novo Projeto ... Open Project ... Abrir Projeto ... Recent Projects Projetos Recentes OrientationApplyDialog Fix Orientation Corrigir Orientação Apply to Aplicar a This page only (already applied) Somente esta página (já aplicado) All pages Todas as páginas This page and the following ones Esta página e as seguintes Every other page All odd or even pages, depending on the current page being odd or even. Todas as demais(exceto esta) The current page will be included. A página atual será incluida. Selected pages Páginas selecionadas Use Ctrl+Click / Shift+Click to select multiple pages. Usar Ctrl+Click / Shift+Click para selecionar multiplas páginas. Every other selected page Todas as demais selecionadas (exceto esta) OrientationOptionsWidget Form Modelo Rotate Girar ... ... Reset Resetar Scope Escopo Apply to ... Aplicar a... OutOfMemoryDialog Out of memory Sem memória disponível Out of Memory Situation in Scan Tailor Sem memória disponível no Scan Tailor Possible reasons Possíveis razões Did you have to fix the DPI of your source images? Are you sure the values you entered were correct? Teve que corrigir o DPI da imagem original? Tem certeza que os valores inseridos são corretos? 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. Às vezes as imagens podem ter um número de DPI errada de origem. Scan Tailor tenta detectá-lo, mas nem sempre é fácil. Pode ser necessário selecionar "Corrigir DPI mesmo que pareça normal" quando você cria um projeto e olhar na guia "Todas as páginas" na caixa de diálogo "Corrigir DPI", que também é acessível a partir do menu Ferramentas. Is your output DPI set too high? Usually you don't need it higher than 600. Seu DPI de saída é muito grande? Normalmente não é necessário mais que 600. What can help O que pode ajudar 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 seus DPIs. Aprenda aqui <a href="http://vimeo.com/12524529">como estimar DPIs desconhecidos</a>. If your hardware and operating system are 64-bit capable, consider switching to a 64-bit version of Scan Tailor. Se seu hardware e seu sistema operacional são 64-bits, considere mudar para a versão de 64-bits do 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. Ao trabalhar com tons de cinza, certifique-se que eles são realmente cinza. Se são imagens coloridas que parecem cinza, convertelas em escala de cinza usando algum tipo de conversor de lote. Isto irá economizar memória e aumentar o desempenho. 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. Finalmente, você pode economizar memória certificando-se que as miniaturas são pré-criadas, em vez de ser criada automaticamente. Isto pode ser conseguido movendo-se lentamente a lista de miniaturas de cima para baixo antes de iniciar qualquer trabalho. What won't help O que não ajuda 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. Surpreendentemente, o aumento da RAM não vai ajudar aqui. A falta de RAM é compensado pelo mecanismo de troca que gera uma memória virtual no disco rígido e que torna as coisas lentas, mas mantém o programa em execução. A situação de falta de memória significa que o programa funciona fora de endereços de memória disponíveis, que não tem nada a ver com a quantidade de RAM que você tem. A única maneira de aumentar o endereço de memória é passado para um hardware de 64 bits, sistema operacional de 64 bits e Scan Tailor de 64 bits. Save Project Salvar Projeto Save Project As ... Salvar Projeto Como... Don't Save Não salvar Project Saved Successfully Projeto salvo com sucesso 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. Observe que, enquanto o Scan Tailor tenta alocar alguma memoria e lhe dá a oportunidade de salvar seu projeto, nem sempre isso é possível. Desta vez ele conseguiu, mas pode ser que na próxima não consiga. Scan Tailor Projects Proyectos de Scan Tailor Error Error saving the project file! Se produjo un error a intentar guardar el proyecto! OutputApplyColorsDialog Apply Mode Aplicar modo Apply to Aplicar a This page only (already applied) Somente esta página (já aplicado) All pages Todas as páginas This page and the following ones Esta página e as que seguem Selected pages Páginas selecionadas Use Ctrl+Click / Shift+Click to select multiple pages. Usar Ctrl+Click / Shift+Click para selecionar multiplas páginas. OutputChangeDewarpingDialog Apply Dewarping Mode Aplicar modo de anti-deformação Mode Modo Off Desligado Auto (experimental) Automatico (experimental) Manual Manual Scope Escopo This page only Somente esta página All pages Todas as páginas This page and the following ones Esta página e as que seguem Selected pages Paginas selecionadas Use Ctrl+Click / Shift+Click to select multiple pages. Usar Ctrl+Click / Shift+Click para selecionar multiplas páginas. OutputChangeDpiDialog Apply Output Resolution Aplicar Resolução de saída DPI DPI Scope Escopo This page only Somente esta página This page and the following ones Esta página e as que seguem Selected pages Paginas selecionadas Use Ctrl+Click / Shift+Click to select multiple pages. Usar Ctrl+Click / Shift+Click para selecionar multiplas páginas. All pages Todas as páginas OutputOptionsWidget Form Modelo Output Resolution (DPI) Resolução de Saída (DPI) Change ... Mudar ... Mode Modo White margins Margens Brancas Equalize illumination Equalizar Iluminação Dewarping Anti-deformação Depth perception Percepção de profundidade No despeckling Não eliminar manchas Apply To ... Aplicar a... Despeckling Eliminar manchas Cautious despeckling Eliminador de manchas fraco ... ... Normal despeckling Eliminador de manchas normal Aggressive despeckling Eliminador de manchas forte Thinner Fino Thicker Grosso PageLayoutApplyDialog Apply to Aplicar a This page only (already applied) Somente esta página (já aplicado) All pages Todas as páginas This page and the following ones Esta página e as que seguem Every other page All odd or even pages, depending on the current page being odd or even. Todas as demais The current page will be included. A página atual será incluida. Selected pages Páginas selecionadas Use Ctrl+Click / Shift+Click to select multiple pages. Usar Ctrl+Click / Shift+Click para selecionar multiplas páginas. Every other selected page Todas as demais selecionadas (exceto esta) PageLayoutOptionsWidget Form Modelo Margins Margens Millimeters (mm) Milimetros (mm) Inches (in) Polegadas (in) Top Acima ... ... Bottom Abaixo Left Esquerda Right Direita Apply To ... Aplicar a... Alignment Alinhamento Match size with other pages Igualar em tamanho as demais páginas PageSplitModeDialog Split Pages Dividir Páginas Mode Modo Auto Auto Manual Manual Scope Escopo This page only Somente esta página This page and the following ones Esta página e as que seguem Selected pages Páginas selecionadas Use Ctrl+Click / Shift+Click to select multiple pages. Usar Ctrl+Click / Shift+Click para selecionar multiplas páginas. All pages Todas as páginas PageSplitOptionsWidget Form Modelo Page Layout Layout da Página ? ? Change ... Mudar ... Split Line need context Linha Divisória Auto Auto Manual Manual PictureZonePropDialog Zone Properties Propriedades da Zona Subtract from all layers Subtrair de todas as camadas Add to auto layer Agregar a camada automatica Subtract from auto layer Sustrair da camada automatica ProjectFilesDialog Input Directory Pasta de entrada Output Directory Pasta de saida Error Error No files in project! No hay archivos en el proyecto! Input directory is not set or doesn't exist. La carpeta de entrada no esta fijado o no existe. Input and output directories can't be the same. Las carpetas de entrada y de salida no pueden ser las mismas. Create Directory? ¿Crear Carpeta? Output directory doesn't exist. Create it? La carpeta de salida no existe. ¿Desea crearla? Unable to create output directory. Imposible crear la carpeta de salida. Output directory is not set or doesn't exist. La carpeta de salida no esta fijada 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. Algunos de los archivos fallaron al cargar. O es un formato no soportado, o estan corruptos. Deberia quitarlos del proyecto. Project Files Arquivos do Projeto Browse Procurar Files Not In Project Arquivos fora do projeto Select All Selecionar tudo <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;">Remove selected files from project.</p></body></html> Add selected files to project. Adicionar arquivos selecionados ao projeto >> >> Remove selected files from project./html> Remover arquivos selecionados do projeto << << Files In Project Arquivos no projeto Right to left layout (for Hebrew and Arabic) Layout da direita para esquerda(para Hebreu e Ãrabe) Fix DPIs, even if they look OK Corrigir DPI mesmo que pareça normal ProjectOpeningContext Error Error Unable to interpret the project file. Imposible de interpretar el archivo del proyecto. RelinkingDialog Relinking Revincular Undo Desfazer ... ... Substitution File for %1 Substitution Directory for %1 This change would merge several files into one. RemovePagesDialog Remove Pages Remover Páginas Remove %1 page(s) from project? Remover %1 página(s) do projeto? Corresponding output files will be deleted, while input files will remain. Arquivos de saída correspondentes serão excluídos, enquanto os arquivos de entrada serão mantidos. SelectContentApplyDialog Select Content Selecione o Conteúdo Apply to Aplicar a This page only (already applied) Somente esta página (já aplicado) All pages Todas as páginas This page and the following ones Esta página e as que seguem Every other page All odd or even pages, depending on the current page being odd or even. Todas as pares ou impares The current page will be included. A página atual será incluida. Selected pages Página selecionada Use Ctrl+Click / Shift+Click to select multiple pages. Usar Ctrl+Click / Shift+Click para selecionar multiplas páginas. Every other selected page Todas as demais selecionadas SelectContentOptionsWidget Form Modelo Content Box Caixa de conteúdo Auto Auto Manual Manual Scope Escopo Apply to ... Aplicar a... SettingsDialog Compiled without OpenGL support. Compilado sin soporte OpenGL. Your hardware / driver don't provide the necessary features. Su Hardware / driver no provee de las caracteristicas necesarias. Settings Preferências Use 3D acceleration for user interface Usar aceleração 3D para a interface de usuário StageListView Launch batch processing Comenzar procesado SystemLoadWidget Form Modelo System load Carga do sistema ... ... Minimal Minimo Normal Normal ThumbnailSequence %1 (page %2) %1 (pagina %2) ZoneContextMenuInteraction Properties Propriedades Delete Excluir Delete confirmation Confirmação de exclusão Really delete this zone? Realmente excluir esta zona? ZoneCreationInteraction Click to finish this zone. ESC to cancel. Clicar para finalizar esta zona. ESC para cancelar. Connect first and last points to finish this zone. ESC to cancel. Ligue o primeiro e último ponto para terminar esta zona. ESC para cancelar. Zones need to have at least 3 points. ESC to cancel. Zonas precisa ter ao menos 3 pontos. ESC para cancelar. ZoneDefaultInteraction Drag the vertex. Arraste o vértice. Click to create a new vertex here. Clique para criar um novo vértice. Right click to edit zone properties. Clique com o botão direito para editar as propriedades da zona. Click to start creating a new picture zone. Clique para criar uma nova imagem da zona. ZoneVertexDragInteraction Merge these two vertices. Mesclar estes dois vértices. Move the vertex to one of its neighbors to merge them. Mover o vértice para o vértice mais próximo e mescla-los. deskew::Filter Deskew Alinhamento deskew::ImageView Use Ctrl+Wheel to rotate or Ctrl+Shift+Wheel for finer rotation. Use Ctrl+Roda para girar ou Ctrl+Shift+Roda para uma rotação mais precisa. Drag this handle to rotate the image. Arrastre para girar a imagem. deskew::OptionsWidget Apply Deskew Aplicar Alinhamento fix_orientation::ApplyDialog Can't do: more than one group is selected. Impossível continuar: mais de um grupo está selecionado. fix_orientation::Filter Fix Orientation Corrigir Orientação output::ChangeDpiDialog Custom Personalizar Error Erro DPI is not set. DPI não configurado. DPI is too low! DPI muito baixo! DPI is too high! DPI muito alto! output::FillZoneEditor Pick color Escolher cor output::Filter Output Saída output::OptionsWidget Black and White Branco e Preto Color / Grayscale Cor / Tons de Cinza Mixed Misto Apply Despeckling Level Aplicar nivel de eliminação de manchas Apply Depth Perception Aplicar Profundidade de Percepção Off Desligado Auto Auto Manual Manual output::Task::UiUpdater Picture zones are only available in Mixed mode. Zonas de imagens só estão disponíveis no modo Misto. Despeckling can't be done in Color / Grayscale mode. Eliminação de manchas não funciona no modo Cor / Tons de Cinza. Output Saída Picture Zones Propriedades das Zonas da Imagem Fill Zones Preenche Zonas Dewarping Anti-deformação Despeckling Eliminar manchas page_layout::ApplyDialog Can't do: more than one group is selected. Impossível continuar: mais de um grupo está selecionado. page_layout::Filter Natural order Ordem Natural Order by increasing width Ordenar por largura Order by increasing height Ordenar por altura Margins Margens page_layout::ImageView Resize margins by dragging any of the solid lines. Modifique as margens arrastrando qualquer das linhas. page_layout::OptionsWidget Apply Margins Aplicar Margens Apply Alignment Aplicar Alinhamento page_split::Filter Natural order Ordem Natural Order by split type Ordenar por tipo de divisão Split Pages Dividir Paginas page_split::ImageView Drag the line or the handles. Arraste a linha ou as alças. page_split::OptionsWidget Set manually Corrigir Manualmente Auto detected Detecção Automatica page_split::UnremoveButton Restore removed page. Restaurar página removida. select_content::ApplyDialog Can't do: more than one group is selected. Impossível continuar: mais de um grupo está selecionado. select_content::Filter Natural order Ordem Natural Order by increasing width Ordenar por largura Order by increasing height Ordenar por altura Select Content Selecione o Conteúdo select_content::ImageView Use the context menu to enable / disable the content box. Use o menu contextual para habilitar / desabilitar a caixa de conteúdo. Drag lines or corners to resize the content box. Arraste linhas ou cantos para redimencionar a caixa de conteúdo. Create Content Box Criar uma Caixa de Conteúdo Remove Content Box Remover a Caixa de Conteúdo scantailor-RELEASE_0_9_12_2/translations/scantailor_ru.ts000066400000000000000000003573371271170121200234370ustar00rootroot00000000000000 AboutDialog About Scan Tailor О программе Scan Tailor 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 recogrnition is out of scope of this project. Scan Tailor - Ñто интерактивный инÑтрумент Ð´Ð»Ñ Ð¿Ð¾ÑÑ‚-обработки Ñканированных Ñтраниц. Он делает такие операции как разрезание Ñтраниц, компенÑÐ°Ñ†Ð¸Ñ Ð½Ð°ÐºÐ»Ð¾Ð½Ð°, добавление/удаление полей, и другие. Ð’Ñ‹ даете ему необработанные Ñканы, а в результате получаете Ñтраницы, готовые Ð´Ð»Ñ Ð¿ÐµÑ‡Ð°Ñ‚Ð¸ или Ñборки в PDF или DJVU файл. Сканирование и оптичеÑкое раÑпознавание Ñимволов не входÑÑ‚ в задачи проекта. 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 ИоÑиф Ðрцимович 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. Petr Kovář - ВерÑÐ¸Ñ Ð´Ð»Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð½Ð¾Ð¹ Ñтроки. Neco Torquato - Brazilian Portuguese Svetoslav Sashkov, Mandor - Bulgarian Petr Kovář - Czech Stefan Birkner - German Angelo Gemmi - Italian Masahiro Kitagawa - Japanese Patrick Pascal - French Daniel Koć - Polish Joseph Artsimovich - Russian Marián Hvolka - Slovak Flavio Benelli - Spanish Davidson Wang - Traditional Chinese Yuri Chornoivan - Ukrainian denver 22 Translators Переводчики Svetoslav Sashkov, Mandor - Bulgarian Stefan Birkner - German Angelo Gemmi - Italian Masahiro Kitagawa - Japanese Patrick Pascal - French Joseph Artsimovich - Russian Davidson Wang - Traditional Chinese Svetoslav Sashkov, Mandor - БолгарÑкий Stefan Birkner - German Angelo Gemmi - Italian Masahiro Kitagawa - ЯпонÑкий Patrick Pascal - French ИоÑиф Ðрцимович - РуÑÑкий Davidson Wang - Traditional Chinese Contributors БлагодарноÑти U235 - Picture auto-detection algorithm. Robert B. - Dewarping algorithm. Andrey Bergman - System load adjustment. U235 - Ðлгоритм Ð°Ð²Ñ‚Ð¾Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ ÐºÐ°Ñ€Ñ‚Ð¸Ð½Ð¾Ðº. Robert B. - Ðлгоритм уÑÑ‚Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð³ÐµÐ¾Ð¼ÐµÑ‚Ñ€Ð¸Ñ‡ÐµÑких иÑкажений. Andrey Bergman - Регулировка загрузки ÑиÑтемы. Documentation Ð”Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ License Ð›Ð¸Ñ†ÐµÐ½Ð·Ð¸Ñ BatchProcessingLowerPanel Form Beep when finished Звуковой Ñигнал по окончании ColorPickupInteraction Click on an area to pick up its color, or ESC to cancel. Кликните на облаÑть, чтобы захватить ее цвет, или ESC Ð´Ð»Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ñ‹. DeskewApplyDialog 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+Клик Ð´Ð»Ñ Ð²Ñ‹Ð±Ð¾Ñ€Ð° группы Ñтраниц. DeskewOptionsWidget Form Deskew КомпенÑÐ°Ñ†Ð¸Ñ Ð½Ð°ÐºÐ»Ð¾Ð½Ð° Auto ÐвтоматичеÑки Manual Вручную Apply To ... Применить к ... DragHandler Unrestricted dragging is possible by holding down the Shift key. Ð£Ð´ÐµÑ€Ð¶Ð¸Ð²Ð°Ñ ÐºÐ½Ð¾Ð¿ÐºÑƒ Shift, можно перетаÑкивать без ограничений. ErrorWidget Form FixDpiDialog 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 не получить. DPI is too small for this pixel size. Such combination would probably lead to out of memory errors. Значение DPI Ñлишком маленькое Ð´Ð»Ñ Ð´Ð°Ð½Ð½Ð¾Ð³Ð¾ пикÑельного размера. Ð¢Ð°ÐºÐ°Ñ ÐºÐ¾Ð¼Ð±Ð¸Ð½Ð°Ñ†Ð¸Ñ Ñкорее вÑего привела бы к нехватке памÑти. %1 (page %2) %1 (Ñтраница %2) Fix DPI ИÑправить DPI Tab 1 Tab 2 DPI Custom ОÑобый x Apply Применить FixDpiSinglePageDialog Fix DPI ИÑправить DPI DPI for %1 DPI Ð´Ð»Ñ %1 Custom ОÑобый %1 (page %2) %1 (Ñтраницы %2) 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 не получить. DPI is too small for this pixel size. Such combination would probably lead to out of memory errors. Значение DPI Ñлишком маленькое Ð´Ð»Ñ Ð´Ð°Ð½Ð½Ð¾Ð³Ð¾ пикÑельного размера. Ð¢Ð°ÐºÐ°Ñ ÐºÐ¾Ð¼Ð±Ð¸Ð½Ð°Ñ†Ð¸Ñ Ñкорее вÑего привела бы к нехватке памÑти. ImageViewBase Use the mouse wheel or +/- to zoom. When zoomed, dragging is possible. ИÑпользуйте колеÑо мыши Ð´Ð»Ñ ÑƒÐ²ÐµÐ»Ð¸Ñ‡ÐµÐ½Ð¸Ñ. Ð’ увеличенном виде доÑтупно перетаÑкивание. Unrestricted dragging is possible by holding down the Shift key. Ð£Ð´ÐµÑ€Ð¶Ð¸Ð²Ð°Ñ ÐºÐ½Ð¾Ð¿ÐºÑƒ Shift, можно перетаÑкивать без ограничений. InteractiveXSpline Click to create a new control point. Кликните Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð½Ð¾Ð²Ð¾Ð¹ контрольной точки. This point can be dragged. ÐšÐ¾Ð½Ñ‚Ñ€Ð¾Ð»ÑŒÐ½Ð°Ñ Ñ‚Ð¾Ñ‡ÐºÐ° доÑтупна Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÑ‚Ð°ÑкиваниÑ. Drag this point or delete it by pressing Del or D. ÐšÐ¾Ð½Ñ‚Ñ€Ð¾Ð»ÑŒÐ½Ð°Ñ Ñ‚Ð¾Ñ‡ÐºÐ° доÑтупна Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÑ‚Ð°ÑÐºÐ¸Ð²Ð°Ð½Ð¸Ñ Ð¸ Ð´Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ñ‡ÐµÑ€ÐµÐ· Del или D. 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. LoadFilesStatusDialog Some files failed to load Ðекоторые файлы не удалоÑÑŒ загрузить Loaded successfully: %1 Загружено: %1 Failed to load: %1 Ðе удалоÑÑŒ загрузить: %1 MainWindow Error Ошибка 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". Вывод невозможен, поÑкольку еще не извеÑтны итоговые размеры Ñтраниц. Ð”Ð»Ñ Ð¸Ñ… определениÑ, прогоните пакетную обработку на Ñтапах "ÐŸÐ¾Ð»ÐµÐ·Ð½Ð°Ñ ÐžÐ±Ð»Ð°Ñть' или "ПолÑ". %2 - Scan Tailor %3 [%1bit] Error saving the project file! Ошибка при Ñохранении файла! Save Project Сохранить проект Save this project? Сохранить Ñтот проект? Scan Tailor Projects Проекты Scan Tailor You can't output pages yet. First you need to process all of them with the "Page Layout" filter. Вывод невозможен, поÑкольку еще не извеÑтны итоговые размеры Ñтраниц. Ð”Ð»Ñ Ð¸Ñ… определениÑ, прогоните пакетную обработку на Ñтапах "ÐŸÐ¾Ð»ÐµÐ·Ð½Ð°Ñ ÐžÐ±Ð»Ð°Ñть' или "Макет Ñтраницы". Unnamed Без имени MainWindow Keep current page in view. Держать текущую Ñтраницу в поле зрениÑ. Use Home, End, PgUp (or Q), PgDown (or W) to navigate between pages. ИÑпользуйте Home, End, PgUp (или Q), PgDown (или W) Ð´Ð»Ñ Ð½Ð°Ð²Ð¸Ð³Ð°Ñ†Ð¸Ð¸ по Ñтраницам. Help Помощь Debug Режим отладки Debug Mode Режим отладки Ctrl+S Save Project As ... Сохранить проект как ... Next Page Ð¡Ð»ÐµÐ´ÑƒÑŽÑ‰Ð°Ñ Ñтраница PgDown Previous Page ÐŸÑ€ÐµÐ´Ñ‹Ð´ÑƒÑ‰Ð°Ñ Ñтраница PgUp First Page Первый Ñтраница Home Last Page ПоÑледнÑÑ Ñтраница End About О программе Fix DPI ... ИÑправить DPI ... Relinking ... Пути в проекте ... Start Batch Processing ЗапуÑтить пакетную обработку Stop Batch Processing ОÑтановить пакетную обработку Tools ИнÑтрументы File Файл Process Обработка 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 "Page Layout". Вывод невозможен, поÑкольку еще не извеÑтны итоговые размеры Ñтраниц. Ð”Ð»Ñ Ð¸Ñ… определениÑ, прогоните пакетную обработку на Ñтапах "ÐŸÐ¾Ð»ÐµÐ·Ð½Ð°Ñ Ð¾Ð±Ð»Ð°Ñть" или "Макет Ñтраницы". Open Project Открыть проект Beep when finished Звуковой Ñигнал по окончании Insert before ... Ð’Ñтавить перед ... Insert after ... Ð’Ñтавить поÑле ... Remove from project ... Удалить из проекта ... Insert here ... Ð’Ñтавить Ñюда ... Unable to open the project file. Ðе удалоÑÑŒ открыть файл проекта. The project file is broken. Файл проекта поврежден. Files to insert Skip failed files Unable to interpret the project file. Ðе удалоÑÑŒ импортировать проект. File to insert Ð’Ñтавить файл Images not in project (%1) Ð˜Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð½Ðµ в проекте (%1) Error opening the image file. Ошибка при открытии файла изображениÑ. %1 (page %2) %1 (Ñтраницы %2) Remove Удалить Unable to delete file: %1 Ðевозможно удалить файл: %1 New Project ... Ðовый проект ... Ctrl+N Open Project ... Открыть проект ... Ctrl+O Use PgUp, PgDown or Q, W to navigate between pages. Страницы можно лиÑтать Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ PgUp и PgDown, а также Q и W. Q W Close Project Закрыть проект Ctrl+W Quit Выход Ctrl+Q Keep the current page in view. ÐвтоматичеÑки переходить к выбранной Ñтранице. Scroll to a selected page automatically. ÐвтоматичеÑки переходить к выбранной Ñтранице. Thumbnail list auto-scrolling. Ðвтопрокрутка ленты предпроÑмотра. Stop batch processing ОÑтановить пакетную обработку Settings ... ÐаÑтройки ... NewOpenProjectDialog New Project Ðовый проект Open Project Открыть проект NewOpenProjectPanel Form New Project Ðовый проект Open Project Открыть проект Recent Projects Ðедавние проекты New Project ... Ðовый проект ... Open Project ... Открыть проект ... OrientationApplyDialog Fix Orientation ИÑправление ориентации Apply to Применить This page only (already applied) Только к Ñтой Ñтранице (уже применено) All pages Ко вÑем Ñтраницам This page and the following ones К Ñтой Ñтранице и вÑем поÑледующим Every other page All odd or even pages, depending on the current page being odd or even. К каждой второй Ñтранице The current page will be included. Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ñтраница будет включена в ÑпиÑок. Selected pages К выбранным Ñтраницам Use Ctrl+Click / Shift+Click to select multiple pages. ИÑпользуйте Ctrl+Клик / Shift+Клик Ð´Ð»Ñ Ð²Ñ‹Ð±Ð¾Ñ€Ð° группы Ñтраниц. Every other selected page К каждой второй выбранной Ñтранице Every page in range Ко вÑем Ñтраницам в диапазоне Every other page in range К каждой второй Ñтранице в диапазоне Range Диапазон OrientationOptionsWidget Form Rotate Поворот ... Reset Ð¡Ð±Ñ€Ð¾Ñ Scope ОблаÑть Ð¿Ñ€Ð¸Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Apply to ... Применить к ... OutOfMemoryDialog Out of memory Scan Tailor: Ðехватка памÑти 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. ИÑправьте неправильные DPI. ÐаучитеÑÑŒ <a href="http://vimeo.com/12527484">раÑчитывать неизвеÑтные 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! Ошибка при Ñохранении файла! OutputApplyColorsDialog Apply Colors Применить режим вывода Scope Применить 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+Клик Ð´Ð»Ñ Ð²Ñ‹Ð±Ð¾Ñ€Ð° группы Ñтраниц. Apply Mode Применить режим вывода OutputChangeDewarpingDialog Apply Dewarping Mode Режим раÑпрÑÐ¼Ð»ÐµÐ½Ð¸Ñ Ñтрок Mode Режим Off Отключено Auto (experimental) ÐвтоматичеÑки (ÑкÑÐ¿ÐµÑ€Ð¸Ð¼ÐµÐ½Ñ‚Ð°Ð»ÑŒÐ½Ð°Ñ Ñ„ÑƒÐ½ÐºÑ†Ð¸Ñ) Manual Вручную Scope Применить This page only Только к Ñтой Ñтранице All pages Ко вÑем Ñтраницам This page and the following ones К Ñтой Ñтранице и вÑем поÑледующим Selected pages К выбранным Ñтраницам Use Ctrl+Click / Shift+Click to select multiple pages. ИÑпользуйте Ctrl+Клик / Shift+Клик Ð´Ð»Ñ Ð²Ñ‹Ð±Ð¾Ñ€Ð° группы Ñтраниц. OutputChangeDpiDialog DPI Scope Применить This page only (already applied) Только к Ñтой Ñтранице (уже применено) This page and the following ones К Ñтой Ñтранице и вÑем поÑледующим Selected pages К выбранным Ñтраницам Use Ctrl+Click / Shift+Click to select multiple pages. ИÑпользуйте Ctrl+Клик / Shift+Клик Ð´Ð»Ñ Ð²Ñ‹Ð±Ð¾Ñ€Ð° группы Ñтраниц. This page only Только к Ñтой Ñтранице All pages Ко вÑем Ñтраницам Apply Output Resolution Применить разрешение вывода OutputOptionsWidget Form Output Resolution (DPI) Разрешение вывода (DPI) Change ... Изменить ... Dewarping РаÑпрÑмление Ñтрок Depth perception ВоÑприÑтие глубины No despeckling Ðе удалÑть пÑтна Dewarp УÑтранение геометричеÑких иÑкажений Despeckling Удаление пÑтен Cautious despeckling ОÑторожное удаление пÑтен ... Normal despeckling Обычное удаление пÑтен Aggressive despeckling ÐгреÑÑивное удаление пÑтен Colors Режим Light Светлый Dark Темный B/W Threshold Черно-белый порог Apply To ... Применить к ... Mode Режим White margins Белые Ð¿Ð¾Ð»Ñ Equalize illumination ВыровнÑть оÑвещение Despeckle УдалÑть пÑтна Thinner Тоньше Thicker Жирнее PageLayoutApplyDialog Scope Применить Apply to Применить This page only (already applied) Только к Ñтой Ñтранице (уже применено) All pages Ко вÑем Ñтраницам This page and the following ones К Ñтой Ñтранице и вÑем поÑледующим Every other page All odd or even pages, depending on the current page being odd or even. К каждой второй Ñтранице The current page will be included. Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ñтраница будет включена в ÑпиÑок. Selected pages К выбранным Ñтраницам Use Ctrl+Click / Shift+Click to select multiple pages. ИÑпользуйте Ctrl+Клик / Shift+Клик Ð´Ð»Ñ Ð²Ñ‹Ð±Ð¾Ñ€Ð° группы Ñтраниц. Every other selected page К каждой второй выбранной Ñтранице PageLayoutOptionsWidget Form Margins ÐŸÐ¾Ð»Ñ Millimeters (mm) Миллиметры (mm) Inches (in) Дюймы (in) Top Сверху ... Bottom Снизу Left Слева Right Справа Apply To ... Применить к ... Alignment Выравнивание Match size with other pages ВыровнÑть размеры Ñ Ð´Ñ€ÑƒÐ³Ð¸Ð¼Ð¸ Ñтраницами Align with other pages ВыровнÑть Ñ Ð´Ñ€ÑƒÐ³Ð¸Ð¼Ð¸ Ñтраницами Go to the widest page. Перейти к Ñамой широкой Ñтранице. <!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:'Sans Serif'; font-size:10pt; 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 href="widest_page"><span style=" text-decoration: underline; color:#0057ae;">Widest Page</span></a></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:'Sans Serif'; font-size:10pt; 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 href="widest_page"><span style=" text-decoration: underline; color:#0057ae;">Ð¡Ð°Ð¼Ð°Ñ ÑˆÐ¸Ñ€Ð¾ÐºÐ°Ñ Ñтраница</span></a></p></body></html> Go to the tallest page. Перейти к Ñамой выÑокой Ñтранице. <!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:'Sans Serif'; font-size:10pt; 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 href="tallest_page"><span style=" text-decoration: underline; color:#0057ae;">Tallest Page</span></a></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:'Sans Serif'; font-size:10pt; 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 href="tallest_page"><span style=" text-decoration: underline; color:#0057ae;">Ð¡Ð°Ð¼Ð°Ñ Ð²Ñ‹ÑÐ¾ÐºÐ°Ñ Ñтраница</span></a></p></body></html> Widest Page Ð¡Ð°Ð¼Ð°Ñ ÑˆÐ¸Ñ€Ð¾ÐºÐ°Ñ Ñтраница Tallest Page Ð¡Ð°Ð¼Ð°Ñ Ð²Ñ‹ÑÐ¾ÐºÐ°Ñ Ñтраница PageSplitModeDialog Split Pages Разрезание Ñтраниц Mode Тип разреза Auto ÐвтоматичеÑки Manual Вручную Scope Применить This page only (already applied) Только к Ñтой Ñтранице (уже применено) This page and the following ones К Ñтой Ñтранице и вÑем поÑледующим Selected pages К выбранным Ñтраницам Use Ctrl+Click / Shift+Click to select multiple pages. ИÑпользуйте Ctrl+Клик / Shift+Клик Ð´Ð»Ñ Ð²Ñ‹Ð±Ð¾Ñ€Ð° группы Ñтраниц. This page only Только к Ñтой Ñтранице All pages Ко вÑем Ñтраницам PageSplitOptionsWidget Form Page Layout Тип разреза ? Change ... Изменить ... Split Line Ð Ð°Ð·Ð´ÐµÐ»Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð»Ð¸Ð½Ð¸Ñ Auto ÐвтоматичеÑки Manual Вручную PictureZonePropDialog Zone Properties СвойÑтва зоны Subtract from all layers ВычеÑть из вÑех Ñлоев Add to auto layer Добавить к авто-Ñлою Subtract from auto layer ВычеÑть из авто-ÑÐ»Ð¾Ñ ProjectFilesDialog Input Directory Ð”Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ Ð²Ð²Ð¾Ð´Ð° Output Directory Ð”Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ Ð²Ñ‹Ð²Ð¾Ð´Ð° 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. Ðекоторые из файлов не загрузилиÑÑŒ. Либо программа не поддерживает их формат, либо они повреждены. Вам Ñледует удалить их из проекта. Project Files Файлы проекта Browse Обзор 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;"> <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;">Добавить выбранные файлы в проект.</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;">Убрать выбранные файлы из проекта.</p></body></html> << Files In Project Файлы в проекте Right to left layout (for Hebrew and Arabic) СиÑтема пиÑьменноÑти Ñправа-налево (Ð´Ð»Ñ Ð˜Ð²Ñ€Ð¸Ñ‚Ð° или ÐрабÑкого) Fix DPIs, even if they look OK Править DPI, даже еÑли они выглÑдÑÑ‚ нормальными ProjectOpeningContext Open Project Открыть проект Scan Tailor Projects Проекты Scan Tailor Error Ошибка Unable to open the project file. Ðе удалоÑÑŒ открыть файл проекта. The project file is broken. Файл проекта поврежден. Unable to interpret the project file. Ðе удалоÑÑŒ импортировать проект. RelinkingDialog Relinking Пути проекта Undo Откатить дейÑтвие ... Substitution File for %1 Substitution Directory for %1 This change would merge several files into one. RemoveFileDialog Remove File Удалить файл Remove %1 from project? Удалить %1 из проекта? Also delete the file from disk Удалить его также и Ñ Ð´Ð¸Ñка RemovePagesDialog Remove Pages Удалить Ñтраницы Remove %1 page(s) from project? Удалить %1 Ñтраниц из проекта? Corresponding output files will be deleted, while input files will remain. СоответÑтвующие файлы вывода будут удалены, а иÑходные оÑтанутÑÑ Ð½Ð° диÑке. Files will remain on disk Файлы оÑтанутÑÑ Ð½Ð° диÑке SelectContentApplyDialog Select Content ÐŸÐ¾Ð»ÐµÐ·Ð½Ð°Ñ Ð¾Ð±Ð»Ð°Ñть Apply to Применить This page only (already applied) Только к Ñтой Ñтранице (уже применено) All pages Ко вÑем Ñтраницам This page and the following ones К Ñтой Ñтранице и вÑем поÑледующим Every other page All odd or even pages, depending on the current page being odd or even. К каждой второй Ñтранице The current page will be included. Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ñтраница будет включена в ÑпиÑок. Selected pages К выбранным Ñтраницам Use Ctrl+Click / Shift+Click to select multiple pages. ИÑпользуйте Ctrl+Клик / Shift+Клик Ð´Ð»Ñ Ð²Ñ‹Ð±Ð¾Ñ€Ð° группы Ñтраниц. Every other selected page К каждой второй выбранной Ñтранице SelectContentOptionsWidget Form Content Box ÐŸÐ¾Ð»ÐµÐ·Ð½Ð°Ñ Ð¾Ð±Ð»Ð°Ñть Auto ÐвтоматичеÑки Manual Вручную Scope Применить Apply to ... Применить к ... SettingsDialog Compiled without OpenGL support. Собрано без поддержки OpenGL. Your hardware / driver don't provide the necessary features. Ваше железо или драйвер не поддерживают необходимого функционала. Settings ÐаÑтройки Use 3D acceleration for user interface ИÑпользовать 3D уÑкорение Ð´Ð»Ñ Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñа Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ StageListView Launch batch processing ЗапуÑтить пакетную обработку SystemLoadWidget Form System load Загрузка ÑиÑтемы ... Minimal ÐœÐ¸Ð½Ð¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Normal МакÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ ThumbnailSequence %1 (page %2) %1 (Ñтр. %2) ZoneContextMenuInteraction Properties СвойÑтва Delete Удалить Delete confirmation Подтверждение ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Really delete this zone? ДейÑтвительно удалить Ñту зону? ZoneCreationInteraction Click to finish this zone. ESC to cancel. Кликните Ð´Ð»Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ñтой зоны. ESC Ð´Ð»Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ñ‹. Connect first and last points to finish this zone. ESC to cancel. Соедините первую и поÑледнюю точку Ð´Ð»Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ñтой зоны. ESC Ð´Ð»Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ñ‹. Zones need to have at least 3 points. ESC to cancel. Зоны должны Ñодержать по крайней мере 3 точки. ESC Ð´Ð»Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ñ‹. ZoneDefaultInteraction Drag the vertex. Вершину можно перетаÑкивать. Click to create a new vertex here. Кликните Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð½Ð¾Ð²Ð¾Ð¹ вершины. Right click to edit zone properties. Правый клик Ð´Ð»Ñ Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ ÑвойÑтв зоны. Click to start creating a new picture zone. Кликните чтобы начать Ñоздание новой зоны. ZonePropertiesDialog Zone Properties СвойÑтва зоны Subtract from all layers ВычеÑть из вÑех Ñлоев Add to auto layer Добавить к авто-Ñлою Subtract from auto layer ВычеÑть из авто-ÑÐ»Ð¾Ñ ZoneVertexDragInteraction Merge these two vertices. Объединить Ñти две вершины. Move the vertex to one of its neighbors to merge them. Придвиньте вершину к одной из ÑоÑедних вершин, чтобы объединить их. deskew::Filter Deskew КомпенÑÐ°Ñ†Ð¸Ñ Ð½Ð°ÐºÐ»Ð¾Ð½Ð° deskew::ImageView Use Ctrl+Wheel to rotate or Ctrl+Shift+Wheel for finer rotation. ИÑпользуйте Ctrl+КолеÑо мыши Ð´Ð»Ñ Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ, или Ctrl+Shift+КолеÑо мыши Ð´Ð»Ñ Ð±Ð¾Ð»ÐµÐµ точного вращениÑ. Drag this handle to rotate the image. ТÑните Ñту рукоÑтку чтобы повернуть изображение. deskew::OptionsWidget Apply Deskew Применить наклон fix_orientation::ApplyDialog Error Ошибка Range is required. ТребуетÑÑ ÑƒÐºÐ°Ð·Ð°Ñ‚ÑŒ диапазон. Page %1 (the current page) must be inside the range. Страница %1 (Ñ‚ÐµÐºÑƒÑ‰Ð°Ñ Ñтраница) должна быть внутри диапазона. Can't do: more than one group is selected. Ðевозможно: выбрано неÑколько групп Ñтраниц. fix_orientation::Filter Fix Orientation ИÑправление ориентации output::ChangeDpiDialog Error Ошибка Horizontal DPI is not set. Горизонтальный DPI не уÑтановлен. Vertical DPI is not set. Вертикальный DPI не уÑтановлен. DPI is too low! DPI Ñлишком низкий! DPI is too high! DPI Ñлишком выÑокий! Custom ОÑобый DPI is not set. DPI не указан. output::FillZoneEditor Pick color Выбрать цвет output::Filter Output Вывод output::OptionsWidget Black and White Черно-белый Bitonal Двухтоновый Color / Grayscale Цветной / Серый Mixed Смешанный Apply Despeckling Level Применить уровень ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ð¿Ñтен Apply Depth Perception Применить воÑприÑтие глубины Off Отключено Auto ÐвтоматичеÑки Manual Вручную output::PictureZoneEditor::ContextMenuHandler Properties СвойÑтва Delete Удалить Delete confirmation Подтверждение ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Really delete this zone? ДейÑтвительно удалить Ñту зону? output::PictureZoneEditor::DefaultState Drag the vertex. Вершину можно перетаÑкивать. Click to create a new vertex here. Кликните Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð½Ð¾Ð²Ð¾Ð¹ вершины. Right click to edit zone properties. Правый клик Ð´Ð»Ñ Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ ÑвойÑтв зоны. Click to start creating a new picture zone. Кликните чтобы начать Ñоздание новой зоны. output::PictureZoneEditor::SplineCreationState Finish the zone by connecting its start and end points. Закончите зону, Ñоединив начало Ñ ÐºÐ¾Ð½Ñ†Ð¾Ð¼. At least 3 edges are required. ESC to discard this zone. Ðужно как минимум 3 ребра. ESC Ð´Ð»Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ñ‹ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð·Ð¾Ð½Ñ‹. output::PictureZoneEditor::VertexDragHandler Merge these two vertices. Объединить Ñти две вершины. Move the vertex to one of its neighbors to merge them. Придвиньте вершину к одной из ÑоÑедних вершин, чтобы объединить их. 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 Удаление пÑтен page_layout::ApplyDialog Can't do: more than one group is selected. Ðевозможно: выбрано неÑколько групп Ñтраниц. page_layout::Filter Page Layout Макет Ñтраницы Natural order ЕÑтеÑтвенный порÑдок Order by increasing width Сортировка по возраÑтающей ширине Order by increasing height Сортировка по возраÑтающей выÑоте Margins ÐŸÐ¾Ð»Ñ page_layout::ImageView Resize margins by dragging any of the solid lines. МенÑйте размеры полей, перетаÑÐºÐ¸Ð²Ð°Ñ Ñ…Ð¾Ñ‚ÑŒ внешние, хоть внутренние Ñплошные линии. page_layout::OptionsWidget Apply Margins Применить Ð¿Ð¾Ð»Ñ Apply Alignment Применить выравнивание page_split::Filter Natural order ЕÑтеÑтвенный порÑдок Order by split type Сортировка по типу разреза Order by split line count Сортировка по чиÑлу резаков Split Pages Разрезка Ñтраниц page_split::ImageView Drag this handle to skew the line. ТÑните Ñту рукоÑтку чтобы наклонить линию. This line can be dragged. Это линию можно перетаÑкивать мышкой. Drag the line or the handles. ТÑните линию или рукоÑтки. page_split::OptionsWidget This page only Только Ð´Ð»Ñ Ñтой Ñтраницы All pages Ð”Ð»Ñ Ð²Ñех Ñтраниц Set manually УÑтановлено вручную Auto detected Определено автоматичеÑки page_split::UnremoveButton Restore removed page. Вернуть на меÑто удаленную Ñтраницу. select_content::ApplyDialog Can't do: more than one group is selected. Ðевозможно: выбрано неÑколько групп Ñтраниц. select_content::Filter Natural order ЕÑтеÑтвенный порÑдок Order by increasing width Сортировка по возраÑтающей ширине Order by increasing height Сортировка по возраÑтающей выÑоте Select Content ÐŸÐ¾Ð»ÐµÐ·Ð½Ð°Ñ Ð¾Ð±Ð»Ð°Ñть select_content::ImageView Create Content Box Создать полезную облаÑть Remove Content Box Убрать полезную облаÑть Use the context menu to enable / disable the content box. ИÑпользуйте контекÑтное меню Ð´Ð»Ñ Ð²ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ / Ð¾Ñ‚ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð»ÐµÐ·Ð½Ð¾Ð¹ облаÑти. Drag lines or corners to resize the content box. ПеретаÑкивайте линии или углы, чтобы изменить размеры полезной облаÑти. scantailor-RELEASE_0_9_12_2/translations/scantailor_sk.ts000066400000000000000000002722641271170121200234210ustar00rootroot00000000000000 AboutDialog About Scan Tailor O programe Scan Tailor About O programe 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 recogrnition is out of scope of this project. Scan Tailor je interaktívny nástroj na úpravu zoskenovaných strán. Program vie rozdeÅ¥ovaÅ¥ dvojstrany, opraviÅ¥ natoÄenie, nastavenie okrajov a viac. NaÄítate nespracované skeny a výsledok bude pripravený pre vytvorenie PDF alebo DJVU súborov. Skenovanie a OCR niesú cieľom tohoto projektu. Authors Autori Lead Developer Hlavný vývojár U235 - Picture auto-detection algorithm. Robert B. - First generation dewarping algorithm. Andrey Bergman - System load adjustment. Petr Kovář - Command line interface. Translators Prekladatelia Neco Torquato - Brazilian Portuguese Svetoslav Sashkov, Mandor - Bulgarian Petr Kovář - Czech Stefan Birkner - German Angelo Gemmi - Italian Masahiro Kitagawa - Japanese Patrick Pascal - French Daniel Koć - Polish Joseph Artsimovich - Russian Marián Hvolka - Slovak Flavio Benelli - Spanish Davidson Wang - Traditional Chinese Contributors Prispeli Yuri Chornoivan - Ukrainian Documentation Dokumentácia denver 22 License Licencia 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 je interaktívny nástroj na úpravu zoskenovaných strán. Program vie rozdeÅ¥ovaÅ¥ dvojstrany, opraviÅ¥ natoÄenie, nastavenie okrajov a viac. NaÄítate nespracované skeny a výsledok bude pripravený pre vytvorenie PDF alebo DJVU súborov. Skenovanie a OCR niesú cieľom tohoto projektu. Joseph Artsimovich BatchProcessingLowerPanel Form Beep when finished UpozorniÅ¥ zvukom pri ukonÄení ColorPickupInteraction Click on an area to pick up its color, or ESC to cancel. Kliknite do oblasti pre získanie farby alebo stlaÄte klávesu ESC pre zruÅ¡enie. CrashReportDialog Crash Report Hlásenie o chybe Scan Tailor crashed. That's usually caused by errors in the software. You can help us find and fix the problem by submitting this crash report. Scan Tailor padol. To je väÄÅ¡inou zapríÄinené chybou v programe. Môžete nám pomôcÅ¥ nájsÅ¥ a opraviÅ¥ problém odeslaním tohoto hlásenia. Information to be sent Informácie na odeslanie If you have additional information, for example you know how to reproduce the crash, please provide it here. English or Russian please. Ak máte doplňujúce informácie (napríklad viete ako chybu zopakovaÅ¥) napíšte ich sem. Anglicky alebo rusky. If you are willing to provide further assistance to developers to help fix this problem, please provide your email address. Ak chcete, aby vás vývojári mohli v prípade potreby kontaktovaÅ¥, vyplňte prosím svoju e-mailovú adresu. Email [optional] Email [voliteľné] This file contains the internal state of the program when it crashed Tento súbor obsahuje stav programu pri páde Dump file Obsah súboru Sending ... Odosielanie ... Unexpected response (code %1) from dispatcher NeoÄakávaná chyba (code %1) na strane odosielateľa Unexpected response from dispatcher NeoÄakávaná chyba na strane príjemcu Unexpected response (code %1) from the receiving side NeoÄakávaná chyba (code %1) na strane príjemcu Successfully sent ÚspeÅ¡ne odoslané DeskewApplyDialog Apply to PoužiÅ¥ na This page only (already applied) Len pre túto stranu (už použité) All pages VÅ¡etky strany This page and the following ones Táto a nasledujúce strany Selected pages Vybrané strany Use Ctrl+Click / Shift+Click to select multiple pages. Pre výber viacerých strán použite Ctr+Klik / Shift+Klik. DeskewOptionsWidget Form Deskew NatoÄenie Auto Automaticky Manual RuÄne Apply To ... PoužiÅ¥ na ... DragHandler Unrestricted dragging is possible by holding down the Shift key. Neobmedzené posúvanie strany je možné, ak pridržíte klávesu Shift. ErrorWidget Form FixDpiDialog Fix DPI OpraviÅ¥ DPI Tab 1 Tab 2 DPI Custom Vlastné x Apply PoužiÅ¥ Need Fixing Nutná oprava All Pages VÅ¡etky strany DPI is too large and most likely wrong. DPI je príliÅ¡ vysoké a pravdepodobne nesprávne. DPI is too small. Even if it's correct, you are not going to get acceptable results with it. DPI je príliÅ¡ nízke. Môže byÅ¥ správne, dostanete vÅ¡ak neakceptovateľný výsledok. DPI is too small for this pixel size. Such combination would probably lead to out of memory errors. DPI je príliÅ¡ nízke vzhľadom k poÄtu pixelov a pravdepodobne by to viedlo k nedostatku pamäti. %1 (page %2) %1 (strana %2) FixDpiSinglePageDialog Fix DPI Oprava DPI DPI for %1 DPI pre %1 Custom Vlastné %1 (page %2) %1 (strana %2) DPI is too large and most likely wrong. DPI je príliÅ¡ vysoké a pravdepodobne nesprávne. DPI is too small. Even if it's correct, you are not going to get acceptable results with it. DPI je príliÅ¡ nízke. Môže byÅ¥ správne, dostanete vÅ¡ak neakceptovateľný výsledok. DPI is too small for this pixel size. Such combination would probably lead to out of memory errors. DPI je príliÅ¡ nízke vzhľadom k poÄtu pixelov a pravdepodobne by to viedlo k nedostatku pamäti. ImageViewBase Use the mouse wheel or +/- to zoom. When zoomed, dragging is possible. Pre priblíženie použite kolieÄko myÅ¡i alebo klávesu + resp. -. Aj po zväÄÅ¡ení je možné stranu Å¥ahaním posúvaÅ¥. InteractiveXSpline Click to create a new control point. Nový kontrolný bod vytvoríte kliknutím. This point can be dragged. S týmto bodom môžete hýbaÅ¥. Drag this point or delete it by pressing Del or D. Posuňte tento bod alebo ho zmažte klávesou Del nebo D. LoadFileTask The following file could not be loaded: %1 Tento súbor nemohl byÅ¥ naÄítaný: %1 The following file doesn't exist:<br>%1<br><br>Use the <a href="#relink">Relinking Tool</a> to locate it. LoadFilesStatusDialog Some files failed to load Loaded successfully: %1 Failed to load: %1 MainWindow MainWindow Keep current page in view. PonechaÅ¥ zobrazenú aktuálnu stranu. Use Home, End, PgUp (or Q), PgDown (or W) to navigate between pages. K presunu medzi stranami slúžia klávesy Home, End PgUp (alebo Q), PgDown (alebo W). Tools Nástroje File Súbor Help Pomocník Debug Ladenie Debug Mode Save Project UložiÅ¥ projekt Ctrl+S Save Project As ... UložiÅ¥ projekt ako ... Next Page ÄŽalÅ¡ia strana PgDown Previous Page PredoÅ¡lá strana PgUp New Project ... Nový projekt ... Ctrl+N Open Project ... OtvoriÅ¥ projekt ... Ctrl+O Q W Close Project ZatvoriÅ¥ projekt Ctrl+W Quit Koniec Ctrl+Q Settings ... Nastavenie... First Page Prvá strana Home Last Page Posledná strana End About O programe Fix DPI ... Relinking ... Stop batch processing ZastaviÅ¥ dávkové spracovanie Save this project? UložiÅ¥ projekt? Insert before ... VložiÅ¥ pred ... Insert after ... VložiÅ¥ za ... Remove from project ... OdstrániÅ¥ z projektu ... Insert here ... VložiÅ¥ sem ... Scan Tailor Projects Zoznam projektov Open Project OtvoriÅ¥ projekt Error Chyba Unable to open the project file. Nemožno otvoriÅ¥ súbor s projektom. The project file is broken. Súbor s projektom je poÅ¡kodený. 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". Výstup zatiaľ nie je možný, lebo eÅ¡te nie je známa koneÄná veľkosÅ¥ strán. Na urÄenie veľkosti strán spustite dávkové spracovanie pre "Vybrat Obsah" alebo "Okraje". Unnamed Nepomenovaný %2 - Scan Tailor %3 [%1bit] Error saving the project file! Chyba pri ukladaní súboru s projektom! Files to insert Skip failed files File to insert Súbor na vloženie Images not in project (%1) Obrázky, ktoré nie sú v projekte (%1) Error opening the image file. Chyba pri otváraní súboru s obrázkom. Remove OdstrániÅ¥ NewOpenProjectPanel Form New Project ... Nový projekt ... Open Project ... OtvoriÅ¥ projekt ... Recent Projects Posledné otvorené projekty OrientationApplyDialog Fix Orientation OpraviÅ¥ orientáciu Apply to PoužiÅ¥ na This page only (already applied) Len na túto stranu (už použité) All pages VÅ¡etky strany This page and the following ones Táto a následujúce strany Every other page All odd or even pages, depending on the current page being odd or even. Každá druhá strana The current page will be included. Aktuálna strana bude zahrnutá. Selected pages Vybrané strany Use Ctrl+Click / Shift+Click to select multiple pages. Pre výber viacerých strán použite Ctr+Klik / Shift+Klik. Every other selected page Každá druhá vybraná strana OrientationOptionsWidget Form Rotate OtoÄiÅ¥ ... Reset ObnoviÅ¥ Scope Rozsah Apply to ... PoužiÅ¥ na ... 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 UložiÅ¥ projekt Save Project As ... UložiÅ¥ projekt ako ... 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 Zoznam projektov Error Chyba Error saving the project file! Chyba pri ukladaní súboru s projektom! OutputApplyColorsDialog Apply Mode Mód použitia Apply to PoužiÅ¥ na This page only (already applied) Len na túto stranu (už použité) All pages VÅ¡etky strany This page and the following ones Táto a nasledujúce strany Selected pages Vybrané strany Use Ctrl+Click / Shift+Click to select multiple pages. Pre výber viacerých strán použite Ctr+Klik / Shift+Klik. OutputChangeDewarpingDialog Apply Dewarping Mode PoužiÅ¥ mód deformácie Mode Mód Off Vypnuté Auto (experimental) Automaticky (experimentálne) Manual RuÄne Scope Rozsah This page only Len na túto stranu All pages VÅ¡etky strany This page and the following ones Táto a nasledujúce strany Selected pages Vybrané strany Use Ctrl+Click / Shift+Click to select multiple pages. Pre výber viacerých strán použite Ctr+Klik / Shift+Klik. OutputChangeDpiDialog Apply Output Resolution PoužiÅ¥ výstupné rozlíšenie DPI Scope Rozsah This page only Len na tuto stranu All pages VÅ¡etky strany This page and the following ones Táto a nasledujúce strany Selected pages Vybrané strany Use Ctrl+Click / Shift+Click to select multiple pages. Pre výber viacerých strán použite Ctr+Klik / Shift+Klik. OutputOptionsWidget Form Output Resolution (DPI) Výstupné rozlíšenie (DPI) Change ... ZmeniÅ¥ ... Mode Mód White margins Biele okraje Equalize illumination KompenzovaÅ¥ osvetlenie Thinner TenÅ¡ie Thicker HrubÅ¡ie Apply To ... PoužiÅ¥ na ... Dewarping Deformácie Depth perception Despeckling ÄŒistenie No despeckling Bez Äistenia Cautious despeckling Opatrné Äistenie ... Normal despeckling Normálne Äistenie Aggressive despeckling Agresívne Äistenie PageLayoutApplyDialog Apply to PoužiÅ¥ na This page only (already applied) Len na túto stranu (už použité) All pages VÅ¡etky strany This page and the following ones Táto a nasledujúce strany Every other page All odd or even pages, depending on the current page being odd or even. Každá druhá strana The current page will be included. Aktuálna strana bude zahrnutá. Selected pages Vybrané strany Use Ctrl+Click / Shift+Click to select multiple pages. Pre výber viacerých strán použite Ctr+Klik / Shift+Klik. Every other selected page Každá druhá vybraná strana PageLayoutOptionsWidget Form Margins Okraje Millimeters (mm) Milimetre (mm) Inches (in) Palce (in) Top Hore ... Bottom Dolu Left Vľavo Right Vpravo Apply To ... PoužiÅ¥ na ... Alignment Zarovnanie Match size with other pages Rovnaká veľkosÅ¥ ako ostatné strany PageSplitModeDialog Split Pages RozdeliÅ¥ strany Mode Mód Auto Automaticky Manual RuÄne Scope Rozsah This page only Len na túto stranu All pages VÅ¡etky strany This page and the following ones Táto a nasledujúce strany Selected pages Vybrané strany Use Ctrl+Click / Shift+Click to select multiple pages. Pre výber viacerých strán použite Ctr+Klik / Shift+Klik. PageSplitOptionsWidget Form Page Layout Rozvrhnutie strany ? Change ... ZmeniÅ¥ ... Split Line Deliaca Äiara Auto Automaticky Manual RuÄne PictureZonePropDialog Zone Properties Vlastnosti zóny Subtract from all layers OdobraÅ¥ zo vÅ¡etkých vrstiev Add to auto layer PridaÅ¥ k automatickej vrstve Subtract from auto layer OdobraÅ¥ z automatickej vrstvy ProjectFilesDialog Project Files Súbory s projektami Input Directory Vstupný adresár Browse PrezeraÅ¥ Output Directory Výstupný adresár Files Not In Project Súbory, ktoré nie sú v projekte Select All Vybrat vÅ¡etko <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;">PridaÅ¥ vybrané súbory do projektu.</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;">OdstrániÅ¥ vybrané súbory z projektu.</p></body></html> << Files In Project Súbory v projekte Right to left layout (for Hebrew and Arabic) Sprava doľava (HebrejÄina, ArabÄina) Fix DPIs, even if they look OK OpraviÅ¥ DPI Error Chyba No files in project! Projekt nemá žiadne súbory! Input directory is not set or doesn't exist. Vstupný adresár nie je nastavený alebo neexistuje. Input and output directories can't be the same. Vstupné a výstupné adresáre musia byÅ¥ rôzne. Create Directory? VytvoriÅ¥ adresár? Output directory doesn't exist. Create it? Výstupný adresár neexistuje. VytvoriÅ¥? Unable to create output directory. Nedá sa vytvoriÅ¥ výstupný adresár. Output directory is not set or doesn't exist. Výstupný adresár nie je nastavený alebo neexistuje. 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. Niektoré súbory sa nenaÄítali. BuÄ nepodporujeme ich formát, alebo sú poÅ¡kodené. VyraÄte ich z projektu. ProjectOpeningContext Error Chyba Unable to interpret the project file. Chyba pri Äítaní súboru s projektom. RelinkingDialog Relinking Undo ... Substitution File for %1 Substitution Directory for %1 This change would merge several files into one. RemovePagesDialog Remove Pages VyradiÅ¥ strany Remove %1 page(s) from project? VyradiÅ¥ %1 strán z projektu? Corresponding output files will be deleted, while input files will remain. PrísluÅ¡né výstupné súbory budú zmazané, vstupné zostanú. SelectContentApplyDialog Select Content VybraÅ¥ obsah Apply to PoužiÅ¥ na This page only (already applied) Len na túto stranu (už použité) All pages VÅ¡etky strany This page and the following ones Táto a nasledujúce strany Every other page All odd or even pages, depending on the current page being odd or even. Každá druhá strana The current page will be included. Aktuálna strana bude zahrnutá. Selected pages Vybrané strany Use Ctrl+Click / Shift+Click to select multiple pages. Pre výber viacerých strán použite Ctr+Klik / Shift+Klik. Every other selected page Každá druhá vybraná strana SelectContentOptionsWidget Form Content Box Obsah Auto Automaticky Manual RuÄne Scope Rozsah Apply to ... PoužiÅ¥ na ... SettingsDialog Settings Nastavenie Use 3D acceleration for user interface PoužívaÅ¥ 3D akceleráciu pre použivateľské rozhranie Compiled without OpenGL support. Skompilované bez podpory OpenGL. Your hardware / driver don't provide the necessary features. Váš hardvér nemá potrebné vlastnosti. StageListView Launch batch processing SpustiÅ¥ dávkové spracovanie SystemLoadWidget Form System load Systémová záťaž ... Minimal Minimálna Normal Normálna ThumbnailSequence %1 (page %2) %1 (strana %2) ZoneContextMenuInteraction Delete confirmation Potvrdenie zmazania Really delete this zone? Naozaj chcete zmazaÅ¥ túto zónu? Delete ZmazaÅ¥ Properties Vlastnosti ZoneCreationInteraction Click to finish this zone. ESC to cancel. Kliknite pre uzatvorenie zóny. ESC pre zruÅ¡enie. Connect first and last points to finish this zone. ESC to cancel. SpojiÅ¥ prvý a posledný bod pre uzatvorenie zóny. ESC pre zruÅ¡enie. Zones need to have at least 3 points. ESC to cancel. Zóna musí maÅ¥ aspoň 3 body. ESC pre zruÅ¡enie. ZoneDefaultInteraction Drag the vertex. Posunúť vrchol. Click to create a new vertex here. Kliknutím vytvoríte nový vrchol. Right click to edit zone properties. Kliknite pravým tlaÄítkom pre editáciu vlastností zóny. Click to start creating a new picture zone. Klikom zaÄnite vytváraÅ¥ novú zónu obrázka. ZoneVertexDragInteraction Merge these two vertices. ZlúÄiÅ¥ vrcholy. Move the vertex to one of its neighbors to merge them. ZlúÄiÅ¥ vrchol s jedným z jeho susedov. deskew::Filter Deskew NatoÄenie deskew::ImageView Use Ctrl+Wheel to rotate or Ctrl+Shift+Wheel for finer rotation. Použite Ctrl+KolieÄko pre otáÄanie alebo Ctr+Shift+KolieÄko pre jemné otáÄanie. Drag this handle to rotate the image. Ťahajte pre natoÄenie obrázku. deskew::OptionsWidget Apply Deskew PoužiÅ¥ natoÄenie fix_orientation::ApplyDialog Can't do: more than one group is selected. Nemožno vykonaÅ¥: je vybraná viac než jedna skupina. fix_orientation::Filter Fix Orientation OpraviÅ¥ orientáciu output::ChangeDpiDialog Custom Vlastné Error Chyba DPI is not set. DPI nie je nastavené. DPI is too low! DPI je príliÅ¡ nízke! DPI is too high! DPI je príliÅ¡ vysoké! output::FillZoneEditor Pick color Vyberte farbu output::Filter Output Výstup output::OptionsWidget Black and White ÄŒierna a biela Color / Grayscale Farebné / Stupne Å¡edej Mixed ZmieÅ¡ané Apply Despeckling Level PoužiÅ¥ úroveň Äistenia Apply Depth Perception Off Vypnuté Auto Automaticky Manual RuÄne output::Task::UiUpdater Picture zones are only available in Mixed mode. Zóny obrázkov sú možné len v zmieÅ¡anom móde. Despeckling can't be done in Color / Grayscale mode. ÄŒistenie nemožno použiÅ¥ vo farebnom / Å¡edom móde. Output Výstup Picture Zones Zóny obrázkov Fill Zones Prázdne zóny Dewarping Deformácie Despeckling ÄŒistenie page_layout::ApplyDialog Can't do: more than one group is selected. Nemožno vykonaÅ¥: je vybraná viac než jedna skupina. page_layout::Filter Natural order Postupné zoradenie Order by increasing width ZoradiÅ¥ podľa rastúcej šírky Order by increasing height ZoradiÅ¥ podľa rastúcej výšky Margins Okraje page_layout::ImageView Resize margins by dragging any of the solid lines. Zmeňte okraje Å¥ahaním za ľubovoľný okraj. page_layout::OptionsWidget Apply Margins PoužiÅ¥ okraje Apply Alignment PoužiÅ¥ zarovnanie page_split::Filter Natural order Postupné zoradenie Order by split type Split Pages OrezaÅ¥ strany page_split::ImageView Drag the line or the handles. Ťahajte za okraj alebo za rukoväť. page_split::OptionsWidget Set manually NastaviÅ¥ ruÄne Auto detected Autodetekcia page_split::UnremoveButton Restore removed page. ObnoviÅ¥ odstránenú stranu. select_content::ApplyDialog Can't do: more than one group is selected. Nemožno vykonaÅ¥: je vybraná viac než jedna skupina. select_content::Filter Natural order Postupné zoradenie Order by increasing width ZoradiÅ¥ podľa rastúcej šírky Order by increasing height ZoradiÅ¥ podľa rastúcej výšky Select Content VybraÅ¥ obsah select_content::ImageView Use the context menu to enable / disable the content box. Použite kontextové menu pre zapnutie alebo vypnutie obsahu. Drag lines or corners to resize the content box. Výber obsahu zmeníte Å¥ahaním za okraje alebo rohy. Create Content Box VytvoriÅ¥ výber obsahu Remove Content Box OdstrániÅ¥ výber obsahu scantailor-RELEASE_0_9_12_2/translations/scantailor_uk.ts000066400000000000000000003152401271170121200234130ustar00rootroot00000000000000 AboutDialog About Scan Tailor Про Scan Tailor 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 ЙоÑиф Ðрцимович U235 - Picture auto-detection algorithm. U235 — алгоритм автоматичного виÑÐ²Ð»ÐµÐ½Ð½Ñ Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½ÑŒ. Robert B. - First generation dewarping algorithm. Robert B. — початковий алгоритм Ð²Ð¸Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ Ñ€Ñдків. Andrey Bergman - System load adjustment. Ðндрєй Бергман — Ñ€ÐµÐ³ÑƒÐ»ÑŽÐ²Ð°Ð½Ð½Ñ Ð½Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ð½Ð° ÑиÑтему. Petr Kovář - Command line interface. Petr Kovář — Ñ–Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð½Ð¾Ð³Ð¾ Ñ€Ñдка. Neco Torquato - Brazilian Portuguese Svetoslav Sashkov, Mandor - Bulgarian Svetoslav Sashkov, Mandor — болгарÑька Petr Kovář - Czech Petr Kovář — чеÑька Stefan Birkner - German Stefan Birkner — німецька Angelo Gemmi - Italian Angelo Gemmi —італійÑька Masahiro Kitagawa - Japanese Masahiro Kitagawa — ÑпонÑька Patrick Pascal - French Patrick Pascal — французька Daniel Koć - Polish Daniel Koć — польÑька Joseph Artsimovich - Russian ЙоÑиф Ðрцимович — роÑійÑька Marián Hvolka - Slovak Marián Hvolka — Ñловацька Flavio Benelli - Spanish Flavio Benelli — Ñ–ÑпанÑька Davidson Wang - Traditional Chinese Davidson Wang — традиційна китайÑька Yuri Chornoivan - Ukrainian Юрій Чорноіван — українÑька denver 22 denver 22 Translators Перекладачі Contributors УчаÑники розробки Documentation Ð”Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ñ–Ñ License Ð›Ñ–Ñ†ÐµÐ½Ð·ÑƒÐ²Ð°Ð½Ð½Ñ BatchProcessingLowerPanel Form Форма Beep when finished Гудок піÑÐ»Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ ColorPickupInteraction Click on an area to pick up its color, or ESC to cancel. Клацніть на ділÑнці, щоб взÑти колір з неї, або натиÑніть Esc, щоб ÑкаÑувати дію. DeskewApplyDialog 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+клацаннÑм. DeskewOptionsWidget Form Форма Deskew Ð’Ð¸Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ Ð½Ð°Ñ…Ð¸Ð»Ñƒ Auto Ðвтоматично Manual Вручну Apply To ... ЗаÑтоÑувати до… DragHandler Unrestricted dragging is possible by holding down the Shift key. Ð£Ñ‚Ñ€Ð¸Ð¼ÑƒÐ²Ð°Ð½Ð½Ñ Ð½Ð°Ñ‚Ð¸Ñнутою клавіші Shift надає змогу перетÑгувати без обмежень. ErrorWidget Form Форма ImageLabel Мітка-Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ TextLabel Мітка-текÑÑ‚ FixDpiDialog 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) %1 (Ñторінка %2) Fix DPI Виправити щільніÑть Tab 1 Вкладка 1 Tab 2 Вкладка 2 DPI ЩільніÑть Custom Ðетипова x x Apply ЗаÑтоÑувати FixDpiSinglePageDialog Fix DPI Ð’Ð¸Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ Ñ‰Ñ–Ð»ÑŒÐ½Ð¾Ñті DPI for %1 ЩільніÑть %1 Custom Ðетипова x x %1 (page %2) %1 (Ñторінка %2) 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. Ð—Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ñ‰Ñ–Ð»ÑŒÐ½Ð¾Ñті Ñ” надто малим Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ розміру точок. Таке поєднаннÑ, ймовірно, призведе до Ð²Ð¸Ñ‡ÐµÑ€Ð¿Ð°Ð½Ð½Ñ Ð¾Ð±â€™Ñ”Ð¼Ñƒ пам’Ñті. ImageViewBase Use the mouse wheel or +/- to zoom. When zoomed, dragging is possible. Ð”Ð»Ñ Ð·Ð¼Ñ–Ð½Ð¸ маÑштабу ÑкориÑтайтеÑÑ ÐºÐ¾Ð»Ñ–Ñ‰Ð°Ñ‚ÐºÐ¾Ð¼ миші або кнопками +/-. У режимі збільшеного маÑштабу можливе перетÑÐ³ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿ÐµÑ€ÐµÐ³Ð»Ñду. InteractiveXSpline Click to create a new control point. ÐатиÑніть, щоб Ñтворити контрольну точку. This point can be dragged. Цю точку можна перетÑгувати. Drag this point or delete it by pressing Del or D. ПеретÑгніть цю точку або вилучіть Ñ—Ñ— за допомогою натиÑÐºÐ°Ð½Ð½Ñ ÐºÐ»Ð°Ð²Ñ–Ñˆ Del або D. 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. LoadFilesStatusDialog Some files failed to load Ðе вдалоÑÑ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶Ð¸Ñ‚Ð¸ деÑкі з файлів Loaded successfully: %1 УÑпішно завантажено: %1 Failed to load: %1 Ðе вдалоÑÑ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶Ð¸Ñ‚Ð¸: %1 MainWindow Save Project Зберегти проект Save this project? Зберегти цей проект? 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. Файл проекту пошкоджено. 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 - Scan Tailor %3 [%1bit] Files to insert Skip failed files %1 - Scan Tailor %2 %1 — Scan Tailor %2 Error saving the project file! Помилка під Ñ‡Ð°Ñ Ð·Ð±ÐµÑ€ÐµÐ¶ÐµÐ½Ð½Ñ Ñ„Ð°Ð¹Ð»Ð° проекту! File to insert Файл, Ñкий Ñлід вÑтавити Images not in project (%1) Ð—Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð¿Ð¾Ð·Ð° проектом (%1) Error opening the image file. Помилка під Ñ‡Ð°Ñ Ñпроби відкрити файл зображеннÑ. Remove Вилучити MainWindow Головне вікно Keep current page in view. Утримувати переглÑд поточної Ñторінки. Use Home, End, PgUp (or Q), PgDown (or W) to navigate between pages. СкориÑтайтеÑÑ ÐºÐ»Ð°Ð²Ñ–ÑˆÐ°Ð¼Ð¸ Home, End, PgUp (або Q), PgDown (або W) Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÑ…Ð¾Ð´Ñƒ між Ñторінками. Tools ІнÑтрументи File Файл Help Довідка Debug ДіагноÑтика Debug Mode Режим діагноÑтики Ctrl+S Ctrl+S Save Project As ... Зберегти проект Ñк… Next Page ÐаÑтупна Ñторінка PgDown PgDown Previous Page ÐŸÐ¾Ð¿ÐµÑ€ÐµÐ´Ð½Ñ Ñторінка PgUp PgUp New Project ... Створити проект… Ctrl+N Ctrl+N Open Project ... Відкрити проект… Ctrl+O Ctrl+O Q Q W W Close Project Закрити проект Ctrl+W Ctrl+W Quit Вийти Ctrl+Q Ctrl+Q First Page Перша Ñторінка Home Home Last Page ОÑÑ‚Ð°Ð½Ð½Ñ Ñторінка End End About Про програму Fix DPI ... Виправити щільніÑть… Relinking ... Повторне з’єднаннÑ… Stop batch processing Припинити пакетну обробку Settings ... Параметри… NewOpenProjectPanel Form Форма New Project ... Створити проект… Open Project ... Відкрити проект… Recent Projects ОÑтанні проекти OrientationApplyDialog Fix Orientation Ð’Ð¸Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ñ€Ñ–Ñ”Ð½Ñ‚Ð°Ñ†Ñ–Ñ— Apply to ЗаÑтоÑувати до This page only (already applied) Лише цієї Ñторінки (вже заÑтоÑовано) All pages Ð’ÑÑ–Ñ… Ñторінок This page and the following ones Цієї Ñторінки Ñ– наÑтупних Ñторінок Every other page All odd or even pages, depending on the current page being odd or even. Ð’ÑÑ–Ñ… інших Ñторінок The current page will be included. Обробку буде заÑтоÑовано Ñ– до поточної Ñторінки. Selected pages Позначених Ñторінок Use Ctrl+Click / Shift+Click to select multiple pages. Декілька Ñторінок можна позначити Ctrl+клацаннÑм / Shift+клацаннÑм. Every other selected page Ð’ÑÑ–Ñ… інших позначених Ñторінок OrientationOptionsWidget Form Форма Rotate Поворот ... … Reset Відновити Scope Діапазон Apply to ... ЗаÑтоÑувати до… OutOfMemoryDialog Out of memory Ðе виÑтачає пам’Ñті Out of Memory Situation in Scan Tailor 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. Іноді дані щодо роздільноÑті, вбудовані до зображень, Ñ” помилковими. Scan Tailor намагаєтьÑÑ Ð²Ð¸Ñвити файли з помилковими даними, але Ð²Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð½Ðµ завжди Ñпрацьовує. Ймовірно, вам варто позначити пункт «ВиправлÑти щільніÑть, навіть Ñкщо програма вважає Ñ—Ñ— правильною» під Ñ‡Ð°Ñ ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ñƒ, а потім ÑкориÑтатиÑÑ Ð²ÐºÐ»Ð°Ð´ÐºÐ¾ÑŽ «ВÑÑ– Ñторінки» діалогового вікна «Виправити щільніÑть», доÑтуп до Ñкого можна також отримати за допомогою меню «ІнÑтрументи». Is your output DPI set too high? Usually you don't need it higher than 600. Можливо, вÑтановлено велике Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð¾Ñтаточної щільноÑті? Зазвичай, це Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð¼Ð°Ñ” не перевищувати 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. Виправте Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ñ‰Ñ–Ð»ÑŒÐ½Ð¾Ñтей. ÐаÑтанови щодо Ð²Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð½Ð°Ð»ÐµÐ¶Ð½Ð¸Ñ… щільноÑтей можна знайти <a href="http://vimeo.com/12524529">тут</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! Помилка під Ñ‡Ð°Ñ Ð·Ð±ÐµÑ€ÐµÐ¶ÐµÐ½Ð½Ñ Ñ„Ð°Ð¹Ð»Ð° проекту! 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+клацаннÑм. OutputChangeDewarpingDialog Apply Dewarping Mode ЗаÑтоÑÑƒÐ²Ð°Ð½Ð½Ñ Ñ€ÐµÐ¶Ð¸Ð¼Ñƒ Ð²Ð¸Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ Ñ€Ñдків Mode Режим Off Вимкнути Auto (experimental) Ðвтоматичний (теÑтовий) Manual Вручну Scope Діапазон This page only Лише Ñ†Ñ Ñторінка All pages Ð’ÑÑ– Ñторінки This page and the following ones Ð¦Ñ Ñторінка Ñ– наÑтупні Ñторінки Selected pages Позначені Ñторінки Use Ctrl+Click / Shift+Click to select multiple pages. Декілька Ñторінок можна позначити Ctrl+клацаннÑм / Shift+клацаннÑм. OutputChangeDpiDialog Apply Output Resolution Ð’Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ñ€Ð¾Ð·Ð´Ñ–Ð»ÑŒÐ½Ð¾Ñті результату DPI ЩільніÑть Scope Діапазон This page only Лише Ñ†Ñ Ñторінка This page and the following ones Ð¦Ñ Ñторінка Ñ– наÑтупні Ñторінки Selected pages Позначені Ñторінки Use Ctrl+Click / Shift+Click to select multiple pages. Декілька Ñторінок можна позначити Ctrl+клацаннÑм / Shift+клацаннÑм. All pages Ð’ÑÑ– Ñторінки OutputOptionsWidget Form Форма Output Resolution (DPI) РоздільніÑть результату (Ñ‚/д) Change ... Змінити… Mode Режим White margins Білі Ð¿Ð¾Ð»Ñ Equalize illumination ВирівнÑти оÑвітленіÑть Dewarping Ð’Ð¸Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ Ñ€Ñдків Depth perception СприйнÑÑ‚Ñ‚Ñ Ð³Ð»Ð¸Ð±Ð¸Ð½Ð¸ No despeckling Без Ð²Ð¸Ð»ÑƒÑ‡ÐµÐ½Ð½Ñ Ð¿Ð»Ñм Apply To ... ЗаÑтоÑувати до… Despeckling Ð’Ð¸Ð»ÑƒÑ‡ÐµÐ½Ð½Ñ Ð¿Ð»Ñм Cautious despeckling Обережне Ð²Ð¸Ð»ÑƒÑ‡ÐµÐ½Ð½Ñ ... … Normal despeckling Звичайне Ð²Ð¸Ð»ÑƒÑ‡ÐµÐ½Ð½Ñ Aggressive despeckling ПоÑилене Ð²Ð¸Ð»ÑƒÑ‡ÐµÐ½Ð½Ñ Thinner Тонше Thicker ТовÑтіше PageLayoutApplyDialog Apply to ЗаÑтоÑувати до This page only (already applied) Цієї Ñторінки (вже заÑтоÑовано) All pages Ð’ÑÑ–Ñ… Ñторінок This page and the following ones Цієї Ñторінки Ñ– наÑтупних Ñторінок Every other page All odd or even pages, depending on the current page being odd or even. Ð’ÑÑ–Ñ… інших Ñторінок The current page will be included. Обробку буде заÑтоÑовано Ñ– до поточної Ñторінки. Selected pages Позначених Ñторінок Use Ctrl+Click / Shift+Click to select multiple pages. Декілька Ñторінок можна позначити Ctrl+клацаннÑм / Shift+клацаннÑм. Every other selected page Ð’ÑÑ–Ñ… інших позначених Ñторінок PageLayoutOptionsWidget Form Форма Margins ÐŸÐ¾Ð»Ñ Millimeters (mm) У міліметрах (мм) Inches (in) У дюймах (дюйм) Top Верхнє ... … Bottom Ðижнє Left Ліве Right Праве Apply To ... ЗаÑтоÑувати до… Alignment Ð’Ð¸Ñ€Ñ–Ð²Ð½ÑŽÐ²Ð°Ð½Ð½Ñ Match size with other pages Узгоджувати розміри з іншими Ñторінками PageSplitModeDialog Split Pages Поділ на Ñторінки Mode Режим Auto Ðвтоматичний Manual Вручну Scope Діапазон This page only Лише Ñ†Ñ Ñторінка This page and the following ones Ð¦Ñ Ñторінка Ñ– наÑтупні Ñторінки Selected pages Позначені Ñторінки Use Ctrl+Click / Shift+Click to select multiple pages. Декілька Ñторінок можна позначити Ctrl+клацаннÑм / Shift+клацаннÑм. All pages Ð’ÑÑ– Ñторінки PageSplitOptionsWidget Form Форма Page Layout ÐšÐ¾Ð¼Ð¿Ð¾Ð½ÑƒÐ²Ð°Ð½Ð½Ñ Ñторінки ? ? Change ... Змінити… Split Line Ð›Ñ–Ð½Ñ–Ñ Ð¿Ð¾Ð´Ñ–Ð»Ñƒ Auto Ðвтоматично Manual Вручну PictureZonePropDialog Zone Properties ВлаÑтивоÑті ділÑнки Subtract from all layers Вилучити з уÑÑ–Ñ… Ñторінок Add to auto layer Додати до автоматичного шару Subtract from auto layer Вилучити з автоматичного шару ProjectFilesDialog Input Directory Каталог початкових файлів Output Directory Каталог результатів 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. ЧаÑтину файлів не вдалоÑÑ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶Ð¸Ñ‚Ð¸. Підтримки цього формату не передбачено або дані пошкоджено. Вам Ñлід вилучити ці файли з проекту. Project Files Файли проекту Browse Вказати 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;"> <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;">Додати позначені файли до проекту.</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;">Вилучити позначені файли з проекту.</p></body></html> << ↠Files In Project Файли у проекті Right to left layout (for Hebrew and Arabic) ÐšÐ¾Ð¼Ð¿Ð¾Ð½ÑƒÐ²Ð°Ð½Ð½Ñ Ñправа ліворуч (Ð´Ð»Ñ Ñ–Ð²Ñ€Ð¸Ñ‚Ñƒ та арабÑької) Fix DPIs, even if they look OK ВиправлÑти щільніÑть, навіть Ñкщо програма вважає Ñ—Ñ— правильною ProjectOpeningContext Error Помилка Unable to interpret the project file. Ðе вдалоÑÑ Ð¾Ð±Ñ€Ð¾Ð±Ð¸Ñ‚Ð¸ файл проекту. RelinkingDialog Relinking Повторне Ð·â€™Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Undo СкаÑувати ... … Substitution File for %1 Substitution Directory for %1 This change would merge several files into one. RemovePagesDialog Remove Pages Ð’Ð¸Ð»ÑƒÑ‡ÐµÐ½Ð½Ñ Ñторінок Remove %1 page(s) from project? Вилучити з проекту %1 Ñторінок? Corresponding output files will be deleted, while input files will remain. Відповідні файли результатів буде вилучено, початкові файли не вилучатимутьÑÑ. SelectContentApplyDialog Select Content Вибір вміÑту Apply to ЗаÑтоÑувати до This page only (already applied) Цієї Ñторінки (вже заÑтоÑовано) All pages Ð’ÑÑ–Ñ… Ñторінок This page and the following ones Цієї Ñторінки Ñ– наÑтупних Ñторінок Every other page All odd or even pages, depending on the current page being odd or even. Ð’ÑÑ–Ñ… інших Ñторінок The current page will be included. Обробку буде заÑтоÑовано Ñ– до поточної Ñторінки. Selected pages Позначених Ñторінок Use Ctrl+Click / Shift+Click to select multiple pages. Декілька Ñторінок можна позначити Ctrl+клацаннÑм / Shift+клацаннÑм. Every other selected page Ð’ÑÑ–Ñ… інших позначених Ñторінок SelectContentOptionsWidget Form Форма Content Box Поле вміÑту Auto Ðвтоматично Manual Вручну Scope Діапазон Apply to ... ЗаÑтоÑувати до… SettingsDialog Compiled without OpenGL support. Зібрано без підтримки OpenGL. Your hardware / driver don't provide the necessary features. Ðа вашому обладнанні або у драйвері до нього не передбачено відповідних можливоÑтей. Settings Параметри Use 3D acceleration for user interface ВикориÑтовувати приÑÐºÐ¾Ñ€ÐµÐ½Ð½Ñ Ð¿Ð¾ÐºÐ°Ð·Ñƒ проÑторових об’єктів у інтерфейÑÑ– StageListView Launch batch processing Почати пакетну обробку SystemLoadWidget Form Форма System load ÐÐ°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ð½Ð° ÑиÑтему ... … Minimal Мінімальне Normal Звичайне ThumbnailSequence %1 (page %2) %1 (Ñторінка %2) ZoneContextMenuInteraction Properties ВлаÑтивоÑті Delete Вилучити Delete confirmation ÐŸÑ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð²Ð¸Ð»ÑƒÑ‡ÐµÐ½Ð½Ñ Really delete this zone? Вилучити цю ділÑнку? ZoneCreationInteraction Click to finish this zone. ESC to cancel. Клацніть, щоб завершити цю ділÑнку. ÐатиÑніть Esc, щоб ÑкаÑувати вилученнÑ. Connect first and last points to finish this zone. ESC to cancel. Ð”Ð»Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ Ñ†Ñ–Ñ”Ñ— ділÑнки з’єднайте першу Ñ– оÑтанню точки. ÐатиÑніть Esc, щоб ÑкаÑувати дію. Zones need to have at least 3 points. ESC to cancel. ДілÑнки мають ÑкладатиÑÑ Ð¿Ñ€Ð¸Ð½Ð°Ð¹Ð¼Ð½Ñ– з 3 точок. ÐатиÑніть Esc, щоб ÑкаÑувати дію. ZoneDefaultInteraction Drag the vertex. ПеретÑгніть вершину. Click to create a new vertex here. Клацніть, щоб Ñтворити вершину тут. Right click to edit zone properties. Клацніть правою кнопкою, щоб змінити влаÑтивоÑті ділÑнки. Click to start creating a new picture zone. Клацніть, щоб почати ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð´Ñ–Ð»Ñнки зображеннÑ. ZoneVertexDragInteraction Merge these two vertices. Об’єднати ці дві вершини. Move the vertex to one of its neighbors to merge them. ПереÑуньте вершину до однієї з ÑуÑідніх вершин, щоб об’єднати ці вершини. deskew::Filter Deskew Ð’Ð¸Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ Ð½Ð°Ñ…Ð¸Ð»Ñƒ deskew::ImageView Use Ctrl+Wheel to rotate or Ctrl+Shift+Wheel for finer rotation. СкориÑтайтеÑÑ Ctrl+коліщатком миші Ð´Ð»Ñ Ð¾Ð±ÐµÑ€Ñ‚Ð°Ð½Ð½Ñ Ð°Ð±Ð¾ Ctrl+Shift+коліщатком миші Ð´Ð»Ñ Ñ‚Ð¾Ñ‡Ð½Ñ–ÑˆÐ¾Ð³Ð¾ обертаннÑ. Drag this handle to rotate the image. ПеретÑгніть цей кружок, щоб повернути зображеннÑ. deskew::OptionsWidget Apply Deskew ЗаÑтоÑувати Ð²Ð¸Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ Ð½Ð°Ñ…Ð¸Ð»Ñƒ fix_orientation::ApplyDialog Can't do: more than one group is selected. Помилка: позначено більше однієї групи. fix_orientation::Filter Fix Orientation Ð’Ð¸Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ñ€Ñ–Ñ”Ð½Ñ‚Ð°Ñ†Ñ–Ñ— output::ChangeDpiDialog Custom Ðетипове Error Помилка DPI is not set. ЩільніÑть не вÑтановлено. DPI is too low! Занадто низька щільніÑть! DPI is too high! Занадто виÑока щільніÑть! output::FillZoneEditor Pick color Вибір кольору output::Filter Output Результат output::OptionsWidget Black and White Чорно-білий Color / Grayscale Кольоровий/Сірий Mixed Мішаний Apply Despeckling Level Рівень уÑÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð»Ñм Apply Depth Perception ЗаÑтоÑÑƒÐ²Ð°Ð½Ð½Ñ ÑприйнÑÑ‚Ñ‚Ñ Ð³Ð»Ð¸Ð±Ð¸Ð½Ð¸ Off Вимкнути Auto Ðвтоматично Manual Вручну 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 Ð’Ð¸Ð»ÑƒÑ‡ÐµÐ½Ð½Ñ Ð¿Ð»Ñм page_layout::ApplyDialog Can't do: more than one group is selected. Помилка: позначено більше однієї групи. page_layout::Filter Natural order Природний порÑдок Order by increasing width За зроÑтаннÑм ширини Order by increasing height За зроÑтаннÑм виÑоти Margins ÐŸÐ¾Ð»Ñ page_layout::ImageView Resize margins by dragging any of the solid lines. Змініть розмір полів перетÑгуваннÑм Ñуцільних ліній. page_layout::OptionsWidget Apply Margins ЗаÑтоÑÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð¾Ð»Ñ–Ð² Apply Alignment ЗаÑтоÑÑƒÐ²Ð°Ð½Ð½Ñ Ð²Ð¸Ñ€Ñ–Ð²Ð½ÑŽÐ²Ð°Ð½Ð½Ñ page_split::Filter Natural order Природний порÑдок Order by split type За типом поділу Split Pages Поділ на Ñторінки page_split::ImageView Drag the line or the handles. ПеретÑгніть лінію або контрольні точки. page_split::OptionsWidget Set manually Ð’Ñтановити вручну Auto detected Визначити автоматично page_split::UnremoveButton Restore removed page. Відновити вилучену Ñторінку. select_content::ApplyDialog Can't do: more than one group is selected. Помилка: позначено більше однієї групи. select_content::Filter Natural order Природний порÑдок Order by increasing width За зроÑтаннÑм ширини Order by increasing height За зроÑтаннÑм виÑоти Select Content Вибір вміÑту select_content::ImageView Use the context menu to enable / disable the content box. СкориÑтайтеÑÑ ÐºÐ¾Ð½Ñ‚ÐµÐºÑтним меню Ð´Ð»Ñ Ð²Ð¼Ð¸ÐºÐ°Ð½Ð½Ñ Ð°Ð±Ð¾ Ð²Ð¸Ð¼Ð¸ÐºÐ°Ð½Ð½Ñ Ð¿Ð¾ÐºÐ°Ð·Ñƒ Ð¿Ð¾Ð»Ñ Ð²Ð¼Ñ–Ñту. Drag lines or corners to resize the content box. ПеретÑгніть межі або кутові точки, щоб змінити розміри Ð¿Ð¾Ð»Ñ Ð²Ð¼Ñ–Ñту. Create Content Box Створити поле вміÑту Remove Content Box Вилучити поле вміÑту scantailor-RELEASE_0_9_12_2/translations/scantailor_untranslated.ts000066400000000000000000002507151271170121200255050ustar00rootroot00000000000000 AboutDialog About Scan Tailor 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 U235 - Picture auto-detection algorithm. Robert B. - First generation dewarping algorithm. Andrey Bergman - System load adjustment. Petr Kovář - Command line interface. Neco Torquato - Brazilian Portuguese Svetoslav Sashkov, Mandor - Bulgarian Petr Kovář - Czech Stefan Birkner - German Angelo Gemmi - Italian Masahiro Kitagawa - Japanese Patrick Pascal - French Daniel Koć - Polish Joseph Artsimovich - Russian Marián Hvolka - Slovak Flavio Benelli - Spanish Davidson Wang - Traditional Chinese Yuri Chornoivan - Ukrainian denver 22 Translators Contributors Documentation License BatchProcessingLowerPanel Form Beep when finished ColorPickupInteraction Click on an area to pick up its color, or ESC to cancel. DeskewApplyDialog 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. DeskewOptionsWidget Form Deskew Auto Manual Apply To ... DragHandler Unrestricted dragging is possible by holding down the Shift key. ErrorWidget Form 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) ImageViewBase Use the mouse wheel or +/- to zoom. When zoomed, dragging is possible. InteractiveXSpline Click to create a new control point. This point can be dragged. Drag this point or delete it by pressing Del or D. 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. LoadFilesStatusDialog Some files failed to load Loaded successfully: %1 Failed to load: %1 MainWindow Save Project MainWindow Keep current page in view. Use Home, End, PgUp (or Q), PgDown (or W) to navigate between pages. Tools File Help Debug Mode 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 First Page Home Last Page End About Fix DPI ... Relinking ... Settings ... Stop batch processing Save this project? 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. 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 - Scan Tailor %3 [%1bit] Error saving the project file! Files to insert Images not in project (%1) Skip failed files Remove NewOpenProjectPanel Form New Project ... Open Project ... Recent Projects OrientationApplyDialog Fix Orientation Apply to This page only (already applied) All pages This page and the following ones Every other page All odd or even pages, depending on the current page being odd or even. The current page will be included. Selected pages Use Ctrl+Click / Shift+Click to select multiple pages. Every other selected page OrientationOptionsWidget Form Rotate ... Reset Scope Apply to ... 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! 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. OutputChangeDewarpingDialog Apply Dewarping Mode Mode Off Auto (experimental) Manual Scope This page only All pages This page and the following ones Selected pages Use Ctrl+Click / Shift+Click to select multiple pages. OutputChangeDpiDialog Apply Output Resolution DPI Scope This page only This page and the following ones Selected pages Use Ctrl+Click / Shift+Click to select multiple pages. All pages OutputOptionsWidget Form Output Resolution (DPI) Change ... Mode White margins Equalize illumination Dewarping Depth perception No despeckling Apply To ... Despeckling Cautious despeckling ... Normal despeckling Aggressive despeckling Thinner Thicker PageLayoutApplyDialog Apply to This page only (already applied) All pages This page and the following ones Every other page All odd or even pages, depending on the current page being odd or even. The current page will be included. Selected pages Use Ctrl+Click / Shift+Click to select multiple pages. Every other selected page PageLayoutOptionsWidget Form Margins Millimeters (mm) Inches (in) Top ... Bottom Left Right Apply To ... Alignment Match size with other pages PageSplitModeDialog Split Pages Mode Auto Manual Scope This page only This page and the following ones Selected pages Use Ctrl+Click / Shift+Click to select multiple pages. All pages PageSplitOptionsWidget Form Page Layout ? Change ... Split Line Auto Manual PictureZonePropDialog Zone Properties Subtract from all layers Add to auto layer Subtract from auto layer ProjectFilesDialog Input Directory Output Directory Project Files Browse 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;"> <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;">Remove selected files from project.</p></body></html> << 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. ProjectOpeningContext Error Unable to interpret the project file. RelinkingDialog Relinking Undo ... Substitution File for %1 Substitution Directory for %1 This change would merge several files into one. RemovePagesDialog Remove Pages Remove %1 page(s) from project? Corresponding output files will be deleted, while input files will remain. SelectContentApplyDialog Select Content Apply to This page only (already applied) All pages This page and the following ones Every other page All odd or even pages, depending on the current page being odd or even. The current page will be included. Selected pages Use Ctrl+Click / Shift+Click to select multiple pages. Every other selected page SelectContentOptionsWidget Form Content Box Auto Manual Scope Apply to ... SettingsDialog Settings Use 3D acceleration for user interface Compiled without OpenGL support. Your hardware / driver don't provide the necessary features. StageListView Launch batch processing SystemLoadWidget Form System load ... Minimal Normal ThumbnailSequence %1 (page %2) ZoneContextMenuInteraction Properties Delete Delete confirmation Really delete this zone? ZoneCreationInteraction Click to finish this zone. ESC to cancel. Connect first and last points to finish this zone. ESC to cancel. Zones need to have at least 3 points. ESC to cancel. ZoneDefaultInteraction Drag the vertex. Click to create a new vertex here. Right click to edit zone properties. Click to start creating a new picture zone. ZoneVertexDragInteraction Merge these two vertices. Move the vertex to one of its neighbors to merge them. deskew::Filter Deskew deskew::ImageView Use Ctrl+Wheel to rotate or Ctrl+Shift+Wheel for finer rotation. Drag this handle to rotate the image. deskew::OptionsWidget Apply Deskew fix_orientation::ApplyDialog Can't do: more than one group is selected. fix_orientation::Filter Fix Orientation output::ChangeDpiDialog Custom Error DPI is not set. DPI is too low! DPI is too high! output::FillZoneEditor Pick color output::Filter Output output::OptionsWidget Black and White Color / Grayscale Mixed Apply Despeckling Level Apply Depth Perception Off Auto Manual 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 page_layout::ApplyDialog Can't do: more than one group is selected. page_layout::Filter Natural order Order by increasing width Order by increasing height Margins page_layout::ImageView Resize margins by dragging any of the solid lines. page_layout::OptionsWidget Apply Margins Apply Alignment page_split::Filter Natural order Order by split type Split Pages page_split::ImageView Drag the line or the handles. page_split::OptionsWidget Set manually Auto detected page_split::UnremoveButton Restore removed page. select_content::ApplyDialog Can't do: more than one group is selected. select_content::Filter Natural order Order by increasing width Order by increasing height Select Content select_content::ImageView Use the context menu to enable / disable the content box. Drag lines or corners to resize the content box. Create Content Box Remove Content Box scantailor-RELEASE_0_9_12_2/translations/scantailor_zh_CN.ts000066400000000000000000001704241271170121200240000ustar00rootroot00000000000000 AboutDialog About Scan Tailor 关于 Scan Tailor 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 Joseph Artsimovich 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. Petr Kovář - 命令行界é¢ã€‚ Neco Torquato - Brazilian Portuguese Neco Torquato - 巴西葡è„牙语 Svetoslav Sashkov, Mandor - Bulgarian Svetoslav Sashkov, Mandor - ä¿åŠ åˆ©äºšè¯­ Petr Kovář - Czech Petr Kovář - æ·å…‹è¯­ Stefan Birkner - German Stefan Birkner - 德语 Angelo Gemmi - Italian Angelo Gemmi - æ„大利语 Masahiro Kitagawa - Japanese Masahiro Kitagawa - 日语 Patrick Pascal - French Patrick Pascal - 法语 Daniel Koć - Polish Daniel Koć - 波兰语 Joseph Artsimovich - Russian Joseph Artsimovich - 俄语 Marián Hvolka - Slovak Marián Hvolka - 斯洛ä¼å…‹è¯­ Flavio Benelli - Spanish Flavio Benelli - 西ç­ç‰™è¯­ Davidson Wang - Traditional Chinese Davidson Wang - ç¹ä½“中文 Yuri Chornoivan - Ukrainian Yuri Chornoivan - 乌克兰语 denver 22 denver 22 Translators 翻译者 Contributors 贡献者 Documentation 文档 License 许å¯è¯ Damir13 - Croatian Damir13 - 克罗地亚语 Hxcan Cai - Simplified Chinese Hxcan Cai - 简体中文 BatchProcessingLowerPanel Form è¡¨å• Beep when finished 完整之åŽå‘出蜂鸣 ColorPickupInteraction Click on an area to pick up its color, or ESC to cancel. 点击一个区域以æå–它的颜色,或者按ESC以喿¶ˆã€‚ CrashReportDialog Crash Report 崩溃报告 Scan Tailor crashed. That's usually caused by errors in the software. You can help us find and fix the problem by submitting this crash report. Scan Tailor已崩溃。这通常是由软件里的错误引起的。你å¯ä»¥é€šè¿‡æäº¤è¿™ä¸ªå´©æºƒæŠ¥å‘Šæ¥å¸®åŠ©æˆ‘ä»¬æ‰¾åˆ°å¹¶ä¿®å¤è¿™ä¸ªé—®é¢˜ã€‚ Information to be sent å°†è¦å‘é€çš„ä¿¡æ¯ If you have additional information, for example you know how to reproduce the crash, please provide it here. English or Russian please. 如果妳还有其它的信æ¯ï¼Œä¾‹å¦‚采å–什么步骤å¯ä»¥é‡çŽ°è¿™ä¸ªå´©æºƒé—®é¢˜ï¼Œè¯·åœ¨è¿™é‡Œæä¾›ã€‚ 请使用英语或俄语。 If you are willing to provide further assistance to developers to help fix this problem, please provide your email address. 如果你愿æ„为开å‘者æä¾›è¿›ä¸€æ­¥çš„帮助以修å¤è¿™ä¸ªé—®é¢˜ï¼Œè¯·ç•™ä¸‹ä½ çš„邮箱地å€ã€‚ Email [optional] 邮箱·「å¯é€‰ã€ This file contains the internal state of the program when it crashed 这个文件中包å«äº†è¿™ä¸ªç¨‹åºåœ¨å´©æºƒæ—¶çš„å†…éƒ¨çŠ¶æ€ Dump file 转储文件 Sending ... 正在å‘é€â€¦â€¦ Unexpected response (code %1) from dispatcher 从调度器收到未预期的回å¤ï¼ˆä»£ç  %1) Unexpected response from dispatcher ä»Žè°ƒåº¦å™¨æ”¶åˆ°æœªé¢„æœŸçš„å›žå¤ Unexpected response (code %1) from the receiving side 从接收方收到未预期的回å¤ï¼ˆä»£ç  %1) Successfully sent å‘逿ˆåŠŸ DeskewApplyDialog 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+鼠标主键以选择多个页é¢ã€‚ DeskewOptionsWidget Form è¡¨å• Deskew 抗扭斜 Auto 自动 Manual 手动 Apply To ... 应用到…… DragHandler Unrestricted dragging is possible by holding down the Shift key. 按ä½Shift键以自由拖放。 ErrorWidget Form è¡¨å• FixDpiDialog Fix DPI ä¿®å¤DPI Tab 1 标签1 Tab 2 标签2 DPI DPI Custom 自定义 x 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) ImageViewBase Use the mouse wheel or +/- to zoom. When zoomed, dragging is possible. 使用鼠标滚轮或+/-æ¥ç¼©æ”¾ã€‚放大之åŽï¼Œå¯ä»¥æ‹–动。 InteractiveXSpline Click to create a new control point. å•击以创建一个新的控制点。 This point can be dragged. 这个控制点å¯ä»¥æ‹–动。 Drag this point or delete it by pressing Del or D. 拖动这个控制点,或者按Del或Dæ¥åˆ é™¤å®ƒã€‚ 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">é‡é“¾æŽ¥å·¥å…·</a> æ¥æ‰¾åˆ°å®ƒã€‚ LoadFilesStatusDialog Some files failed to load æŸäº›æ–‡ä»¶æœªèƒ½è½½å…¥ Loaded successfully: %1 æˆåŠŸè½½å…¥ï¼š %1 Failed to load: %1 载入失败: %1 MainWindow MainWindow ä¸»çª—å£ Keep current page in view. ä¿æŒå½“å‰é¡µé¢å¤„于视图中。 Use Home, End, PgUp (or Q), PgDown (or W) to navigate between pages. 使用Homeã€Endã€PgUp(或Q)ã€PgDown(或W)æ¥åœ¨ä¸åŒé¡µé¢ä¹‹é—´åˆ‡æ¢ã€‚ Tools 工具 File 文件 Help 帮助 Debug Mode è°ƒè¯•æ¨¡å¼ Save Project ä¿å­˜é¡¹ç›® Ctrl+S Ctrl+S Save Project As ... 项目å¦å­˜ä¸ºâ€¦â€¦ Next Page ä¸‹ä¸ªé¡µé¢ PgDown PgDown Previous Page å‰ä¸ªé¡µé¢ PgUp PgUp New Project ... 新建项目…… Ctrl+N Ctrl+N Open Project ... 打开项目…… Ctrl+O Ctrl+O Q Q W W Close Project 关闭项目 Ctrl+W Ctrl+W Quit 退出 Ctrl+Q Ctrl+Q Settings ... 设置…… First Page 第一页 Home Home Last Page 最åŽä¸€é¡µ End End About 关于 Fix DPI ... ä¿®å¤DPI…… Relinking ... 釿–°é“¾æŽ¥â€¦â€¦ Stop batch processing åœæ­¢æ‰¹é‡å¤„ç† Save this project? 是å¦è¦ä¿å­˜è¿™ä¸ªé¡¹ç›®ï¼Ÿ 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. 项目文件已æŸå。 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 - Scan Tailor %3 [%1bit] %2 - Scan Tailor %3 [%1ä½] Error saving the project file! ä¿å­˜é¡¹ç›®æ–‡ä»¶æ—¶å‡ºé”™ï¼ Files to insert è¦æ’入的文件 Images not in project (%1) ä¸åœ¨é¡¹ç›®ä¸­çš„图片(%1) Skip failed files 跳过处ç†å¤±è´¥çš„æ–‡ä»¶ Remove 删除 NewOpenProjectPanel Form è¡¨å• New Project ... 新建项目…… Open Project ... 打开项目…… Recent Projects 最近项目 OrientationApplyDialog Fix Orientation ä¿®å¤æœå‘ Apply to 应用到 This page only (already applied) 仅此页é¢ï¼ˆå·²ç»åº”用) All pages æ‰€æœ‰é¡µé¢ This page and the following ones 此页é¢åŠåŽç»­é¡µé¢ Every other page All odd or even pages, depending on the current page being odd or even. æ¯éš”一页 The current page will be included. 当å‰é¡µé¢å°†ä¼šåŒ…å«åœ¨å†…。 Selected pages æ‰€é€‰é¡µé¢ Use Ctrl+Click / Shift+Click to select multiple pages. 使用Ctrl+鼠标主键/Shift+鼠标主键以选择多个页é¢ã€‚ Every other selected page 所选页é¢ä¸­æ¯éš”一页 OrientationOptionsWidget Form è¡¨å• Rotate 旋转 ... …… Reset é‡ç½® Scope 范围 Apply to ... 应用到…… OutOfMemoryDialog Out of memory 内存ä¸è¶³ Out of Memory Situation in Scan Tailor 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. ä¿®å¤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! ä¿å­˜é¡¹ç›®æ–‡ä»¶æ—¶å‡ºé”™ï¼ 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+鼠标主键以选择多个页é¢ã€‚ OutputChangeDewarpingDialog Apply Dewarping Mode åº”ç”¨æŠ—æ‰­æ›²æ¨¡å¼ Mode æ¨¡å¼ Off å…³ Auto (experimental) 自动(试验状æ€ï¼‰ Manual 手动 Scope 范围 This page only ä»…æ­¤é¡µé¢ All pages æ‰€æœ‰é¡µé¢ This page and the following ones 此页é¢åŠåŽç»­é¡µé¢ Selected pages æ‰€é€‰é¡µé¢ Use Ctrl+Click / Shift+Click to select multiple pages. 使用Ctrl+鼠标主键/Shift+鼠标主键以选择多个页é¢ã€‚ OutputChangeDpiDialog Apply Output Resolution 应用输出分辨率 DPI DPI Scope 范围 This page only ä»…æ­¤é¡µé¢ All pages æ‰€æœ‰é¡µé¢ This page and the following ones 此页é¢åŠåŽç»­é¡µé¢ Selected pages æ‰€é€‰é¡µé¢ Use Ctrl+Click / Shift+Click to select multiple pages. 使用Ctrl+鼠标主键/Shift+鼠标主键以选择多个页é¢ã€‚ OutputOptionsWidget Form è¡¨å• Output Resolution (DPI) 输出分辨率(DPI) Change ... 改å˜â€¦â€¦ Mode æ¨¡å¼ White margins ç™½è‰²è¾¹è· Equalize illumination å‡è¡¡ç…§æ˜Žçж况 Thinner 更稀 Thicker 更浓 Apply To ... 应用到…… Dewarping 抗扭曲 Depth perception 深度感知 Despeckling 抗噪声 No despeckling ä¸è¿›è¡ŒæŠ—å™ªå£°å¤„ç† Cautious despeckling 谨慎地抗噪声 ... …… Normal despeckling 普通抗噪声 Aggressive despeckling 积æžåœ°æŠ—噪声 PageLayoutApplyDialog Apply to 应用到 This page only (already applied) 仅此页é¢ï¼ˆå·²ç»åº”用) All pages æ‰€æœ‰é¡µé¢ This page and the following ones 此页é¢åŠåŽç»­é¡µé¢ Every other page All odd or even pages, depending on the current page being odd or even. æ¯éš”一页 The current page will be included. 当å‰é¡µé¢å°†ä¼šåŒ…å«åœ¨å†…。 Selected pages æ‰€é€‰é¡µé¢ Use Ctrl+Click / Shift+Click to select multiple pages. 使用Ctrl+鼠标主键/Shift+鼠标主键以选择多个页é¢ã€‚ Every other selected page 所选页é¢ä¸­æ¯éš”一页 PageLayoutOptionsWidget Form è¡¨å• Margins è¾¹è· Millimeters (mm) 毫米(mm) Inches (in) 英寸(in) Top 顶部 ... …… Bottom 底部 Left 左侧 Right å³ä¾§ Apply To ... 应用到…… Alignment å¯¹é½ Match size with other pages 与其它页é¢çš„å°ºå¯¸åŒ¹é… PageSplitModeDialog Split Pages åˆ†å‰²é¡µé¢ Mode æ¨¡å¼ Auto 自动 Manual 手动 Scope 范围 This page only ä»…æ­¤é¡µé¢ All pages æ‰€æœ‰é¡µé¢ This page and the following ones 此页é¢åŠåŽç»­é¡µé¢ Selected pages æ‰€é€‰é¡µé¢ Use Ctrl+Click / Shift+Click to select multiple pages. 使用Ctrl+鼠标主键/Shift+鼠标主键以选择多个页é¢ã€‚ PageSplitOptionsWidget Form è¡¨å• Page Layout 页é¢å¸ƒå±€ ? ? Change ... 改å˜â€¦â€¦ Split Line 分割线 Auto 自动 Manual 手动 PictureZonePropDialog Zone Properties 区域属性 Subtract from all layers 从所有层中å‡åŽ» Add to auto layer 加入到所有层 Subtract from auto layer 从自动层中å‡åŽ» 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;"> <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;">将选中的文件添加到项目中。</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;">将选中的文件从项目中删除。</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. æŸäº›æ–‡ä»¶æ— æ³•载入。 å¯èƒ½æ˜¯å› ä¸ºæˆ‘们䏿”¯æŒå®ƒä»¬çš„æ ¼å¼ï¼Œä¹Ÿæœ‰å¯èƒ½æ˜¯å®ƒä»¬å·²ç»æŸå。 你应当将它们从项目中删除。 ProjectOpeningContext Error 错误 Unable to interpret the project file. 无法解æžé¡¹ç›®æ–‡ä»¶ã€‚ RelinkingDialog Relinking 釿–°é“¾æŽ¥ Undo 撤销 ... …… Substitution File for %1 %1的替代文件 Substitution Directory for %1 %1的替代目录 This change would merge several files into one. 这个改å˜ä¼šå°†å¤šä¸ªæ–‡ä»¶åˆå¹¶æˆä¸€ä¸ªã€‚ RemovePagesDialog Remove Pages åˆ é™¤é¡µé¢ Remove %1 page(s) from project? 从项目中删除%1个页é¢ï¼Ÿ Corresponding output files will be deleted, while input files will remain. 对应的输出文件会被删除,而输入文件会被ä¿ç•™ã€‚ SelectContentApplyDialog Select Content 选择内容 Apply to 应用到 This page only (already applied) 仅此页é¢ï¼ˆå·²ç»åº”用) All pages æ‰€æœ‰é¡µé¢ This page and the following ones 此页é¢åŠåŽç»­é¡µé¢ Every other page All odd or even pages, depending on the current page being odd or even. æ¯éš”一页 The current page will be included. 当å‰é¡µé¢å°†ä¼šåŒ…å«åœ¨å†…。 Selected pages æ‰€é€‰é¡µé¢ Use Ctrl+Click / Shift+Click to select multiple pages. 使用Ctrl+鼠标主键/Shift+鼠标主键以选择多个页é¢ã€‚ Every other selected page 所选页é¢ä¸­æ¯éš”一页 SelectContentOptionsWidget Form è¡¨å• Content Box 内容框 Auto 自动 Manual 手动 Scope 范围 Apply to ... 应用到…… SettingsDialog Settings 设置 Use 3D acceleration for user interface 对用户界é¢ä½¿ç”¨3D加速 Compiled without OpenGL support. 编译过程中未å¯ç”¨OpenGL支æŒã€‚ Your hardware / driver don't provide the necessary features. 你的硬件/驱动未æä¾›å¿…è¦çš„特性。 StageListView Launch batch processing å¯åŠ¨æ‰¹é‡å¤„ç† SystemLoadWidget Form è¡¨å• System load 系统负载 ... …… Minimal æœ€å° Normal 普通 ThumbnailSequence %1 (page %2) %1 ï¼ˆé¡µé¢ %2) ZoneContextMenuInteraction Delete confirmation 确认删除 Really delete this zone? 确认删除这个区域? Delete 删除 Properties 属性 ZoneCreationInteraction Click to finish this zone. ESC to cancel. å•击以结æŸè¿™ä¸ªåŒºåŸŸã€‚按ESCåˆ™å–æ¶ˆã€‚ Connect first and last points to finish this zone. ESC to cancel. 连接第一点和最åŽä¸€ä¸ªç‚¹ä»¥ç»“æŸè¿™ä¸ªåŒºåŸŸã€‚按ESCåˆ™å–æ¶ˆã€‚ Zones need to have at least 3 points. ESC to cancel. åŒºåŸŸéœ€è¦æœ‰æœ€å°‘3个点。按ESCåˆ™å–æ¶ˆã€‚ ZoneDefaultInteraction Drag the vertex. 拖动这个顶点。 Click to create a new vertex here. å•击以在这里创建一个新的顶点。 Right click to edit zone properties. å•击鼠标辅键以编辑区域属性。 Click to start creating a new picture zone. å•击以创建一个新的图片区域。 ZoneVertexDragInteraction Merge these two vertices. åˆå¹¶è¿™ä¸¤ä¸ªé¡¶ç‚¹ã€‚ Move the vertex to one of its neighbors to merge them. 将这个顶点移动到æŸä¸ªç›¸é‚»é¡¶ç‚¹ä¸Šä»¥åˆå¹¶å®ƒä»¬ã€‚ deskew::Filter Deskew 抗扭斜 deskew::ImageView Use Ctrl+Wheel to rotate or Ctrl+Shift+Wheel for finer rotation. 使用Ctrl+æ»šè½®æ¥æ—‹è½¬ï¼Œæˆ–者使用Ctrl+Shift+æ»šè½®æ¥æ›´ç²¾ç¡®åœ°æ—‹è½¬ã€‚ Drag this handle to rotate the image. 拖动手柄以旋转图片。 deskew::OptionsWidget Apply Deskew 应用抗扭斜 fix_orientation::ApplyDialog Can't do: more than one group is selected. 无法处ç†ï¼šå½“å‰é€‰ä¸­äº†å¤šä¸ªç»„。 fix_orientation::Filter Fix Orientation ä¿®å¤æœå‘ output::ChangeDpiDialog Custom 自定义 Error 错误 DPI is not set. 未设置DPI。 DPI is too low! DPIè¿‡ä½Žï¼ DPI is too high! DPIè¿‡é«˜ï¼ output::FillZoneEditor Pick color 拾å–颜色 output::Filter Output 输出 output::OptionsWidget Black and White 黑白 Color / Grayscale 彩色/ç°åº¦ Mixed æ··åˆ Apply Despeckling Level 应用抗噪声级别 Apply Depth Perception 应用深度感知 Off å…³ Auto 自动 Manual 手动 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 抗噪声 page_layout::ApplyDialog Can't do: more than one group is selected. 无法处ç†ï¼šå½“å‰é€‰ä¸­äº†å¤šä¸ªç»„。 page_layout::Filter Natural order è‡ªç„¶é¡ºåº Order by increasing width 按照宽度从å°åˆ°å¤§æŽ’åº Order by increasing height 按照高度从å°åˆ°å¤§æŽ’åº Margins è¾¹è· page_layout::ImageView Resize margins by dragging any of the solid lines. 拖动任一个实线以改å˜è¾¹è·å¤§å°ã€‚ page_layout::OptionsWidget Apply Margins åº”ç”¨è¾¹è· Apply Alignment åº”ç”¨å¯¹é½æ¡ä»¶ page_split::Filter Natural order è‡ªç„¶é¡ºåº Order by split type æŒ‰ç…§åˆ†å‰²ç±»åž‹æŽ’åº Split Pages åˆ†å‰²é¡µé¢ page_split::ImageView Drag the line or the handles. 拖动线或手柄。 page_split::OptionsWidget Set manually 手动设置 Auto detected 自动检测 page_split::UnremoveButton Restore removed page. æ¢å¤å·²è¢«åˆ é™¤çš„页é¢ã€‚ select_content::ApplyDialog Can't do: more than one group is selected. 无法处ç†ï¼šå½“å‰é€‰ä¸­äº†å¤šä¸ªç»„。 select_content::Filter Natural order è‡ªç„¶é¡ºåº Order by increasing width 按照宽度从å°åˆ°å¤§æŽ’åº Order by increasing height 按照高度从å°åˆ°å¤§æŽ’åº Select Content 选择内容 select_content::ImageView Use the context menu to enable / disable the content box. 使用上下文èœå•æ¥å¯ç”¨/ç¦ç”¨å†…容框。 Drag lines or corners to resize the content box. 拖动线或边角以改å˜å†…容框的大å°ã€‚ Create Content Box 创建内容框 Remove Content Box 删除内容框 scantailor-RELEASE_0_9_12_2/translations/scantailor_zh_TW.ts000066400000000000000000002641361271170121200240360ustar00rootroot00000000000000 AboutDialog About Scan Tailor 關於 Scan Tailor 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 Joseph Artsimovich U235 - Picture auto-detection algorithm. Robert B. - First generation dewarping algorithm. Andrey Bergman - System load adjustment. Petr Kovář - Command line interface. Neco Torquato - Brazilian Portuguese Svetoslav Sashkov, Mandor - Bulgarian Petr Kovář - Czech Stefan Birkner - German Angelo Gemmi - Italian Masahiro Kitagawa - Japanese Patrick Pascal - French Daniel Koć - Polish Joseph Artsimovich - Russian Marián Hvolka - Slovak Flavio Benelli - Spanish Davidson Wang - Traditional Chinese Yuri Chornoivan - Ukrainian denver 22 Translators 翻譯人員 Svetoslav Sashkov, Mandor - Bulgarian Stefan Birkner - German Angelo Gemmi - Italian Masahiro Kitagawa - Japanese Patrick Pascal - French Joseph Artsimovich - Russian Davidson Wang - Traditional Chinese Svetoslav Sashkov, Mandor - Bulgarian Stefan Birkner - German Angelo Gemmi - Italian Masahiro Kitagawa - Japanese Patrick Pascal - French Joseph Artsimovich - Russian Davidson Wang - Traditional Chinese Contributors è²¢ç»è€… U235 - Picture auto-detection algorithm. Robert B. - Dewarping algorithm. Andrey Bergman - System load adjustment. U235 - Picture è‡ªå‹•åµæ¸¬æ¼”算法. Robert B. - Dewarping 演算法. Andrey Bergman - 系統負載調整. Documentation 文件製作 denver 22 phaedrus Taxman denver 22 phaedrus Taxman License 版權 BatchProcessingLowerPanel Form Beep when finished å®Œæˆæ™‚以"å—¶"è²æç¤º ColorPickupInteraction Click on an area to pick up its color, or ESC to cancel. 以滑鼠左éµåœ¨å€åŸŸä¸­æŒ‘é¸é¡è‰²ï¼Œæˆ–按ESCéµå–消. DeskewApplyDialog 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+滑鼠左éµä¾†é¸æ“‡å¤šå€‹é é¢. DeskewOptionsWidget Form Deskew ç³¾å Auto 自動 Manual 手動 Apply To ... 套用到... DragHandler Unrestricted dragging is possible by holding down the Shift key. 長按 Shift éµå¯ä»¥è‡ªç”±ç§»å‹•ç•«é¢. ErrorWidget Form FixDpiDialog 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 é ) Fix DPI 修正DPI Tab 1 Tab 2 DPI Custom 自訂 x Apply 套用 FixDpiSinglePageDialog Fix DPI 修正DPI DPI for %1 %1çš„DPI Custom 自訂 %1 (page %2) %1 (第 %2 é ) 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éŽå°. 這樣的組åˆå¯èƒ½æœƒå°Žè‡´è¨˜æ†¶é«”溢ä½éŒ¯èª¤. ImageViewBase Use the mouse wheel or +/- to zoom. When zoomed, dragging is possible. 使用滑鼠滾輪或+/-éµä¾†ç¸®æ”¾. 縮放後å¯å°åœ–片進行拖曳移動. InteractiveXSpline Click to create a new control point. This point can be dragged. Drag this point or delete it by pressing Del or D. 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. LoadFilesStatusDialog Some files failed to load Loaded successfully: %1 Failed to load: %1 MainWindow Save Project 儲存專案 Save this project? 是å¦è¦å„²å­˜æ­¤å°ˆæ¡ˆ? 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. 專案檔已毀æ. 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 - Scan Tailor %3 [%1bit] Error saving the project file! 儲存時發生錯誤! Files to insert Skip failed files File to insert æ’入檔案 Images not in project (%1) 專案外的圖片 (%1) Error opening the image file. é–‹å•Ÿå½±åƒæ™‚發生錯誤. Remove 移除 MainWindow Keep current page in view. ä¿æŒç¸®åœ–與處ç†ç•«é¢åŒæ­¥. Use Home, End, PgUp (or Q), PgDown (or W) to navigate between pages. 使用 Homeéµ, PgUp(或Q)éµ, PgDown(或W)éµä¾†å°‹è¦½é é¢. Tools 工具 File 檔案 Help 說明 Debug 除錯 Debug Mode 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 First Page ç¬¬ä¸€é  Home Last Page æœ€å¾Œé  End About 關於 Fix DPI ... Relinking ... Stop batch processing åœæ­¢æ‰¹æ¬¡è™•ç† Settings ... 設定... NewOpenProjectPanel Form New Project ... 建立新專案... Open Project ... 開啟專案... Recent Projects 最近使用的專案 OrientationApplyDialog Fix Orientation ä¿®æ­£æ–¹å‘ Apply to 套用到 This page only (already applied) åªæœ‰é€™ä¸€é . (已經套用了) All pages 所有é é¢ This page and the following ones 本é åŠä»¥ä¸‹çš„é é¢ Every other page All odd or even pages, depending on the current page being odd or even. æ‰€æœ‰å¥‡æ•¸èˆ‡å¶æ•¸é é¢, 會根據目å‰çš„é é¢æ˜¯å¥‡æ•¸æˆ–å¶æ•¸. 所有其他é é¢ The current page will be included. 將會包å«ç›®å‰çš„é é¢. Selected pages 鏿“‡çš„é é¢ Use Ctrl+Click / Shift+Click to select multiple pages. 使用 Ctrl或Shift+滑鼠左éµä¾†é¸æ“‡å¤šå€‹é é¢. Every other selected page æ‰€æœ‰å…¶ä»–é¸æ“‡çš„é é¢ OrientationOptionsWidget Form Rotate 旋轉 ... Reset é‡è¨­ Scope ç¯„åœ Apply to ... 套用到... 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 Scan Tailor 專案 Error 錯誤 Error saving the project file! 儲存時發生錯誤! 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+滑鼠左éµä¾†é¸æ“‡å¤šå€‹é é¢. OutputChangeDewarpingDialog Apply Dewarping Mode Mode æ¨¡å¼ Off Auto (experimental) Manual 手動 Scope ç¯„åœ This page only åªæœ‰é€™ä¸€é  All pages 所有é é¢ This page and the following ones 本é åŠä»¥ä¸‹çš„é é¢ Selected pages 鏿“‡çš„é é¢ Use Ctrl+Click / Shift+Click to select multiple pages. 使用 Ctrl或Shift+滑鼠左éµä¾†é¸æ“‡å¤šå€‹é é¢. OutputChangeDpiDialog Apply Output Resolution 套用輸出解æžåº¦ DPI DPI Scope ç¯„åœ This page only åªæœ‰é€™ä¸€é  This page and the following ones 本é åŠä»¥ä¸‹çš„é é¢ Selected pages 鏿“‡çš„é é¢ Use Ctrl+Click / Shift+Click to select multiple pages. 使用 Ctrl或Shift+滑鼠左éµä¾†é¸æ“‡å¤šå€‹é é¢. All pages 所有é é¢ OutputOptionsWidget Form Output Resolution (DPI) 輸出解æžåº¦ (DPI) Change ... 變更... Mode æ¨¡å¼ White margins 空白邊界 Equalize illumination 等化亮度 Dewarping Depth perception No despeckling ä¸é€²è¡Œé›œé»žæ¸…除 Dewarp 消除彎曲變形 Apply To ... 套用到... Despeckling 雜點清除 Cautious despeckling 輕微的雜點清除 ... Normal despeckling 普通的雜點清除 Aggressive despeckling 強力的雜點清除 Thinner 較細的 Thicker 較粗的 PageLayoutApplyDialog Apply to 套用到 This page only (already applied) åªæœ‰é€™ä¸€é . (已經套用了) All pages 所有é é¢ This page and the following ones 本é åŠä»¥ä¸‹çš„é é¢ Every other page All odd or even pages, depending on the current page being odd or even. 所有其他é é¢ The current page will be included. 將會包å«ç›®å‰çš„é é¢. Selected pages 鏿“‡çš„é é¢ Use Ctrl+Click / Shift+Click to select multiple pages. 使用 Ctrl或Shift+滑鼠左éµä¾†é¸æ“‡å¤šå€‹é é¢. Every other selected page æ‰€æœ‰å…¶ä»–é¸æ“‡çš„é é¢ PageLayoutOptionsWidget Form Margins é‚Šè· Millimeters (mm) 公厘 (mm) Inches (in) è‹±å‹ (in) Top 上 ... Bottom 下 Left å·¦ Right å³ Apply To ... 套用到... Alignment å°é½Š Match size with other pages 與其他é é¢å¤§å°ç›¸åŒ PageSplitModeDialog Split Pages 切割é é¢ Mode æ¨¡å¼ Auto 自動 Manual 手動 Scope ç¯„åœ This page only åªæœ‰é€™ä¸€é  This page and the following ones 本é åŠä»¥ä¸‹çš„é é¢ Selected pages 鏿“‡çš„é é¢ Use Ctrl+Click / Shift+Click to select multiple pages. 使用 Ctrl或Shift+滑鼠左éµä¾†é¸æ“‡å¤šå€‹é é¢. All pages 所有é é¢ PageSplitOptionsWidget Form Page Layout é é¢ä½ˆå±€ ? Change ... 變更... Split Line 分割線 Auto 自動 Manual 手動 PictureZonePropDialog Zone Properties å€å¡Šå±¬æ€§ Subtract from all layers 從所有的圖層中刪減 Add to auto layer 加入到自動圖層 Subtract from auto layer 從所有的自動圖層中刪減 ProjectFilesDialog Input Directory 輸入目錄 Output Directory 輸出目錄 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. æŸäº›æª”案載入失敗. å¯èƒ½æ˜¯ä¸è¢«æ”¯æ´çš„æª”案類型或是檔案æå£ž. 您必須從專案中移除那些檔案. Project Files 專案檔案 Browse ç€è¦½ 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;"> <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;">åŠ å…¥é¸æ“‡çš„項目到專案中.</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;">å¾žå°ˆæ¡ˆä¸­ç§»é™¤é¸æ“‡çš„æª”案.</p></body></html> << Files In Project 專案中的檔案 Right to left layout (for Hebrew and Arabic) 從å³è‡³å·¦çš„佈局 (希伯來語和阿拉伯語) Fix DPIs, even if they look OK 總是修正DPI ProjectOpeningContext Error 錯誤 Unable to interpret the project file. 無法解讀專案檔. RelinkingDialog Relinking Undo ... Substitution File for %1 Substitution Directory for %1 This change would merge several files into one. RemovePagesDialog Remove Pages 移除é é¢ Remove %1 page(s) from project? 是å¦è¦å¾žå°ˆæ¡ˆä¸­ç§»é™¤é€™ %1 é ? Corresponding output files will be deleted, while input files will remain. 輸出目錄中的檔案將被刪除, 原始輸入檔案會ä¿ç•™. SelectContentApplyDialog Select Content 鏿“‡å…§å®¹ Apply to 套用到 This page only (already applied) All pages 所有é é¢ This page and the following ones 本é åŠä»¥ä¸‹çš„é é¢ Every other page All odd or even pages, depending on the current page being odd or even. 所有其他é é¢ The current page will be included. 將會包å«ç›®å‰çš„é é¢. Selected pages 鏿“‡çš„é é¢ Use Ctrl+Click / Shift+Click to select multiple pages. 使用 Ctrl或Shift+滑鼠左éµä¾†é¸æ“‡å¤šå€‹é é¢. Every other selected page æ‰€æœ‰å…¶ä»–é¸æ“‡çš„é é¢ SelectContentOptionsWidget Form Content Box 內容å€å¡Š Auto 自動 Manual 手動 Scope ç¯„åœ Apply to ... 套用到... SettingsDialog Compiled without OpenGL support. 沒有支æ´OpenGL的編譯. Your hardware / driver don't provide the necessary features. 您的硬體/é©…å‹•ç¨‹å¼æ²’æœ‰æ”¯æ´æ‰€éœ€è¦çš„功能. Settings 設定 Use 3D acceleration for user interface å°ä½¿ç”¨è€…介é¢ä½¿ç”¨3D加速 StageListView Launch batch processing 開始批次處ç†ç¨‹åº SystemLoadWidget Form System load 系統負載 ... Minimal æœ€å° Normal 普通 ThumbnailSequence %1 (page %2) %1 (第 %2 é ) ZoneContextMenuInteraction Properties 屬性 Delete 刪除 Delete confirmation 確èªåˆªé™¤ Really delete this zone? 是å¦çœŸçš„è¦åˆªé™¤é€™å€‹å€åŸŸ? ZoneCreationInteraction Click to finish this zone. ESC to cancel. 按下滑鼠左éµä¾†æ¨™å®šå€åŸŸ. 按ESCéµå–消. Connect first and last points to finish this zone. ESC to cancel. 將起點與終點相連已完æˆå€åŸŸçš„劃定. 按下ESCéµå–消. Zones need to have at least 3 points. ESC to cancel. 至少需è¦3個點來標定å€åŸŸ. 按ESCéµå–消. ZoneDefaultInteraction Drag the vertex. 拖曳頂點. Click to create a new vertex here. 在此按下滑鼠左éµä¾†å»ºç«‹é ‚點. Right click to edit zone properties. 按下滑鼠å³éµä¾†ç·¨è¼¯å€åŸŸå±¬æ€§. Click to start creating a new picture zone. 按下滑鼠左éµä¾†å»ºç«‹æ–°çš„圖片範åœ. ZoneVertexDragInteraction Merge these two vertices. åˆä½µé€™å…©å€‹é ‚點. Move the vertex to one of its neighbors to merge them. 與最新進的頂點進行åˆä½µ. deskew::Filter Deskew ç³¾å deskew::ImageView Use Ctrl+Wheel to rotate or Ctrl+Shift+Wheel for finer rotation. 使用 Ctrl+滑鼠滾輪來旋轉或Shift+滑鼠滾輪來微調旋轉. Drag this handle to rotate the image. 拖曳此點來旋轉é é¢. deskew::OptionsWidget Apply Deskew fix_orientation::ApplyDialog Can't do: more than one group is selected. 無法進行動作: è¶…éŽä¸€å€‹ä»¥ä¸Šçš„群組被é¸å–. fix_orientation::Filter Fix Orientation ä¿®æ­£æ–¹å‘ output::ChangeDpiDialog Custom 自訂 Error 錯誤 DPI is not set. 尚未設定DPI. DPI is too low! DPI值太低! DPI is too high! DPI值太高! output::FillZoneEditor Pick color 挑é¸é¡è‰² output::Filter Output 輸出 output::OptionsWidget Black and White 單色(黑白) Color / Grayscale 彩色 / ç°éšŽ Mixed æ··åˆ Apply Despeckling Level 套用雜點清除 Apply Depth Perception Off Auto 自動 Manual 手動 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 雜點清除 page_layout::ApplyDialog Can't do: more than one group is selected. 無法進行動作: è¶…éŽä¸€å€‹ä»¥ä¸Šçš„群組被é¸å–. page_layout::Filter Natural order è‡ªç„¶æŽ’åº Order by increasing width 寬度éžå¢žæŽ’åº Order by increasing height 高度éžå¢žæŽ’åº Margins é‚Šè· page_layout::ImageView Resize margins by dragging any of the solid lines. 拖曳線段å¯ä»¥ç”¨ä¾†èª¿æ•´é‚Šè·å¤§å°. page_layout::OptionsWidget Apply Margins 套用邊è·è¨­å®š Apply Alignment 套用å°é½Šè¨­å®š page_split::Filter Natural order è‡ªç„¶æŽ’åº Order by split type Split Pages 分割é é¢ page_split::ImageView Drag the line or the handles. 拖曳線段或控制點. page_split::OptionsWidget Set manually 設為手動 Auto detected è‡ªå‹•é¸æ“‡ page_split::UnremoveButton Restore removed page. 復原被移除的é é¢. select_content::ApplyDialog Can't do: more than one group is selected. 無法進行動作: è¶…éŽä¸€å€‹ä»¥ä¸Šçš„群組被é¸å–. select_content::Filter Natural order è‡ªç„¶æŽ’åº Order by increasing width 寬度éžå¢žæŽ’åº Order by increasing height 高度éžå¢žæŽ’åº Select Content 鏿“‡å…§å®¹ select_content::ImageView Use the context menu to enable / disable the content box. 使用內容é¸å–®ä¾† 啟用/åœç”¨ 內容å€å¡Š. Drag lines or corners to resize the content box. 拖曳線段或角è½ä¾†èª¿æ•´å…§å®¹å€å¡Šå¤§å°. Create Content Box 建立內容å€å¡Š Remove Content Box 移除內容å€å¡Š scantailor-RELEASE_0_9_12_2/ui/000077500000000000000000000000001271170121200160745ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/ui/AboutDialog.ui000066400000000000000000000342611271170121200206330ustar00rootroot00000000000000 AboutDialog 0 0 482 349 About Scan Tailor 1 true 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. true Qt::Vertical 440 127 1 0 font-size: 16pt; font-weight: bold; Scan Tailor Qt::AlignBottom|Qt::AlignHCenter :/icons/appicon-about.png font-size:10pt; font-weight:bold; version Qt::AlignHCenter|Qt::AlignTop Authors true 0 -163 445 451 font-weight: bold; Lead Developer Joseph Artsimovich font-weight: bold; Contributors 0 0 U235 - Picture auto-detection algorithm. Robert B. - First generation dewarping algorithm. Andrey Bergman - System load adjustment. Petr Kovář - Command line interface. font-weight: bold; Translators 0 0 Neco Torquato - Brazilian Portuguese Svetoslav Sashkov, Mandor - Bulgarian Damir13 - Croatian Petr Kovář - Czech Stefan Birkner - German Angelo Gemmi - Italian Masahiro Kitagawa - Japanese Patrick Pascal - French Daniel Koć - Polish Joseph Artsimovich - Russian Marián Hvolka - Slovak Flavio Benelli - Spanish Davidson Wang - Traditional Chinese Yuri Chornoivan - Ukrainian Hxcan Cai - Simplified Chinese font-weight: bold; Documentation 0 0 denver 22 phaedrus Taxman Qt::Vertical 20 0 License <!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:'WenQuanYi Micro Hei'; font-size:10pt; font-weight:400; font-style:normal;"> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Ubuntu'; font-size:11pt;"><br /></p></body></html> true QDialogButtonBox::Close buttonBox clicked(QAbstractButton*) AboutDialog accept() 440 327 477 291 scantailor-RELEASE_0_9_12_2/ui/BatchProcessingLowerPanel.ui000066400000000000000000000031441271170121200235040ustar00rootroot00000000000000 BatchProcessingLowerPanel 0 0 259 73 Form Qt::Horizontal 51 20 Beep when finished Qt::Horizontal 50 20 SystemLoadWidget QWidget
SystemLoadWidget.h
1
scantailor-RELEASE_0_9_12_2/ui/CMakeLists.txt000066400000000000000000000000531271170121200206320ustar00rootroot00000000000000SOURCE_GROUP("UI Files" FILES ${ui_files}) scantailor-RELEASE_0_9_12_2/ui/ErrorWidget.ui000066400000000000000000000050021271170121200206650ustar00rootroot00000000000000 ErrorWidget 0 0 368 208 Form Qt::Vertical 20 40 Qt::Horizontal 40 20 Qt::AlignHCenter ImageLabel TextLabel Qt::AutoText true Qt::LinksAccessibleByMouse Qt::Horizontal 40 20 Qt::Vertical 20 40 scantailor-RELEASE_0_9_12_2/ui/FixDpiDialog.ui000066400000000000000000000110621271170121200207360ustar00rootroot00000000000000 FixDpiDialog 0 0 335 354 Fix DPI 0 Tab 1 Tab 2 DPI Qt::Horizontal 0 20 false Custom false 45 16777215 x false 45 16777215 false Apply Qt::Horizontal 0 20 Qt::Vertical 317 16 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok buttonBox accepted() FixDpiDialog accept() 257 344 157 274 buttonBox rejected() FixDpiDialog reject() 325 344 286 274 scantailor-RELEASE_0_9_12_2/ui/LoadFilesStatusDialog.ui000066400000000000000000000121431271170121200226220ustar00rootroot00000000000000 LoadFilesStatusDialog 0 0 461 312 Some files failed to load 1 true Loaded successfully: %1 0 161 0 0 161 0 120 120 120 75 true QPlainTextEdit::NoWrap true Failed to load: %1 198 18 18 198 18 18 120 120 120 75 true QPlainTextEdit::NoWrap true Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() LoadFilesStatusDialog accept() 257 302 157 274 buttonBox rejected() LoadFilesStatusDialog reject() 325 302 286 274 scantailor-RELEASE_0_9_12_2/ui/MainWindow.ui000066400000000000000000000243071271170121200205150ustar00rootroot00000000000000 MainWindow 0 0 613 445 0 0 MainWindow 0 0 ArrowCursor 0 0 0 Qt::Horizontal false 6 false QAbstractItemView::NoEditTriggers false false true QAbstractItemView::SingleSelection QAbstractItemView::SelectRows Qt::ElideNone false false false 1 1 QFrame::StyledPanel QFrame::Sunken 0 0 0 Keep current page in view. :/icons/keep-in-view.png:/icons/keep-in-view.png true true 249 16777215 Use Home, End, PgUp (or Q), PgDown (or W) to navigate between pages. Qt::AlignHCenter|Qt::AlignTop QComboBox::AdjustToMinimumContentsLength 0 0 613 21 Tools File Help true false Debug Mode Save Project Ctrl+S Save Project As ... Ctrl+Shift+S Next Page PgDown false Previous Page PgUp false New Project ... Ctrl+N Open Project ... Ctrl+O Previous Page Q false Next Page W false Close Project Ctrl+W Quit Ctrl+Q Settings ... First Page Home Last Page End About Fix DPI ... Relinking ... StageListView QTableView
StageListView.h
NonOwningWidget QWidget
NonOwningWidget.h
1
scantailor-RELEASE_0_9_12_2/ui/NewOpenProjectPanel.ui000066400000000000000000000061731271170121200223240ustar00rootroot00000000000000 NewOpenProjectPanel 0 0 302 81 0 0 Form false 7 Qt::Horizontal QSizePolicy::Minimum 20 20 New Project ... Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::Horizontal QSizePolicy::Minimum 20 20 Open Project ... Qt::Horizontal QSizePolicy::Minimum 20 20 Recent Projects scantailor-RELEASE_0_9_12_2/ui/OutOfMemoryDialog.ui000066400000000000000000000425171271170121200220110ustar00rootroot00000000000000 OutOfMemoryDialog 0 0 470 342 Out of memory 0 0 0 font-size: 17px; font-weight: bold; Out of Memory Situation in Scan Tailor Qt::AlignCenter Qt::Vertical QSizePolicy::Minimum 434 7 0 false Possible reasons 0 0 QFrame::NoFrame QFrame::Plain true 0 0 446 227 Did you have to fix the DPI of your source images? Are you sure the values you entered were correct? true true Qt::Horizontal 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. true true Qt::Horizontal Is your output DPI set too high? Usually you don't need it higher than 600. true true Qt::Vertical 20 0 What can help 0 0 QFrame::NoFrame true 0 0 446 227 Fix your DPIs. Learn how to <a href="http://vimeo.com/12524529">estimate unknown DPIs</a>. true true Qt::Horizontal If your hardware and operating system are 64-bit capable, consider switching to a 64-bit version of Scan Tailor. true true Qt::Horizontal 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. true true Qt::Horizontal 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. true true Qt::Vertical 20 9 What won't help 0 QFrame::NoFrame QFrame::Plain true 0 0 446 227 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. true true Qt::Vertical 20 0 Save Project true Qt::Horizontal 40 20 Save Project As ... Qt::Horizontal 40 20 Don't Save font-size: 17px; font-weight: bold; Project Saved Successfully Qt::AlignCenter Qt::Vertical QSizePolicy::Preferred 20 20 0 0 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. Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop true QDialogButtonBox::Close buttonBox rejected() OutOfMemoryDialog reject() 404 337 470 359 scantailor-RELEASE_0_9_12_2/ui/ProjectFilesDialog.ui000066400000000000000000000162061271170121200221510ustar00rootroot00000000000000 ProjectFilesDialog 0 0 482 506 Project Files Input Directory true Browse Output Directory Browse Files Not In Project QAbstractItemView::ExtendedSelection Select All Qt::Vertical 20 40 <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;">Remove selected files from project.</p></body></html> << Qt::Vertical 20 40 Files In Project true QAbstractItemView::ExtendedSelection Select All Right to left layout (for Hebrew and Arabic) Fix DPIs, even if they look OK 0 false Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox rejected() ProjectFilesDialog reject() 295 463 286 274 offProjectSelectAllBtn clicked() offProjectList selectAll() 74 381 86 318 inProjectSelectAllBtn clicked() inProjectList selectAll() 386 380 382 304 scantailor-RELEASE_0_9_12_2/ui/RelinkingDialog.ui000066400000000000000000000107541271170121200215040ustar00rootroot00000000000000 RelinkingDialog 0 0 400 300 Relinking 6 0 0 0 0 0 255 0 0 255 0 0 120 120 120 error true 0 0 Undo ... :/icons/undo-22.png:/icons/undo-22.png Qt::ScrollBarAlwaysOff QAbstractItemView::NoEditTriggers Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok RelinkingListView QListView
RelinkingListView.h
RelinkablePathVisualization QWidget
RelinkablePathVisualization.h
1
buttonBox rejected() RelinkingDialog reject() 325 290 286 274
scantailor-RELEASE_0_9_12_2/ui/RemovePagesDialog.ui000066400000000000000000000067421271170121200220010ustar00rootroot00000000000000 RemovePagesDialog 0 0 352 111 Remove Pages 0 0 ICON Qt::Horizontal QSizePolicy::Preferred 10 20 0 0 Remove %1 page(s) from project? Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter true Corresponding output files will be deleted, while input files will remain. Qt::AlignCenter Qt::Vertical 20 40 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() RemovePagesDialog accept() 248 254 157 274 buttonBox rejected() RemovePagesDialog reject() 316 260 286 274 scantailor-RELEASE_0_9_12_2/ui/SettingsDialog.ui000066400000000000000000000036071271170121200213610ustar00rootroot00000000000000 SettingsDialog 0 0 395 183 Settings Use 3D acceleration for user interface Qt::Vertical 20 40 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() SettingsDialog accept() 248 254 157 274 buttonBox rejected() SettingsDialog reject() 316 260 286 274 scantailor-RELEASE_0_9_12_2/ui/SystemLoadWidget.ui000066400000000000000000000043451271170121200216710ustar00rootroot00000000000000 SystemLoadWidget 0 0 232 39 Form 0 System load Qt::Horizontal QSizePolicy::Fixed 6 20 ... :/icons/minus-16.png:/icons/minus-16.png true false Qt::Horizontal QSlider::NoTicks ... :/icons/plus-16.png:/icons/plus-16.png true scantailor-RELEASE_0_9_12_2/version.h000066400000000000000000000017061271170121200173210ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SCANTAILOR_VERSION_H_ #define SCANTAILOR_VERSION_H_ #define VERSION "0.9.12" #define VERSION_QUAD "0.9.12.1" // Must be "x.x.x.x" or an empty string. #endif scantailor-RELEASE_0_9_12_2/zones/000077500000000000000000000000001271170121200166155ustar00rootroot00000000000000scantailor-RELEASE_0_9_12_2/zones/BasicSplineVisualizer.cpp000066400000000000000000000037351271170121200236030ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "BasicSplineVisualizer.h" #include "EditableZoneSet.h" #include #include #include #ifndef Q_MOC_RUN #include #endif BasicSplineVisualizer::BasicSplineVisualizer() : 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, QTransform const& to_screen, EditableZoneSet const& zones) { BOOST_FOREACH(EditableZoneSet::Zone const& zone, zones) { drawSpline(painter, to_screen, zone.spline()); } } void BasicSplineVisualizer::drawSpline( QPainter& painter, QTransform const& to_screen, EditableSpline::Ptr const& spline) { prepareForSpline(painter, spline); painter.drawPolygon(to_screen.map(spline->toPolygon()), Qt::WindingFill); } void BasicSplineVisualizer::drawVertex(QPainter& painter, QPointF const& pt, QColor const& color) { painter.setPen(Qt::NoPen); painter.setBrush(color); QRectF rect(0, 0, 4, 4); rect.moveCenter(pt); painter.drawEllipse(rect); } void BasicSplineVisualizer::prepareForSpline( QPainter& painter, EditableSpline::Ptr const&) { painter.setPen(m_pen); painter.setBrush(Qt::NoBrush); } scantailor-RELEASE_0_9_12_2/zones/BasicSplineVisualizer.h000066400000000000000000000032511271170121200232410ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef BASIC_SPLINE_VISUALIZER_H_ #define BASIC_SPLINE_VISUALIZER_H_ #include "EditableSpline.h" #include #include class EditableZoneSet; class QPainter; class QTransform; class BasicSplineVisualizer { public: BasicSplineVisualizer(); QRgb solidColor() const { return m_solidColor; } QRgb highlightBrightColor() const { return m_highlightBrightColor; } QRgb highlightDarkColor() const { return m_highlightDarkColor; } void drawVertex(QPainter& painter, QPointF const& pt, QColor const& color); void drawSplines(QPainter& painter, QTransform const& to_screen, EditableZoneSet const& zones); virtual void drawSpline(QPainter& painter, QTransform const& to_screen, EditableSpline::Ptr const& spline); virtual void prepareForSpline(QPainter& painter, EditableSpline::Ptr const& spline); protected: QRgb m_solidColor; QRgb m_highlightBrightColor; QRgb m_highlightDarkColor; QPen m_pen; }; #endif scantailor-RELEASE_0_9_12_2/zones/CMakeLists.txt000066400000000000000000000014551271170121200213620ustar00rootroot00000000000000PROJECT(zones) 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 ) SOURCE_GROUP(Sources FILES ${sources}) QT4_AUTOMOC(${sources}) ADD_LIBRARY(zones STATIC ${sources}) TRANSLATION_SOURCES(scantailor ${sources})scantailor-RELEASE_0_9_12_2/zones/EditableSpline.cpp000066400000000000000000000047321271170121200222130ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "EditableSpline.h" #include "SerializableSpline.h" #include "SplineSegment.h" #include #include #include #ifndef Q_MOC_RUN #include #endif #include EditableSpline::EditableSpline() { } EditableSpline::EditableSpline(SerializableSpline const& spline) { BOOST_FOREACH(QPointF const& pt, spline.toPolygon()) { appendVertex(pt); } SplineVertex::Ptr last_vertex(lastVertex()); if (last_vertex.get() && firstVertex()->point() == last_vertex->point()) { last_vertex->remove(); } setBridged(true); } void EditableSpline::appendVertex(QPointF const& pt) { m_sentinel.insertBefore(pt); } bool EditableSpline::hasAtLeastSegments(int num) const { for (SegmentIterator it((EditableSpline&)*this); num > 0 && it.hasNext(); it.next()) { --num; } return num == 0; } QPolygonF EditableSpline::toPolygon() const { QPolygonF poly; 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()); } return poly; } /*======================== Spline::SegmentIterator =======================*/ bool EditableSpline::SegmentIterator::hasNext() const { return m_ptrNextVertex && m_ptrNextVertex->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); } } scantailor-RELEASE_0_9_12_2/zones/EditableSpline.h000066400000000000000000000034101271170121200216500ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef EDITABLE_SPLINE_H_ #define EDITABLE_SPLINE_H_ #include "RefCountable.h" #include "IntrusivePtr.h" #include "SplineVertex.h" #include "SplineSegment.h" #include class SerializableSpline; class EditableSpline : public RefCountable { public: typedef IntrusivePtr Ptr; class SegmentIterator { public: SegmentIterator(EditableSpline& spline) : m_ptrNextVertex(spline.firstVertex()) {} bool hasNext() const; SplineSegment next(); private: SplineVertex::Ptr m_ptrNextVertex; }; EditableSpline(); EditableSpline(SerializableSpline const& spline); void appendVertex(QPointF const& pt); SplineVertex::Ptr firstVertex() const { return m_sentinel.firstVertex(); } SplineVertex::Ptr lastVertex() const{ return m_sentinel.lastVertex(); } bool hasAtLeastSegments(int num) const; bool bridged() const { return m_sentinel.bridged(); } void setBridged(bool bridged) { m_sentinel.setBridged(true); } QPolygonF toPolygon() const; private: SentinelSplineVertex m_sentinel; }; #endif scantailor-RELEASE_0_9_12_2/zones/EditableZoneSet.cpp000066400000000000000000000037431271170121200223510ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "EditableZoneSet.h.moc" EditableZoneSet::EditableZoneSet() { } void EditableZoneSet::setDefaultProperties(PropertySet const& props) { m_defaultProps = props; } void EditableZoneSet::addZone(EditableSpline::Ptr const& spline) { IntrusivePtr new_props(new PropertySet(m_defaultProps)); m_splineMap.insert(Map::value_type(spline, new_props)); } void EditableZoneSet::addZone(EditableSpline::Ptr const& spline, PropertySet const& props) { IntrusivePtr new_props(new PropertySet(props)); m_splineMap.insert(Map::value_type(spline, new_props)); } void EditableZoneSet::removeZone(EditableSpline::Ptr const& spline) { m_splineMap.erase(spline); } void EditableZoneSet::commit() { emit committed(); } IntrusivePtr EditableZoneSet::propertiesFor(EditableSpline::Ptr const& spline) { Map::iterator it(m_splineMap.find(spline)); if (it != m_splineMap.end()) { return it->second; } else { return IntrusivePtr(); } } IntrusivePtr EditableZoneSet::propertiesFor(EditableSpline::Ptr const& spline) const { Map::const_iterator it(m_splineMap.find(spline)); if (it != m_splineMap.end()) { return it->second; } else { return IntrusivePtr(); } } scantailor-RELEASE_0_9_12_2/zones/EditableZoneSet.h000066400000000000000000000061201271170121200220060ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef EDITABLE_ZONE_SET_H_ #define EDITABLE_ZONE_SET_H_ #include "EditableSpline.h" #include "PropertySet.h" #include "IntrusivePtr.h" #include #ifndef Q_MOC_RUN #include #include #include #endif #include class EditableZoneSet : public QObject { Q_OBJECT private: typedef std::map > Map; public: class const_iterator; class Zone { friend class EditableZoneSet::const_iterator; public: Zone() {} EditableSpline::Ptr const& spline() const { return m_iter->first; } IntrusivePtr const& properties() const { return m_iter->second; } private: explicit Zone(Map::const_iterator it) : m_iter(it) {} Map::const_iterator m_iter; }; class const_iterator : public boost::iterator_facade< const_iterator, Zone const, boost::forward_traversal_tag > { friend class EditableZoneSet; friend class boost::iterator_core_access; public: const_iterator() : m_zone() {} void increment() { ++m_zone.m_iter; } bool equal(const_iterator const& other) const { return m_zone.m_iter == other.m_zone.m_iter; } Zone const& dereference() const { return m_zone; } private: explicit const_iterator(Map::const_iterator it) : m_zone(it) {} Zone m_zone; }; typedef const_iterator iterator; EditableZoneSet(); const_iterator begin() const { return iterator(m_splineMap.begin()); } const_iterator end() const { return iterator(m_splineMap.end()); } PropertySet const& defaultProperties() const { return m_defaultProps; } void setDefaultProperties(PropertySet const& props); void addZone(EditableSpline::Ptr const& spline); void addZone(EditableSpline::Ptr const& spline, PropertySet const& props); void removeZone(EditableSpline::Ptr const& spline); void commit(); IntrusivePtr propertiesFor(EditableSpline::Ptr const& spline); IntrusivePtr propertiesFor(EditableSpline::Ptr const& spline) const; signals: void committed(); private: Map m_splineMap; 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<> struct is_noncopyable : public boost::mpl::true_ { }; } // namespace foreach } // namespace boost #endif scantailor-RELEASE_0_9_12_2/zones/SerializableSpline.cpp000066400000000000000000000046171271170121200231120ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "SerializableSpline.h" #include "EditableSpline.h" #include "SplineVertex.h" #include "XmlMarshaller.h" #include "XmlUnmarshaller.h" #include #include #include #include #include #ifndef Q_MOC_RUN #include #endif SerializableSpline::SerializableSpline(EditableSpline const& spline) { SplineVertex::Ptr vertex(spline.firstVertex()); for (; vertex; vertex = vertex->next(SplineVertex::NO_LOOP)) { m_points.push_back(vertex->point()); } } SerializableSpline::SerializableSpline(QDomElement const& el) { QString const 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())); } } QDomElement SerializableSpline::toXml(QDomDocument& doc, QString const& name) const { QDomElement el(doc.createElement(name)); QString const point_str("point"); XmlMarshaller marshaller(doc); BOOST_FOREACH(QPointF const& pt, m_points) { el.appendChild(marshaller.pointF(pt, point_str)); } return el; } SerializableSpline SerializableSpline::transformed(QTransform const& xform) const { SerializableSpline transformed(*this); BOOST_FOREACH(QPointF& pt, transformed.m_points) { pt = xform.map(pt); } return transformed; } SerializableSpline SerializableSpline::transformed( boost::function const& xform) const { SerializableSpline transformed(*this); BOOST_FOREACH(QPointF& pt, transformed.m_points) { pt = xform(pt); } return transformed; } scantailor-RELEASE_0_9_12_2/zones/SerializableSpline.h000066400000000000000000000027541271170121200225570ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SERIALIZABLE_SPLINE_H_ #define SERIALIZABLE_SPLINE_H_ #include #include #include #ifndef Q_MOC_RUN #include #endif class EditableSpline; class QTransform; class QDomDocument; class QDomElement; class QString; class SerializableSpline { public: SerializableSpline(EditableSpline const& spline); explicit SerializableSpline(QDomElement const& el); QDomElement toXml(QDomDocument& doc, QString const& name) const; SerializableSpline transformed(QTransform const& xform) const; SerializableSpline transformed( boost::function const& xform) const; QPolygonF toPolygon() const { return QPolygonF(m_points); } private: QVector m_points; }; #endif scantailor-RELEASE_0_9_12_2/zones/SplineSegment.cpp000066400000000000000000000022141271170121200220750ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "SplineSegment.h" #include SplineSegment::SplineSegment( SplineVertex::Ptr const& prev, SplineVertex::Ptr const& next) : prev(prev), next(next) { } SplineVertex::Ptr SplineSegment::splitAt(QPointF const& pt) { assert(isValid()); return prev->insertAfter(pt); } bool SplineSegment::isValid() const { return prev && next && prev->next(SplineVertex::LOOP_IF_BRIDGED) == next; } scantailor-RELEASE_0_9_12_2/zones/SplineSegment.h000066400000000000000000000024521271170121200215460ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SPLINE_SEGMENT_H_ #define SPLINE_SEGMENT_H_ #include "SplineVertex.h" #include #include class SplineSegment { public: SplineVertex::Ptr prev; SplineVertex::Ptr next; SplineSegment() {} SplineSegment(SplineVertex::Ptr const& prev, SplineVertex::Ptr const& next); SplineVertex::Ptr splitAt(QPointF const& pt); bool isValid() const; bool operator==(SplineSegment const& other) const { return prev == other.prev && next == other.next; } QLineF toLine() const { return QLineF(prev->point(), next->point()); } }; #endif scantailor-RELEASE_0_9_12_2/zones/SplineVertex.cpp000066400000000000000000000110461271170121200217530ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "SplineVertex.h" #include /*============================= SplineVertex ============================*/ SplineVertex::SplineVertex(SplineVertex* prev, SplineVertex* next) : m_pPrev(prev), m_ptrNext(next) { } void SplineVertex::remove() { // 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_pPrev->m_ptrNext->m_pPrev = m_pPrev; m_pPrev = 0; // This may or may not destroy this object, // depending on if there are other references to it. m_ptrNext.reset(); } bool SplineVertex::hasAtLeastSiblings(int const num) { int todo = num; for (SplineVertex::Ptr node(this); (node = node->next(LOOP)).get() != this; ) { if (--todo == 0) { return true; } } return false; } SplineVertex::Ptr SplineVertex::prev(Loop const loop) { return m_pPrev->thisOrPrevReal(loop); } SplineVertex::Ptr SplineVertex::next(Loop const loop) { return m_ptrNext->thisOrNextReal(loop); } SplineVertex::Ptr SplineVertex::insertBefore(QPointF const& pt) { SplineVertex::Ptr new_vertex(new RealSplineVertex(pt, m_pPrev, this)); m_pPrev->m_ptrNext = new_vertex; m_pPrev = new_vertex.get(); return new_vertex; } SplineVertex::Ptr SplineVertex::insertAfter(QPointF const& pt) { SplineVertex::Ptr new_vertex(new RealSplineVertex(pt, this, m_ptrNext.get())); m_ptrNext->m_pPrev = new_vertex.get(); m_ptrNext = new_vertex; return new_vertex; } /*========================= SentinelSplineVertex =======================*/ 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(); } } SplineVertex::Ptr SentinelSplineVertex::thisOrPrevReal(Loop const loop) { if (loop == LOOP || (loop == LOOP_IF_BRIDGED && m_bridged)) { return SplineVertex::Ptr(m_pPrev); } else { return SplineVertex::Ptr(); } } SplineVertex::Ptr SentinelSplineVertex::thisOrNextReal(Loop const loop) { if (loop == LOOP || (loop == LOOP_IF_BRIDGED && m_bridged)) { return m_ptrNext; } else { return SplineVertex::Ptr(); } } QPointF const SentinelSplineVertex::point() const { assert(!"Illegal call to SentinelSplineVertex::point()"); return QPointF(); } void SentinelSplineVertex::setPoint(QPointF const& pt) { assert(!"Illegal call to SentinelSplineVertex::setPoint()"); } void SentinelSplineVertex::remove() { assert(!"Illegal call to SentinelSplineVertex::remove()"); } SplineVertex::Ptr SentinelSplineVertex::firstVertex() const { if (m_ptrNext.get() == this) { return SplineVertex::Ptr(); } else { return m_ptrNext; } } SplineVertex::Ptr SentinelSplineVertex::lastVertex() const { if (m_pPrev == this) { return SplineVertex::Ptr(); } else { return SplineVertex::Ptr(m_pPrev); } } /*============================== RealSplineVertex ============================*/ RealSplineVertex::RealSplineVertex( QPointF const& pt, SplineVertex* prev, SplineVertex* next) : SplineVertex(prev, next), m_point(pt), m_refCounter(0) { } void RealSplineVertex::ref() const { ++m_refCounter; } void RealSplineVertex::unref() const { if (--m_refCounter == 0) { delete this; } } SplineVertex::Ptr RealSplineVertex::thisOrPrevReal(Loop) { return SplineVertex::Ptr(this); } SplineVertex::Ptr RealSplineVertex::thisOrNextReal(Loop loop) { return SplineVertex::Ptr(this); } QPointF const RealSplineVertex::point() const { return m_point; } void RealSplineVertex::setPoint(QPointF const& pt) { m_point = pt; } scantailor-RELEASE_0_9_12_2/zones/SplineVertex.h000066400000000000000000000073141271170121200214230ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SPLINE_VERTEX_H_ #define SPLINE_VERTEX_H_ #include "IntrusivePtr.h" #include "NonCopyable.h" #include class SplineVertex { public: enum Loop { LOOP, NO_LOOP, LOOP_IF_BRIDGED }; typedef IntrusivePtr Ptr; SplineVertex(SplineVertex* prev, SplineVertex* next); virtual ~SplineVertex() {} /** * 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 {} /** * \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; virtual QPointF const point() const = 0; virtual void setPoint(QPointF const& pt) = 0; virtual void remove(); bool hasAtLeastSiblings(int num); SplineVertex::Ptr prev(Loop loop); SplineVertex::Ptr next(Loop loop); SplineVertex::Ptr insertBefore(QPointF const& pt); SplineVertex::Ptr insertAfter(QPointF const& 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; }; class SentinelSplineVertex : public SplineVertex { DECLARE_NON_COPYABLE(SentinelSplineVertex) public: SentinelSplineVertex(); virtual ~SentinelSplineVertex(); virtual SplineVertex::Ptr thisOrPrevReal(Loop loop); virtual SplineVertex::Ptr thisOrNextReal(Loop loop); virtual QPointF const point() const; virtual void setPoint(QPointF const& pt); virtual void remove(); SplineVertex::Ptr firstVertex() const; SplineVertex::Ptr lastVertex() const; bool bridged() const { return m_bridged; } void setBridged(bool bridged) { m_bridged = bridged; } private: bool m_bridged; }; class RealSplineVertex : public SplineVertex { DECLARE_NON_COPYABLE(RealSplineVertex) public: RealSplineVertex(QPointF const& pt, SplineVertex* prev, SplineVertex* next); virtual void ref() const; virtual void unref() const; virtual SplineVertex::Ptr thisOrPrevReal(Loop loop); virtual SplineVertex::Ptr thisOrNextReal(Loop loop); virtual QPointF const point() const; virtual void setPoint(QPointF const& pt); private: QPointF m_point; mutable int m_refCounter; }; #endif scantailor-RELEASE_0_9_12_2/zones/Zone.cpp000066400000000000000000000032231271170121200202340ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Zone.h" #include #include #include Zone::Zone(SerializableSpline const& spline, PropertySet const& props) : m_spline(spline), m_props(props) { } Zone::Zone(QDomElement const& el, PropertyFactory const& prop_factory) : m_spline(el.namedItem("spline").toElement()), m_props(el.namedItem("properties").toElement(), prop_factory) { } QDomElement Zone::toXml(QDomDocument& doc, QString const& name) const { QDomElement el(doc.createElement(name)); el.appendChild(m_spline.toXml(doc, "spline")); el.appendChild(m_props.toXml(doc, "properties")); return el; } bool Zone::isValid() const { QPolygonF const& 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; } } scantailor-RELEASE_0_9_12_2/zones/Zone.h000066400000000000000000000030521271170121200177010ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef ZONE_H_ #define ZONE_H_ #include "SerializableSpline.h" #include "IntrusivePtr.h" #include "PropertySet.h" class PropertyFactory; class QDomDocument; class QDomElement; class QString; class Zone { // Member-wise copying is OK, but that will produce a partly shallow copy. public: Zone(SerializableSpline const& spline, PropertySet const& props = PropertySet()); Zone(QDomElement const& el, PropertyFactory const& prop_factory); QDomElement toXml(QDomDocument& doc, QString const& name) const; SerializableSpline const& spline() const { return m_spline; } PropertySet& properties() { return m_props; } PropertySet const& properties() const { return m_props; } bool isValid() const; private: SerializableSpline m_spline; PropertySet m_props; }; #endif scantailor-RELEASE_0_9_12_2/zones/ZoneContextMenuInteraction.cpp000066400000000000000000000220431271170121200246270ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ZoneContextMenuInteraction.h" #include "ZoneContextMenuInteraction.h.moc" #include "ZoneInteractionContext.h" #include "ImageViewBase.h" #include "EditableZoneSet.h" #include "QtSignalForwarder.h" #include #include #include #include #include #include #include #include #include #include #include #ifndef Q_MOC_RUN #include #include #include #endif #include #include class ZoneContextMenuInteraction::OrderByArea { public: bool operator()(EditableZoneSet::Zone const& lhs, EditableZoneSet::Zone const& rhs) const { QRectF const lhs_bbox(lhs.spline()->toPolygon().boundingRect()); QRectF const rhs_bbox(rhs.spline()->toPolygon().boundingRect()); qreal const lhs_area = lhs_bbox.width() * lhs_bbox.height(); qreal const 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) ); } ZoneContextMenuInteraction* ZoneContextMenuInteraction::create( ZoneInteractionContext& context, InteractionState& interaction, MenuCustomizer const& menu_customizer) { std::vector selectable_zones(zonesUnderMouse(context)); if (selectable_zones.empty()) { return 0; } else { return new ZoneContextMenuInteraction( context, interaction, menu_customizer, selectable_zones ); } } std::vector ZoneContextMenuInteraction::zonesUnderMouse(ZoneInteractionContext& context) { QTransform const from_screen(context.imageView().widgetToImage()); QPointF const 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; BOOST_FOREACH(EditableZoneSet::Zone const& zone, context.zones()) { QPainterPath path; path.setFillRule(Qt::WindingFill); path.addPolygon(zone.spline()->toPolygon()); if (path.contains(image_mouse_pos)) { selectable_zones.push_back(Zone(zone)); } } return selectable_zones; } ZoneContextMenuInteraction::ZoneContextMenuInteraction( ZoneInteractionContext& context, InteractionState& interaction, MenuCustomizer const& menu_customizer, std::vector& selectable_zones) : m_rContext(context), m_ptrMenu(new QMenu(&context.imageView())), m_highlightedZoneIdx(-1), m_menuItemTriggered(false) { #ifdef Q_WS_MAC m_extraDelaysDone = 0; #endif m_selectableZones.swap(selectable_zones); std::sort(m_selectableZones.begin(), m_selectableZones.end(), OrderByArea()); interaction.capture(m_interaction); int h = 20; int const h_step = 65; int const s = 255 * 64 / 100; int const v = 255 * 96 / 100; int const alpha = 150; QColor color; QSignalMapper* hover_map = new QSignalMapper(this); connect(hover_map, SIGNAL(mapped(int)), SLOT(highlightItem(int))); QPixmap pixmap; std::vector::iterator it(m_selectableZones.begin()); std::vector::iterator const 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(); if (m_selectableZones.size() > 1) { pixmap = QPixmap(16, 16); color.setAlpha(255); pixmap.fill(color); } StandardMenuItems const std_items( propertiesMenuItemFor(*it), deleteMenuItemFor(*it) ); BOOST_FOREACH(ZoneContextMenuItem const& 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() ) ); hover_map->setMapping(action, i); connect(action, SIGNAL(hovered()), hover_map, SLOT(map())); } m_ptrMenu->addSeparator(); } // 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()); } ZoneContextMenuInteraction::~ZoneContextMenuInteraction() { } void ZoneContextMenuInteraction::onPaint(QPainter& painter, InteractionState const&) { painter.setWorldMatrixEnabled(false); painter.setRenderHint(QPainter::Antialiasing); if (m_highlightedZoneIdx >= 0) { QTransform const to_screen(m_rContext.imageView().imageToWidget()); Zone const& zone = m_selectableZones[m_highlightedZoneIdx]; m_visualizer.drawSpline(painter, to_screen, zone.spline()); } } void ZoneContextMenuInteraction::menuAboutToHide() { if (m_menuItemTriggered) { return; } #ifdef Q_WS_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; } #endif InteractionHandler* next_handler = m_rContext.createDefaultInteraction(); if (next_handler) { makePeerPreceeder(*next_handler); } unlink(); m_rContext.imageView().update(); deleteLater(); } void ZoneContextMenuInteraction::menuItemTriggered( InteractionState& interaction, ZoneContextMenuItem::Callback const& callback) { m_menuItemTriggered = true; m_visualizer.switchToStrokeMode(); InteractionHandler* next_handler = callback(interaction); if (next_handler) { makePeerPreceeder(*next_handler); } unlink(); m_rContext.imageView().update(); deleteLater(); } InteractionHandler* ZoneContextMenuInteraction::propertiesRequest(EditableZoneSet::Zone const& zone) { m_rContext.showPropertiesCommand(zone); return m_rContext.createDefaultInteraction(); } InteractionHandler* ZoneContextMenuInteraction::deleteRequest(EditableZoneSet::Zone const& zone) { QMessageBox::StandardButton const btn = QMessageBox::question( &m_rContext.imageView(), tr("Delete confirmation"), tr("Really delete this zone?"), QMessageBox::Yes|QMessageBox::No ); if (btn == QMessageBox::Yes) { m_rContext.zones().removeZone(zone.spline()); m_rContext.zones().commit(); } return m_rContext.createDefaultInteraction(); } ZoneContextMenuItem ZoneContextMenuInteraction::deleteMenuItemFor( EditableZoneSet::Zone const& zone) { return ZoneContextMenuItem( tr("Delete"), boost::bind(&ZoneContextMenuInteraction::deleteRequest, this, zone) ); } ZoneContextMenuItem ZoneContextMenuInteraction::propertiesMenuItemFor( EditableZoneSet::Zone const& zone) { return ZoneContextMenuItem( tr("Properties"), boost::bind(&ZoneContextMenuInteraction::propertiesRequest, this, zone) ); } void ZoneContextMenuInteraction::highlightItem(int const 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(); } std::vector ZoneContextMenuInteraction::defaultMenuCustomizer( EditableZoneSet::Zone const& zone, StandardMenuItems const& std_items) { std::vector items; items.reserve(2); items.push_back(std_items.propertiesItem); items.push_back(std_items.deleteItem); return items; } /*========================== StandardMenuItem =========================*/ ZoneContextMenuInteraction::StandardMenuItems::StandardMenuItems( ZoneContextMenuItem const& properties_item, ZoneContextMenuItem const& delete_item) : propertiesItem(properties_item), deleteItem(delete_item) { } /*============================= Visualizer ============================*/ void ZoneContextMenuInteraction::Visualizer::switchToFillMode(QColor const& color) { m_color = color; } void ZoneContextMenuInteraction::Visualizer::switchToStrokeMode() { m_color = QColor(); } void ZoneContextMenuInteraction::Visualizer::prepareForSpline( QPainter& painter, EditableSpline::Ptr const& spline) { BasicSplineVisualizer::prepareForSpline(painter, spline); if (m_color.isValid()) { painter.setBrush(m_color); } } scantailor-RELEASE_0_9_12_2/zones/ZoneContextMenuInteraction.h000066400000000000000000000076451271170121200243070ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #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 "IntrusivePtr.h" #include "BasicSplineVisualizer.h" #include #include #include #ifndef Q_MOC_RUN #include #endif #include #include #include class ZoneInteractionContext; class QPainter; class QMenu; class ZoneContextMenuInteraction : public QObject, public InteractionHandler { Q_OBJECT public: struct StandardMenuItems { ZoneContextMenuItem propertiesItem; ZoneContextMenuItem deleteItem; StandardMenuItems( ZoneContextMenuItem const& properties_item, ZoneContextMenuItem const& delete_item); }; typedef boost::function< std::vector( EditableZoneSet::Zone const&, StandardMenuItems const& ) > MenuCustomizer; /** * \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, MenuCustomizer const& menu_customizer); virtual ~ZoneContextMenuInteraction(); protected: class Zone : public EditableZoneSet::Zone { public: QColor color; Zone(EditableZoneSet::Zone const& zone) : EditableZoneSet::Zone(zone) {} }; static std::vector zonesUnderMouse(ZoneInteractionContext& context); ZoneContextMenuInteraction( ZoneInteractionContext& context, InteractionState& interaction, MenuCustomizer const& menu_customizer, std::vector& selectable_zones); ZoneInteractionContext& context() { return m_rContext; } private slots: void menuAboutToHide(); void highlightItem(int zone_idx); private: class OrderByArea; class Visualizer : public BasicSplineVisualizer { public: void switchToFillMode(QColor const& color); void switchToStrokeMode(); virtual void prepareForSpline(QPainter& painter, EditableSpline::Ptr const& spline); private: QColor m_color; }; static std::vector defaultMenuCustomizer( EditableZoneSet::Zone const& zone, StandardMenuItems const& std_items); virtual void onPaint(QPainter& painter, InteractionState const& interaction); void menuItemTriggered(InteractionState& interaction, ZoneContextMenuItem::Callback const& callback); InteractionHandler* deleteRequest(EditableZoneSet::Zone const& zone); InteractionHandler* propertiesRequest(EditableZoneSet::Zone const& zone); ZoneContextMenuItem propertiesMenuItemFor(EditableZoneSet::Zone const& zone); ZoneContextMenuItem deleteMenuItemFor(EditableZoneSet::Zone const& zone); ZoneInteractionContext& m_rContext; std::vector m_selectableZones; InteractionState::Captor m_interaction; Visualizer m_visualizer; std::auto_ptr m_ptrMenu; int m_highlightedZoneIdx; bool m_menuItemTriggered; #ifdef Q_WS_MAC int m_extraDelaysDone; #endif }; #endif scantailor-RELEASE_0_9_12_2/zones/ZoneContextMenuItem.h000066400000000000000000000035631271170121200227210ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef ZONE_CONTEXT_MENU_ITEM_H_ #define ZONE_CONTEXT_MENU_ITEM_H_ #include #ifndef Q_MOC_RUN #include #endif 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(QString const& label, Callback const& callback) : m_label(label), m_callback(callback) {} QString const& label() const { return m_label; } Callback const& callback() const { return m_callback; } private: QString m_label; Callback m_callback; }; #endif scantailor-RELEASE_0_9_12_2/zones/ZoneCreationInteraction.cpp000066400000000000000000000154411271170121200241260ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ZoneCreationInteraction.h" #include "ZoneInteractionContext.h" #include "EditableZoneSet.h" #include "ImageViewBase.h" #include #include #include #include #include #include #include #include #include #include #ifndef Q_MOC_RUN #include #endif 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) { QPointF const screen_mouse_pos( m_rContext.imageView().mapFromGlobal(QCursor::pos()) + QPointF(0.5, 0.5) ); QTransform const from_screen(m_rContext.imageView().widgetToImage()); m_nextVertexImagePos = from_screen.map(screen_mouse_pos); makeLastFollower(m_dragHandler); m_dragHandler.makeFirstFollower(m_dragWatcher); makeLastFollower(m_zoomHandler); interaction.capture(m_interaction); m_ptrSpline->appendVertex(m_nextVertexImagePos); updateStatusTip(); } void ZoneCreationInteraction::onPaint(QPainter& painter, InteractionState const& interaction) { painter.setWorldMatrixEnabled(false); painter.setRenderHint(QPainter::Antialiasing); QTransform const to_screen(m_rContext.imageView().imageToWidget()); QTransform const 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); for (EditableSpline::SegmentIterator it(*m_ptrSpline); it.hasNext(); ) { SplineSegment const segment(it.next()); QLineF const 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); } } QLineF const line( to_screen.map(QLineF(m_ptrSpline->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() ); } 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(); } } void ZoneCreationInteraction::onMouseReleaseEvent(QMouseEvent* event, InteractionState& interaction) { if (event->button() != Qt::LeftButton) { return; } if (m_dragWatcher.haveSignificantDrag()) { return; } QTransform const to_screen(m_rContext.imageView().imageToWidget()); QTransform const from_screen(m_rContext.imageView().widgetToImage()); QPointF const screen_mouse_pos(event->pos() + QPointF(0.5, 0.5)); QPointF const image_mouse_pos(from_screen.map(screen_mouse_pos)); 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 to close to the previous one. Proximity const prox(screen_mouse_pos, m_ptrSpline->lastVertex()->point()); if (prox > interaction.proximityThreshold()) { m_ptrSpline->appendVertex(image_mouse_pos); updateStatusTip(); } } event->accept(); } void ZoneCreationInteraction::onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) { QPointF const screen_mouse_pos(event->pos() + QPointF(0.5, 0.5)); QTransform const to_screen(m_rContext.imageView().imageToWidget()); QTransform const from_screen(m_rContext.imageView().widgetToImage()); m_nextVertexImagePos = from_screen.map(screen_mouse_pos); QPointF const last(to_screen.map(m_ptrSpline->lastVertex()->point())); if (Proximity(last, screen_mouse_pos) <= interaction.proximityThreshold()) { m_nextVertexImagePos = m_ptrSpline->lastVertex()->point(); } else if (m_ptrSpline->hasAtLeastSegments(2)) { QPointF const first(to_screen.map(m_ptrSpline->firstVertex()->point())); if (Proximity(first, screen_mouse_pos) <= interaction.proximityThreshold()) { m_nextVertexImagePos = m_ptrSpline->firstVertex()->point(); updateStatusTip(); } } m_rContext.imageView().update(); } void ZoneCreationInteraction::updateStatusTip() { QString tip; 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("Zones need to have at least 3 points. ESC to cancel."); } m_interaction.setInteractionStatusTip(tip); } scantailor-RELEASE_0_9_12_2/zones/ZoneCreationInteraction.h000066400000000000000000000051561271170121200235750ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef ZONE_CREATION_INTERACTION_H_ #define ZONE_CREATION_INTERACTION_H_ #include "InteractionHandler.h" #include "InteractionState.h" #include "DragHandler.h" #include "DragWatcher.h" #include "ZoomHandler.h" #include "BasicSplineVisualizer.h" #include "EditableSpline.h" #include #include #include class ZoneInteractionContext; class ZoneCreationInteraction : public InteractionHandler { Q_DECLARE_TR_FUNCTIONS(ZoneCreationInteraction) public: ZoneCreationInteraction( ZoneInteractionContext& context, InteractionState& interaction); protected: ZoneInteractionContext& context() { return m_rContext; } virtual void onPaint(QPainter& painter, InteractionState const& interaction); virtual void onKeyPressEvent(QKeyEvent* event, InteractionState& interaction); virtual void onMouseReleaseEvent(QMouseEvent* event, InteractionState& interaction); virtual void onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction); 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; }; #endif scantailor-RELEASE_0_9_12_2/zones/ZoneDefaultInteraction.cpp000066400000000000000000000206451271170121200237500ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ZoneDefaultInteraction.h" #include "ZoneInteractionContext.h" #include "EditableZoneSet.h" #include "ImageViewBase.h" #include #include #include #include #include #include #include #include #include #include #ifndef Q_MOC_RUN #include #endif #include #include 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.")); m_segmentProximity.setProximityStatusTip(tr("Click to create a new vertex here.")); m_zoneAreaProximity.setProximityStatusTip(tr("Right click to edit zone properties.")); m_rContext.imageView().interactionState().setDefaultStatusTip( tr("Click to start creating a new picture zone.") ); } void ZoneDefaultInteraction::onPaint(QPainter& painter, InteractionState const& interaction) { painter.setWorldMatrixEnabled(false); painter.setRenderHint(QPainter::Antialiasing); QTransform const to_screen(m_rContext.imageView().imageToWidget()); BOOST_FOREACH(EditableZoneSet::Zone const& zone, m_rContext.zones()) { EditableSpline::Ptr const& 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()); QPointF const prev(to_screen.map(m_ptrNearestVertex->prev(SplineVertex::LOOP)->point())); QPointF const pt(to_screen.map(m_ptrNearestVertex->point())); QPointF const 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. QPointF const screen_vertex(to_screen.map(m_ptrNearestVertex->point())); m_visualizer.drawVertex(painter, screen_vertex, m_visualizer.highlightBrightColor()); } else if (interaction.proximityLeader(m_segmentProximity)) { QLineF const 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()); } } void ZoneDefaultInteraction::onProximityUpdate(QPointF const& mouse_pos, InteractionState& interaction) { m_screenMousePos = mouse_pos; QTransform const to_screen(m_rContext.imageView().imageToWidget()); QTransform const from_screen(m_rContext.imageView().widgetToImage()); QPointF const image_mouse_pos(from_screen.map(mouse_pos)); m_ptrNearestVertex.reset(); m_ptrNearestVertexSpline.reset(); m_nearestSegment = SplineSegment(); m_ptrNearestSegmentSpline.reset(); Proximity best_vertex_proximity; Proximity best_segment_proximity; bool has_zone_under_mouse = false; BOOST_FOREACH(EditableZoneSet::Zone const& zone, m_rContext.zones()) { EditableSpline::Ptr const& spline = zone.spline(); if (!has_zone_under_mouse) { QPainterPath path; path.setFillRule(Qt::WindingFill); path.addPolygon(spline->toPolygon()); has_zone_under_mouse = path.contains(image_mouse_pos); } // Process vertices. for (SplineVertex::Ptr vert(spline->firstVertex()); vert; vert = vert->next(SplineVertex::NO_LOOP)) { Proximity const 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(); ) { SplineSegment const segment(it.next()); QLineF const line(to_screen.map(segment.toLine())); QPointF point_on_segment; Proximity const 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; } } } interaction.updateProximity(m_vertexProximity, best_vertex_proximity, 1); interaction.updateProximity(m_segmentProximity, best_segment_proximity, 0); if (has_zone_under_mouse) { Proximity const zone_area_proximity(std::min(best_vertex_proximity, best_segment_proximity)); interaction.updateProximity(m_zoneAreaProximity, zone_area_proximity, -1, zone_area_proximity); } } void ZoneDefaultInteraction::onMousePressEvent(QMouseEvent* event, InteractionState& interaction) { if (interaction.captured()) { return; } if (event->button() != Qt::LeftButton) { return; } if (interaction.proximityLeader(m_vertexProximity)) { makePeerPreceeder( *m_rContext.createVertexDragInteraction( interaction, m_ptrNearestVertexSpline, m_ptrNearestVertex ) ); delete this; event->accept(); } else if (interaction.proximityLeader(m_segmentProximity)) { QTransform const 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(); } } 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; } makePeerPreceeder(*m_rContext.createZoneCreationInteraction(interaction)); delete this; event->accept(); } void ZoneDefaultInteraction::onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) { QTransform const to_screen(m_rContext.imageView().imageToWidget()); m_screenMousePos = to_screen.map(event->pos() + QPointF(0.5, 0.5)); m_rContext.imageView().update(); } void ZoneDefaultInteraction::onContextMenuEvent(QContextMenuEvent* event, InteractionState& interaction) { event->accept(); InteractionHandler* cm_interaction = m_rContext.createContextMenuInteraction(interaction); if (!cm_interaction) { return; } makePeerPreceeder(*cm_interaction); delete this; } scantailor-RELEASE_0_9_12_2/zones/ZoneDefaultInteraction.h000066400000000000000000000055421271170121200234140ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef ZONE_DEFAULT_INTERACTION_H_ #define ZONE_DEFAULT_INTERACTION_H_ #include "InteractionHandler.h" #include "InteractionState.h" #include "DragHandler.h" #include "DragWatcher.h" #include "BasicSplineVisualizer.h" #include "EditableSpline.h" #include "SplineVertex.h" #include "SplineSegment.h" #include #include class ZoneInteractionContext; class ZoneDefaultInteraction : public InteractionHandler { Q_DECLARE_TR_FUNCTIONS(ZoneDefaultInteraction) public: ZoneDefaultInteraction(ZoneInteractionContext& context); protected: ZoneInteractionContext& context() { return m_rContext; } virtual void onPaint(QPainter& painter, InteractionState const& interaction); virtual void onProximityUpdate(QPointF const& mouse_pos, InteractionState& interaction); virtual void onMousePressEvent(QMouseEvent* event, InteractionState& interaction); virtual void onMouseReleaseEvent(QMouseEvent* event, InteractionState& interaction); virtual void onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction); virtual void onContextMenuEvent(QContextMenuEvent* event, InteractionState& interaction); private: ZoneInteractionContext& m_rContext; BasicSplineVisualizer m_visualizer; InteractionState::Captor m_vertexProximity; InteractionState::Captor m_segmentProximity; InteractionState::Captor m_zoneAreaProximity; QPointF m_screenMousePos; /** * 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; // These are valid if m_vertexProximity is the proximity leader. SplineVertex::Ptr m_ptrNearestVertex; EditableSpline::Ptr m_ptrNearestVertexSpline; // These are valid if m_segmentProximity is the proximity leader. SplineSegment m_nearestSegment; EditableSpline::Ptr m_ptrNearestSegmentSpline; QPointF m_screenPointOnSegment; }; #endif scantailor-RELEASE_0_9_12_2/zones/ZoneInteractionContext.cpp000066400000000000000000000046551271170121200240130ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ZoneInteractionContext.h" #include "ZoneDefaultInteraction.h" #include "ZoneCreationInteraction.h" #include "ZoneVertexDragInteraction.h" #include "ZoneContextMenuInteraction.h" #ifndef Q_MOC_RUN #include #endif 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_contextMenuInteractionCreator( boost::bind(&ZoneInteractionContext::createStdContextMenuInteraction, this, _1) ), m_showPropertiesCommand(&ZoneInteractionContext::showPropertiesStub) { } ZoneInteractionContext::~ZoneInteractionContext() { } InteractionHandler* ZoneInteractionContext::createStdDefaultInteraction() { return new ZoneDefaultInteraction(*this); } InteractionHandler* ZoneInteractionContext::createStdZoneCreationInteraction(InteractionState& interaction) { return new ZoneCreationInteraction(*this, interaction); } InteractionHandler* ZoneInteractionContext::createStdVertexDragInteraction( InteractionState& interaction, EditableSpline::Ptr const& spline, SplineVertex::Ptr const& vertex) { return new ZoneVertexDragInteraction(*this, interaction, spline, vertex); } InteractionHandler* ZoneInteractionContext::createStdContextMenuInteraction(InteractionState& interaction) { return ZoneContextMenuInteraction::create(*this, interaction); } scantailor-RELEASE_0_9_12_2/zones/ZoneInteractionContext.h000066400000000000000000000106431271170121200234520ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef ZONE_INTERACTION_CONTEXT_H_ #define ZONE_INTERACTION_CONTEXT_H_ #include "EditableSpline.h" #include "SplineVertex.h" #include "EditableZoneSet.h" #ifndef Q_MOC_RUN #include #endif class InteractionHandler; class InteractionState; class ImageViewBase; class EditableZoneSet; class ZoneInteractionContext { public: typedef boost::function< InteractionHandler* () > DefaultInteractionCreator; typedef boost::function< InteractionHandler* (InteractionState& interaction) > ZoneCreationInteractionCreator; typedef boost::function< InteractionHandler* ( InteractionState& interaction, EditableSpline::Ptr const& spline, SplineVertex::Ptr const& vertex ) > VertexDragInteractionCreator; typedef boost::function< InteractionHandler* (InteractionState& interaction) > ContextMenuInteractionCreator; typedef boost::function< void (EditableZoneSet::Zone const& zone) > 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(DefaultInteractionCreator const& creator) { m_defaultInteractionCreator = creator; } virtual InteractionHandler* createZoneCreationInteraction(InteractionState& interaction) { return m_zoneCreationInteractionCreator(interaction); } void setZoneCreationInteractionCreator(ZoneCreationInteractionCreator const& creator) { m_zoneCreationInteractionCreator = creator; } virtual InteractionHandler* createVertexDragInteraction( InteractionState& interaction, EditableSpline::Ptr const& spline, SplineVertex::Ptr const& vertex) { return m_vertexDragInteractionCreator(interaction, spline, vertex); } void setVertexDragInteractionCreator(VertexDragInteractionCreator const& creator) { m_vertexDragInteractionCreator = 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(ContextMenuInteractionCreator const& creator) { m_contextMenuInteractionCreator = creator; } virtual void showPropertiesCommand(EditableZoneSet::Zone const& zone) { m_showPropertiesCommand(zone); } void setShowPropertiesCommand(ShowPropertiesCommand const& 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, EditableSpline::Ptr const& spline, SplineVertex::Ptr const& vertex); /** * Creates an instance of ZoneContextMenuInteraction. May return null. */ InteractionHandler* createStdContextMenuInteraction(InteractionState& interaction); static void showPropertiesStub(EditableZoneSet::Zone const&) {} ImageViewBase& m_rImageView; EditableZoneSet& m_rZones; DefaultInteractionCreator m_defaultInteractionCreator; ZoneCreationInteractionCreator m_zoneCreationInteractionCreator; VertexDragInteractionCreator m_vertexDragInteractionCreator; ContextMenuInteractionCreator m_contextMenuInteractionCreator; ShowPropertiesCommand m_showPropertiesCommand; }; #endif scantailor-RELEASE_0_9_12_2/zones/ZoneSet.cpp000066400000000000000000000030671271170121200207160ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ZoneSet.h" #include #include #include #include #ifndef Q_MOC_RUN #include #endif ZoneSet::ZoneSet(QDomElement const& el, PropertyFactory const& prop_factory) { QString const zone_str("zone"); QDomNode node(el.firstChild()); for (; !node.isNull(); node = node.nextSibling()) { if (!node.isElement()) { continue; } if (node.nodeName() != zone_str) { continue; } Zone const zone(node.toElement(), prop_factory); if (zone.isValid()) { m_zones.push_back(zone); } } } QDomElement ZoneSet::toXml(QDomDocument& doc, QString const& name) const { QString const zone_str("zone"); QDomElement el(doc.createElement(name)); BOOST_FOREACH(Zone const& zone, m_zones) { el.appendChild(zone.toXml(doc, zone_str)); } return el; } scantailor-RELEASE_0_9_12_2/zones/ZoneSet.h000066400000000000000000000037411271170121200203620ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef ZONE_SET_H_ #define ZONE_SET_H_ #include "Zone.h" #include #include class PropertyFactory; class QDomDocument; class QDomElement; class QString; class ZoneSet { public: class const_iterator : public boost::iterator_facade< const_iterator, Zone const, boost::forward_traversal_tag > { friend class ZoneSet; friend class boost::iterator_core_access; public: const_iterator() {} void increment() { ++m_it; } bool equal(const_iterator const& other) const { return m_it == other.m_it; } Zone const& dereference() const { return *m_it; } private: explicit const_iterator(std::list::const_iterator it) : m_it(it) {} std::list::const_iterator m_it; }; typedef const_iterator iterator; ZoneSet() {} ZoneSet(QDomElement const& el, PropertyFactory const& prop_factory); virtual ~ZoneSet() {} QDomElement toXml(QDomDocument& doc, QString const& name) const; bool empty() const { return m_zones.empty(); } void add(Zone const& zone) { m_zones.push_back(zone); } const_iterator begin() const { return const_iterator(m_zones.begin()); } const_iterator end() const { return const_iterator(m_zones.end()); } private: std::list m_zones; }; #endif scantailor-RELEASE_0_9_12_2/zones/ZoneVertexDragInteraction.cpp000066400000000000000000000122551271170121200244350ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ZoneVertexDragInteraction.h" #include "ZoneInteractionContext.h" #include "EditableZoneSet.h" #include "ImageViewBase.h" #include #include #include #include #include #include #include #ifndef Q_MOC_RUN #include #endif ZoneVertexDragInteraction::ZoneVertexDragInteraction( ZoneInteractionContext& context, InteractionState& interaction, EditableSpline::Ptr const& spline, SplineVertex::Ptr const& vertex) : m_rContext(context), m_ptrSpline(spline), m_ptrVertex(vertex) { QPointF const screen_mouse_pos( m_rContext.imageView().mapFromGlobal(QCursor::pos()) + QPointF(0.5, 0.5) ); QTransform const to_screen(m_rContext.imageView().imageToWidget()); m_dragOffset = to_screen.map(vertex->point()) - screen_mouse_pos; interaction.capture(m_interaction); checkProximity(interaction); } void ZoneVertexDragInteraction::onPaint(QPainter& painter, InteractionState const& interaction) { painter.setWorldMatrixEnabled(false); painter.setRenderHint(QPainter::Antialiasing); QTransform const to_screen(m_rContext.imageView().imageToWidget()); BOOST_FOREACH(EditableZoneSet::Zone const& zone, m_rContext.zones()) { EditableSpline::Ptr const& 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); } 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); painter.setBrush(Qt::NoBrush); QPointF const pt(to_screen.map(m_ptrVertex->point())); QPointF const prev(to_screen.map(m_ptrVertex->prev(SplineVertex::LOOP)->point())); QPointF const next(to_screen.map(m_ptrVertex->next(SplineVertex::LOOP)->point())); 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); m_visualizer.drawVertex( painter, to_screen.map(m_ptrVertex->point()), m_visualizer.highlightBrightColor() ); } 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; } } void ZoneVertexDragInteraction::onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) { QTransform const from_screen(m_rContext.imageView().widgetToImage()); m_ptrVertex->setPoint(from_screen.map(event->pos() + QPointF(0.5, 0.5) + m_dragOffset)); checkProximity(interaction); m_rContext.imageView().update(); } void ZoneVertexDragInteraction::checkProximity(InteractionState const& interaction) { bool can_merge = false; if (m_ptrVertex->hasAtLeastSiblings(3)) { QTransform const to_screen(m_rContext.imageView().imageToWidget()); QPointF const origin(to_screen.map(m_ptrVertex->point())); QPointF const prev(m_ptrVertex->prev(SplineVertex::LOOP)->point()); Proximity const prox_prev(origin, to_screen.map(prev)); QPointF const next(m_ptrVertex->next(SplineVertex::LOOP)->point()); Proximity const 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 (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.")); } } scantailor-RELEASE_0_9_12_2/zones/ZoneVertexDragInteraction.h000066400000000000000000000035251271170121200241020ustar00rootroot00000000000000/* Scan Tailor - Interactive post-processing tool for scanned pages. Copyright (C) 2007-2009 Joseph Artsimovich 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. 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef ZONE_VERTEX_DRAG_INTERACTION_H_ #define ZONE_VERTEX_DRAG_INTERACTION_H_ #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, EditableSpline::Ptr const& spline, SplineVertex::Ptr const& vertex); protected: virtual void onPaint(QPainter& painter, InteractionState const& interaction); virtual void onMouseReleaseEvent(QMouseEvent* event, InteractionState& interaction); virtual void onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction); private: void checkProximity(InteractionState const& interaction); ZoneInteractionContext& m_rContext; EditableSpline::Ptr m_ptrSpline; SplineVertex::Ptr m_ptrVertex; InteractionState::Captor m_interaction; BasicSplineVisualizer m_visualizer; QPointF m_dragOffset; }; #endif