diff --git a/3rdparty/ext_expat/CMakeLists.txt b/3rdparty/ext_expat/CMakeLists.txt index 517ec1308b..d651e4f1b6 100644 --- a/3rdparty/ext_expat/CMakeLists.txt +++ b/3rdparty/ext_expat/CMakeLists.txt @@ -1,48 +1,48 @@ SET(PREFIX_ext_expat "${EXTPREFIX}" ) -if (WIN32) +if (MINGW) ExternalProject_Add( ext_expat DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://github.com/libexpat/libexpat/releases/download/R_2_2_9/expat-2.2.9.tar.gz URL_MD5 077b953cc38df8fed78e92841cc35443 PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/expat-2.2.9-b9bd355.diff INSTALL_DIR ${PREFIX_ext_expat} SOURCE_SUBDIR expat CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_expat} -DBUILD_tests=OFF -DBUILD_examples=OFF -DBUILD_tools=OFF -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} UPDATE_COMMAND "" DEPENDS ext_patch ) elseif (ANDROID) ExternalProject_Add( ext_expat DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://github.com/libexpat/libexpat/releases/download/R_2_2_9/expat-2.2.9.tar.gz URL_MD5 077b953cc38df8fed78e92841cc35443 INSTALL_DIR ${PREFIX_ext_expat} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_expat} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} /expat BUILD_COMMAND make INSTALL_COMMAND make install UPDATE_COMMAND "" ) else() ExternalProject_Add( ext_expat DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://github.com/libexpat/libexpat/releases/download/R_2_2_9/expat-2.2.9.tar.gz URL_MD5 077b953cc38df8fed78e92841cc35443 INSTALL_DIR ${PREFIX_ext_expat} CONFIGURE_COMMAND ${CMAKE_COMMAND} -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_expat} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} BUILD_COMMAND make INSTALL_COMMAND make install UPDATE_COMMAND "" ) endif() diff --git a/libs/global/kis_signal_compressor_with_param.cpp b/libs/global/kis_signal_compressor_with_param.cpp index 7da2c1488b..3d934c46a5 100644 --- a/libs/global/kis_signal_compressor_with_param.cpp +++ b/libs/global/kis_signal_compressor_with_param.cpp @@ -1,20 +1,21 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_signal_compressor_with_param.h" +#include "kis_signal_compressor_with_param.moc" diff --git a/libs/global/kis_signal_compressor_with_param.h b/libs/global/kis_signal_compressor_with_param.h index a6882bc0f5..a165aba938 100644 --- a/libs/global/kis_signal_compressor_with_param.h +++ b/libs/global/kis_signal_compressor_with_param.h @@ -1,140 +1,173 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_SIGNAL_COMPRESSOR_WITH_PARAM_H #define __KIS_SIGNAL_COMPRESSOR_WITH_PARAM_H #include #include /** * A special class that converts a Qt signal into a std::function call. * - * Example: + * Usage: * - * std::function destinationFunctionCall(std::bind(someNiceFunc, firstParam, secondParam)); - * SignalToFunctionProxy proxy(destinationFunctionCall); - * connect(srcObject, SIGNAL(sigSomethingChanged()), &proxy, SLOT(start())); + * \code{.cpp} + * + * std::function destinationFunctionCall(std::bind(someNiceFunc, firstParam, secondParam)); + * SignalToFunctionProxy proxy(destinationFunctionCall); + * connect(srcObject, SIGNAL(sigSomethingChanged()), &proxy, SLOT(start())); + * + * \endcode * * Now every time sigSomethingChanged() is emitted, someNiceFunc is * called. std::bind allows us to call any method of any class without * changing signature of the class or creating special wrappers. */ class KRITAGLOBAL_EXPORT SignalToFunctionProxy : public QObject { Q_OBJECT public: using TrivialFunction = std::function; public: SignalToFunctionProxy(TrivialFunction function) : m_function(function) { } public Q_SLOTS: void start() { m_function(); } private: TrivialFunction m_function; }; +/** + * A special class that converts a standard function call into Qt signal + * + * Usage: + * + * \code{.cpp} + * + * FunctionToSignalProxy proxy; + * connect(&proxy, SIGNAL(timeout()), &dstObject, SLOT(someDestinationSlot())); + * + * \endcode + * + * Now every time `proxy.start()` is called, the signal `timeout()` is + * forwarded to `someDestinationSlot()` + */ +class KRITAGLOBAL_EXPORT FunctionToSignalProxy : public QObject +{ + Q_OBJECT + +public: + void start() { + emit timeout(); + } + +Q_SIGNALS: + void timeout(); +}; + + /** * A special class for deferring and comressing events with one * parameter of type T. This works like KisSignalCompressor but can * handle events with one parameter. Due to limitation of the Qt this * doesn't allow signal/slots, so it uses std::function instead. * * In the end (after a timeout) the latest param value is returned to * the callback. * * Usage: * * \code{.cpp} * * using namespace std::placeholders; // For _1 placeholder * * // prepare the callback function * std::function callback( * std::bind(&LutDockerDock::setCurrentExposureImpl, this, _1)); * * // Create the compressor object * KisSignalCompressorWithParam compressor(40, callback); * * // When event comes: * compressor.start(0.123456); * * \endcode */ template class KisSignalCompressorWithParam { public: using CallbackFunction = std::function; public: KisSignalCompressorWithParam(int delay, CallbackFunction function, KisSignalCompressor::Mode mode = KisSignalCompressor::FIRST_ACTIVE) : m_compressor(delay, mode), m_function(function) { std::function callback( std::bind(&KisSignalCompressorWithParam::fakeSlotTimeout, this)); m_signalProxy.reset(new SignalToFunctionProxy(callback)); m_compressor.connect(&m_compressor, SIGNAL(timeout()), m_signalProxy.data(), SLOT(start())); } ~KisSignalCompressorWithParam() { } void start(T param) { m_currentParamValue = param; m_compressor.start(); } void stop() { m_compressor.stop(); } bool isActive() const { return m_compressor.isActive(); } void setDelay(int value) { m_compressor.setDelay(value); } private: void fakeSlotTimeout() { m_function(m_currentParamValue); } private: KisSignalCompressor m_compressor; CallbackFunction m_function; QScopedPointer m_signalProxy; T m_currentParamValue; }; #endif /* __KIS_SIGNAL_COMPRESSOR_WITH_PARAM_H */ diff --git a/libs/image/CMakeLists.txt b/libs/image/CMakeLists.txt index c9863e2563..7d9c7381c7 100644 --- a/libs/image/CMakeLists.txt +++ b/libs/image/CMakeLists.txt @@ -1,387 +1,389 @@ add_subdirectory( tests ) add_subdirectory( tiles3 ) include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty ${CMAKE_CURRENT_SOURCE_DIR}/brushengine ${CMAKE_CURRENT_SOURCE_DIR}/commands ${CMAKE_CURRENT_SOURCE_DIR}/commands_new ${CMAKE_CURRENT_SOURCE_DIR}/filter ${CMAKE_CURRENT_SOURCE_DIR}/floodfill ${CMAKE_CURRENT_SOURCE_DIR}/generator ${CMAKE_CURRENT_SOURCE_DIR}/layerstyles ${CMAKE_CURRENT_SOURCE_DIR}/processing ${CMAKE_SOURCE_DIR}/sdk/tests ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ) if(FFTW3_FOUND) include_directories(${FFTW3_INCLUDE_DIR}) endif() if(HAVE_VC) include_directories(SYSTEM ${Vc_INCLUDE_DIR} ${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS}) ko_compile_for_all_implementations(__per_arch_circle_mask_generator_objs kis_brush_mask_applicator_factories.cpp) else() set(__per_arch_circle_mask_generator_objs kis_brush_mask_applicator_factories.cpp) endif() set(kritaimage_LIB_SRCS tiles3/kis_tile.cc tiles3/kis_tile_data.cc tiles3/kis_tile_data_store.cc tiles3/kis_tile_data_pooler.cc tiles3/kis_tiled_data_manager.cc tiles3/KisTiledExtentManager.cpp tiles3/kis_memento_manager.cc tiles3/kis_hline_iterator.cpp tiles3/kis_vline_iterator.cpp tiles3/kis_random_accessor.cc tiles3/swap/kis_abstract_compression.cpp tiles3/swap/kis_lzf_compression.cpp tiles3/swap/kis_abstract_tile_compressor.cpp tiles3/swap/kis_legacy_tile_compressor.cpp tiles3/swap/kis_tile_compressor_2.cpp tiles3/swap/kis_chunk_allocator.cpp tiles3/swap/kis_memory_window.cpp tiles3/swap/kis_swapped_data_store.cpp tiles3/swap/kis_tile_data_swapper.cpp kis_distance_information.cpp kis_painter.cc kis_painter_blt_multi_fixed.cpp kis_marker_painter.cpp KisPrecisePaintDeviceWrapper.cpp kis_progress_updater.cpp brushengine/kis_paint_information.cc brushengine/kis_random_source.cpp brushengine/KisPerStrokeRandomSource.cpp brushengine/kis_stroke_random_source.cpp brushengine/kis_paintop.cc brushengine/kis_paintop_factory.cpp brushengine/kis_paintop_preset.cpp brushengine/kis_paintop_registry.cc brushengine/kis_paintop_settings.cpp brushengine/kis_paintop_settings_update_proxy.cpp brushengine/kis_paintop_utils.cpp brushengine/kis_no_size_paintop_settings.cpp brushengine/kis_locked_properties.cc brushengine/kis_locked_properties_proxy.cpp brushengine/kis_locked_properties_server.cpp brushengine/kis_paintop_config_widget.cpp brushengine/kis_uniform_paintop_property.cpp brushengine/kis_combo_based_paintop_property.cpp brushengine/kis_slider_based_paintop_property.cpp brushengine/kis_standard_uniform_properties_factory.cpp brushengine/KisStrokeSpeedMeasurer.cpp brushengine/KisPaintopSettingsIds.cpp commands/kis_deselect_global_selection_command.cpp commands/KisDeselectActiveSelectionCommand.cpp commands/kis_image_change_layers_command.cpp commands/kis_image_change_visibility_command.cpp commands/kis_image_command.cpp commands/kis_image_layer_add_command.cpp commands/kis_image_layer_move_command.cpp commands/kis_image_layer_remove_command.cpp commands/kis_image_layer_remove_command_impl.cpp commands/kis_image_lock_command.cpp commands/kis_node_command.cpp commands/kis_node_compositeop_command.cpp commands/kis_node_opacity_command.cpp commands/kis_node_property_list_command.cpp commands/kis_reselect_global_selection_command.cpp commands/KisReselectActiveSelectionCommand.cpp commands/kis_set_global_selection_command.cpp commands/KisNodeRenameCommand.cpp commands_new/kis_saved_commands.cpp commands_new/kis_processing_command.cpp commands_new/kis_image_resize_command.cpp commands_new/kis_image_set_resolution_command.cpp commands_new/kis_node_move_command2.cpp commands_new/kis_set_layer_style_command.cpp commands_new/kis_selection_move_command2.cpp commands_new/kis_update_command.cpp commands_new/kis_switch_current_time_command.cpp commands_new/kis_change_projection_color_command.cpp commands_new/kis_activate_selection_mask_command.cpp commands_new/kis_transaction_based_command.cpp commands_new/KisHoldUIUpdatesCommand.cpp commands_new/KisChangeChannelFlagsCommand.cpp commands_new/KisChangeChannelLockFlagsCommand.cpp processing/kis_do_nothing_processing_visitor.cpp processing/kis_simple_processing_visitor.cpp processing/kis_convert_color_space_processing_visitor.cpp processing/kis_assign_profile_processing_visitor.cpp processing/kis_crop_processing_visitor.cpp processing/kis_crop_selections_processing_visitor.cpp processing/kis_transform_processing_visitor.cpp processing/kis_mirror_processing_visitor.cpp processing/KisSelectionBasedProcessingHelper.cpp filter/kis_filter.cc filter/kis_filter_category_ids.cpp filter/kis_filter_configuration.cc filter/kis_color_transformation_configuration.cc filter/kis_filter_registry.cc filter/kis_color_transformation_filter.cc generator/kis_generator.cpp generator/kis_generator_layer.cpp generator/kis_generator_registry.cpp floodfill/kis_fill_interval_map.cpp floodfill/kis_scanline_fill.cpp lazybrush/kis_min_cut_worker.cpp lazybrush/kis_lazy_fill_tools.cpp lazybrush/kis_multiway_cut.cpp lazybrush/KisWatershedWorker.cpp lazybrush/kis_colorize_mask.cpp lazybrush/kis_colorize_stroke_strategy.cpp KisDelayedUpdateNodeInterface.cpp KisCroppedOriginalLayerInterface.cpp KisDecoratedNodeInterface.cpp kis_adjustment_layer.cc kis_selection_based_layer.cpp kis_node_filter_interface.cpp kis_base_accessor.cpp kis_base_node.cpp kis_base_processor.cpp kis_bookmarked_configuration_manager.cc + KisBusyWaitBroker.cpp + KisSafeBlockingQueueConnectionProxy.cpp kis_node_uuid_info.cpp kis_clone_layer.cpp kis_config_widget.cpp kis_convolution_kernel.cc kis_convolution_painter.cc kis_gaussian_kernel.cpp kis_edge_detection_kernel.cpp kis_cubic_curve.cpp kis_default_bounds.cpp kis_default_bounds_base.cpp kis_effect_mask.cc kis_fast_math.cpp kis_fill_painter.cc kis_filter_mask.cpp kis_filter_strategy.cc kis_transform_mask.cpp kis_transform_mask_params_interface.cpp kis_recalculate_transform_mask_job.cpp kis_recalculate_generator_layer_job.cpp kis_transform_mask_params_factory_registry.cpp kis_safe_transform.cpp kis_gradient_painter.cc kis_gradient_shape_strategy.cpp kis_cached_gradient_shape_strategy.cpp kis_polygonal_gradient_shape_strategy.cpp kis_iterator_ng.cpp kis_async_merger.cpp kis_merge_walker.cc kis_updater_context.cpp kis_update_job_item.cpp kis_stroke_strategy_undo_command_based.cpp kis_simple_stroke_strategy.cpp KisRunnableBasedStrokeStrategy.cpp KisRunnableStrokeJobDataBase.cpp KisRunnableStrokeJobData.cpp KisRunnableStrokeJobsInterface.cpp KisFakeRunnableStrokeJobsExecutor.cpp kis_stroke_job_strategy.cpp kis_stroke_strategy.cpp kis_stroke.cpp kis_strokes_queue.cpp KisStrokesQueueMutatedJobInterface.cpp kis_simple_update_queue.cpp kis_update_scheduler.cpp kis_queues_progress_updater.cpp kis_composite_progress_proxy.cpp kis_sync_lod_cache_stroke_strategy.cpp kis_lod_capable_layer_offset.cpp kis_update_time_monitor.cpp KisImageConfigNotifier.cpp kis_group_layer.cc kis_count_visitor.cpp kis_histogram.cc kis_image_interfaces.cpp kis_image_animation_interface.cpp kis_time_range.cpp kis_node_graph_listener.cpp kis_image.cc kis_image_signal_router.cpp KisImageSignals.cpp kis_image_config.cpp kis_projection_updates_filter.cpp kis_suspend_projection_updates_stroke_strategy.cpp kis_regenerate_frame_stroke_strategy.cpp kis_switch_time_stroke_strategy.cpp kis_crop_saved_extra_data.cpp kis_timed_signal_threshold.cpp kis_layer.cc kis_indirect_painting_support.cpp kis_abstract_projection_plane.cpp kis_layer_projection_plane.cpp kis_layer_utils.cpp kis_mask_projection_plane.cpp kis_projection_leaf.cpp KisSafeNodeProjectionStore.cpp kis_mask.cc kis_base_mask_generator.cpp kis_rect_mask_generator.cpp kis_circle_mask_generator.cpp kis_gauss_circle_mask_generator.cpp kis_gauss_rect_mask_generator.cpp ${__per_arch_circle_mask_generator_objs} kis_curve_circle_mask_generator.cpp kis_curve_rect_mask_generator.cpp kis_math_toolbox.cpp kis_memory_statistics_server.cpp kis_name_server.cpp kis_node.cpp kis_node_facade.cpp kis_node_progress_proxy.cpp kis_busy_progress_indicator.cpp kis_node_visitor.cpp kis_paint_device.cc kis_paint_device_debug_utils.cpp kis_fixed_paint_device.cpp KisOptimizedByteArray.cpp kis_paint_layer.cc kis_perspective_math.cpp kis_pixel_selection.cpp kis_processing_information.cpp kis_properties_configuration.cc kis_random_accessor_ng.cpp kis_random_generator.cc kis_random_sub_accessor.cpp kis_wrapped_random_accessor.cpp kis_selection.cc KisSelectionUpdateCompressor.cpp kis_selection_mask.cpp kis_update_outline_job.cpp kis_update_selection_job.cpp kis_serializable_configuration.cc kis_transaction_data.cpp kis_transform_worker.cc kis_perspectivetransform_worker.cpp bsplines/kis_bspline_1d.cpp bsplines/kis_bspline_2d.cpp bsplines/kis_nu_bspline_2d.cpp kis_warptransform_worker.cc kis_cage_transform_worker.cpp kis_liquify_transform_worker.cpp kis_green_coordinates_math.cpp kis_transparency_mask.cc kis_undo_adapter.cpp kis_macro_based_undo_store.cpp kis_surrogate_undo_adapter.cpp kis_legacy_undo_adapter.cpp kis_post_execution_undo_adapter.cpp kis_processing_visitor.cpp kis_processing_applicator.cpp krita_utils.cpp kis_outline_generator.cpp kis_layer_composition.cpp kis_selection_filters.cpp KisProofingConfiguration.h KisRecycleProjectionsJob.cpp kis_keyframe.cpp kis_keyframe_channel.cpp kis_keyframe_commands.cpp kis_scalar_keyframe_channel.cpp kis_raster_keyframe_channel.cpp kis_onion_skin_compositor.cpp kis_onion_skin_cache.cpp kis_idle_watcher.cpp kis_layer_properties_icons.cpp layerstyles/kis_multiple_projection.cpp layerstyles/kis_layer_style_filter.cpp layerstyles/kis_layer_style_filter_environment.cpp layerstyles/kis_layer_style_filter_projection_plane.cpp layerstyles/kis_layer_style_projection_plane.cpp layerstyles/kis_ls_drop_shadow_filter.cpp layerstyles/kis_ls_satin_filter.cpp layerstyles/kis_ls_stroke_filter.cpp layerstyles/kis_ls_bevel_emboss_filter.cpp layerstyles/kis_ls_overlay_filter.cpp layerstyles/kis_ls_utils.cpp layerstyles/gimp_bump_map.cpp layerstyles/KisLayerStyleKnockoutBlower.cpp KisProofingConfiguration.cpp kis_node_query_path.cc kis_asl_layer_style_serializer.cpp KisAslStorage.cpp kis_psd_layer_style.cpp ) set(einspline_SRCS 3rdparty/einspline/bspline_create.cpp 3rdparty/einspline/bspline_data.cpp 3rdparty/einspline/multi_bspline_create.cpp 3rdparty/einspline/nubasis.cpp 3rdparty/einspline/nubspline_create.cpp 3rdparty/einspline/nugrid.cpp ) add_library(kritaimage SHARED ${kritaimage_LIB_SRCS} ${einspline_SRCS}) generate_export_header(kritaimage BASE_NAME kritaimage) target_link_libraries(kritaimage PUBLIC kritaversion kritawidgets kritaglobal kritapsd kritaodf kritapigment kritacommand kritawidgetutils kritametadata Qt5::Concurrent ) target_link_libraries(kritaimage PUBLIC ${Boost_SYSTEM_LIBRARY}) if(NOT HAVE_CXX_ATOMICS_WITHOUT_LIB) if(NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB) target_link_libraries(kritaimage PUBLIC atomic) endif() endif() if(OPENEXR_FOUND) target_link_libraries(kritaimage PUBLIC ${OPENEXR_LIBRARIES}) endif() if(FFTW3_FOUND) target_link_libraries(kritaimage PRIVATE ${FFTW3_LIBRARIES}) endif() if(HAVE_VC) target_link_libraries(kritaimage PUBLIC ${Vc_LIBRARIES}) endif() if (NOT GSL_FOUND) message (WARNING "KRITA WARNING! No GNU Scientific Library was found! Krita's Shaped Gradients might be non-normalized! Please install GSL library.") else () target_link_libraries(kritaimage PRIVATE ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) endif () target_include_directories(kritaimage PUBLIC $ $ $ $ $ ) set_target_properties(kritaimage PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaimage ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/libs/image/KisBusyWaitBroker.cpp b/libs/image/KisBusyWaitBroker.cpp new file mode 100644 index 0000000000..4722298620 --- /dev/null +++ b/libs/image/KisBusyWaitBroker.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2020 Dmitry Kazakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#include "KisBusyWaitBroker.h" + +#include +#include +#include + +#include +#include + +#include "kis_image.h" + + +Q_GLOBAL_STATIC(KisBusyWaitBroker, s_instance) + + +struct KisBusyWaitBroker::Private +{ + QMutex lock; + QSet waitingOnImages; + int guiThreadLockCount = 0; + + std::function feedbackCallback; +}; + + +KisBusyWaitBroker::KisBusyWaitBroker() + : m_d(new Private) +{ +} + +KisBusyWaitBroker::~KisBusyWaitBroker() +{ +} + +KisBusyWaitBroker *KisBusyWaitBroker::instance() +{ + return s_instance; +} + +void KisBusyWaitBroker::notifyWaitOnImageStarted(KisImage* image) +{ + if (QThread::currentThread() != qApp->thread()) return; + + { + QMutexLocker l(&m_d->lock); + m_d->guiThreadLockCount++; + KIS_SAFE_ASSERT_RECOVER_NOOP(!m_d->waitingOnImages.contains(image)); + m_d->waitingOnImages.insert(image); + } + + if (m_d->feedbackCallback && image->refCount()) { + m_d->feedbackCallback(image); + } +} + +void KisBusyWaitBroker::notifyWaitOnImageEnded(KisImage* image) +{ + if (QThread::currentThread() != qApp->thread()) return; + + { + QMutexLocker l(&m_d->lock); + m_d->guiThreadLockCount--; + KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->waitingOnImages.contains(image)); + m_d->waitingOnImages.remove(image); + } +} + +void KisBusyWaitBroker::notifyGeneralWaitStarted() +{ + if (QThread::currentThread() != qApp->thread()) return; + + QMutexLocker l(&m_d->lock); + m_d->guiThreadLockCount++; +} + +void KisBusyWaitBroker::notifyGeneralWaitEnded() +{ + if (QThread::currentThread() != qApp->thread()) return; + + QMutexLocker l(&m_d->lock); + m_d->guiThreadLockCount--; +} + +void KisBusyWaitBroker::setFeedbackCallback(std::function callback) +{ + m_d->feedbackCallback = callback; +} + +bool KisBusyWaitBroker::guiThreadIsWaitingForBetterWeather() const +{ + return m_d->guiThreadLockCount; +} + + diff --git a/libs/image/KisBusyWaitBroker.h b/libs/image/KisBusyWaitBroker.h new file mode 100644 index 0000000000..29cfeb412e --- /dev/null +++ b/libs/image/KisBusyWaitBroker.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2020 Dmitry Kazakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef KISBUSYWAITBROKER_H +#define KISBUSYWAITBROKER_H + +#include +#include "kritaimage_export.h" + +/** + * @brief a simple singleton class for tracking busy-waits on the image and + * breaking deadlock ties when needed. + * + * When KisImage::barrierLock()/waitForDone() goes into sleep, waiting for the + * worker threads to finish, it notifies KisBusyWaitBroker about that. Therefore + * worker threads know that GUI thread is inactive now and can resolve the ties. + */ +class KRITAIMAGE_EXPORT KisBusyWaitBroker +{ +public: + KisBusyWaitBroker(); + ~KisBusyWaitBroker(); + + static KisBusyWaitBroker* instance(); + + void notifyWaitOnImageStarted(KisImage *image); + void notifyWaitOnImageEnded(KisImage *image); + + void notifyGeneralWaitStarted(); + void notifyGeneralWaitEnded(); + + /** + * Set a callback that is called before image goes into sleep. This callback + * may show the user some feedback window with a progress bar. + * + * This callback is expected to be initialized dorung the construction + * of KisPart. + */ + void setFeedbackCallback(std::function callback); + + /** + * @return true if GUI thread is currently sleeping on an image lock. When a worker + * thread is sure that GUI thread is sleeping, it has a bit more freedom in manipulating + * the image (and especially vector shapes) + */ + bool guiThreadIsWaitingForBetterWeather() const; + +private: + +private: + Q_DISABLE_COPY(KisBusyWaitBroker); + + struct Private; + QScopedPointer m_d; +}; + +#endif // KISBUSYWAITBROKER_H diff --git a/libs/global/kis_signal_compressor_with_param.cpp b/libs/image/KisSafeBlockingQueueConnectionProxy.cpp similarity index 52% copy from libs/global/kis_signal_compressor_with_param.cpp copy to libs/image/KisSafeBlockingQueueConnectionProxy.cpp index 7da2c1488b..d6e92e2abf 100644 --- a/libs/global/kis_signal_compressor_with_param.cpp +++ b/libs/image/KisSafeBlockingQueueConnectionProxy.cpp @@ -1,20 +1,38 @@ /* - * Copyright (c) 2014 Dmitry Kazakov + * Copyright (c) 2020 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include "KisSafeBlockingQueueConnectionProxy.h" -#include "kis_signal_compressor_with_param.h" +#include +#include +#include +void KisSafeBlockingQueueConnectionProxyPrivate::passBlockingSignalSafely(FunctionToSignalProxy &source, SignalToFunctionProxy &destination) +{ + if (QThread::currentThread() == qApp->thread() || + KisBusyWaitBroker::instance()->guiThreadIsWaitingForBetterWeather()) { + + destination.start(); + } else { + source.start(); + } +} + +void KisSafeBlockingQueueConnectionProxyPrivate::initProxyObject(QObject *object) +{ + object->moveToThread(qApp->thread()); +} diff --git a/libs/image/KisSafeBlockingQueueConnectionProxy.h b/libs/image/KisSafeBlockingQueueConnectionProxy.h new file mode 100644 index 0000000000..04f7e93731 --- /dev/null +++ b/libs/image/KisSafeBlockingQueueConnectionProxy.h @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2020 Dmitry Kazakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef KISSAFEBLOCKINGQUEUECONNECTIONPROXY_H +#define KISSAFEBLOCKINGQUEUECONNECTIONPROXY_H + +#include +#include +#include +#include "kis_signal_compressor_with_param.h" +#include "kis_assert.h" +#include "kritaimage_export.h" + +namespace KisSafeBlockingQueueConnectionProxyPrivate { +void KRITAIMAGE_EXPORT passBlockingSignalSafely(FunctionToSignalProxy &source, SignalToFunctionProxy &destination); +void KRITAIMAGE_EXPORT initProxyObject(QObject *object); +} + + +/** + * A special class for safe forwarding of blocking-queued signal to the GUI thread. + * + * The class automatically resolves deadlocks when GUI thread blocks on the image. + * This tie-breaking algorithm is implemented via KisBusyWaitBroker. + * + * Usage: + * + * \code{.cpp} + * + * // create the proxy + * KisSafeBlockingQueueConnectionProxy proxy( + * std::bind(&KisShapeLayer::slotTransformShapes, shapeLayer)); + * + * // emit synchronous signal with deadlock-avoidance + * proxy.start(QTransform::fromScale(0.5, 0.5)); + * + * \endcode + */ +template +class KisSafeBlockingQueueConnectionProxy +{ + using CallbackFunction = std::function; +public: + KisSafeBlockingQueueConnectionProxy(CallbackFunction function) + : m_function(function), + m_destination(std::bind(&KisSafeBlockingQueueConnectionProxy::fakeSlotTimeout, this)) + { + KisSafeBlockingQueueConnectionProxyPrivate::initProxyObject(&m_source); + KisSafeBlockingQueueConnectionProxyPrivate::initProxyObject(&m_destination); + + QObject::connect(&m_source, SIGNAL(timeout()), &m_destination, SLOT(start()), Qt::BlockingQueuedConnection); + } + + void start(T value) { + const int sanityQueueSize = m_value.size(); + + m_value.enqueue(value); + KisSafeBlockingQueueConnectionProxyPrivate::passBlockingSignalSafely(m_source, m_destination); + + KIS_SAFE_ASSERT_RECOVER_NOOP(m_value.size() == sanityQueueSize); + } + +private: + void fakeSlotTimeout() { + KIS_SAFE_ASSERT_RECOVER_RETURN(!m_value.isEmpty()); + m_function(m_value.dequeue()); + } + +private: + CallbackFunction m_function; + FunctionToSignalProxy m_source; + SignalToFunctionProxy m_destination; + QQueue m_value; +}; + +/** + * An override of KisSafeBlockingQueueConnectionProxy for forwarding signals + * without any parameters. + */ +template <> +class KisSafeBlockingQueueConnectionProxy +{ + using CallbackFunction = std::function; +public: + KisSafeBlockingQueueConnectionProxy(CallbackFunction function) + : m_function(function), + m_destination(std::bind(&KisSafeBlockingQueueConnectionProxy::fakeSlotTimeout, this)) + { + KisSafeBlockingQueueConnectionProxyPrivate::initProxyObject(&m_source); + KisSafeBlockingQueueConnectionProxyPrivate::initProxyObject(&m_destination); + + QObject::connect(&m_source, SIGNAL(timeout()), &m_destination, SLOT(start()), Qt::BlockingQueuedConnection); + } + + void start() { + KisSafeBlockingQueueConnectionProxyPrivate::passBlockingSignalSafely(m_source, m_destination); + } + +private: + void fakeSlotTimeout() { + m_function(); + } + +private: + CallbackFunction m_function; + FunctionToSignalProxy m_source; + SignalToFunctionProxy m_destination; +}; + +#endif // KISSAFEBLOCKINGQUEUECONNECTIONPROXY_H diff --git a/libs/image/kis_image.cc b/libs/image/kis_image.cc index 358c8640b5..f028464cdc 100644 --- a/libs/image/kis_image.cc +++ b/libs/image/kis_image.cc @@ -1,2235 +1,2244 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_image.h" #include // WORDS_BIGENDIAN #include #include #include #include #include #include #include #include #include #include "KoColorSpaceRegistry.h" #include "KoColor.h" #include "KoColorProfile.h" #include #include "KisProofingConfiguration.h" #include "kis_adjustment_layer.h" #include "kis_annotation.h" #include "kis_count_visitor.h" #include "kis_filter_strategy.h" #include "kis_group_layer.h" #include "commands/kis_image_commands.h" #include "kis_layer.h" #include "kis_meta_data_merge_strategy_registry.h" #include "kis_name_server.h" #include "kis_paint_layer.h" #include "kis_projection_leaf.h" #include "kis_painter.h" #include "kis_selection.h" #include "kis_transaction.h" #include "kis_meta_data_merge_strategy.h" #include "kis_memory_statistics_server.h" #include "kis_image_config.h" #include "kis_update_scheduler.h" #include "kis_image_signal_router.h" #include "kis_image_animation_interface.h" #include "kis_stroke_strategy.h" #include "kis_simple_stroke_strategy.h" #include "kis_image_barrier_locker.h" #include "kis_undo_stores.h" #include "kis_legacy_undo_adapter.h" #include "kis_post_execution_undo_adapter.h" #include "kis_transform_worker.h" #include "kis_processing_applicator.h" #include "processing/kis_crop_processing_visitor.h" #include "processing/kis_crop_selections_processing_visitor.h" #include "processing/kis_transform_processing_visitor.h" #include "processing/kis_convert_color_space_processing_visitor.h" #include "processing/kis_assign_profile_processing_visitor.h" #include "commands_new/kis_image_resize_command.h" #include "commands_new/kis_image_set_resolution_command.h" #include "commands_new/kis_activate_selection_mask_command.h" #include "kis_do_something_command.h" #include "kis_composite_progress_proxy.h" #include "kis_layer_composition.h" #include "kis_wrapped_rect.h" #include "kis_crop_saved_extra_data.h" #include "kis_layer_utils.h" #include "kis_lod_transform.h" #include "kis_suspend_projection_updates_stroke_strategy.h" #include "kis_sync_lod_cache_stroke_strategy.h" #include "kis_projection_updates_filter.h" #include "kis_layer_projection_plane.h" #include "kis_update_time_monitor.h" #include "tiles3/kis_lockless_stack.h" #include #include #include "kis_time_range.h" #include "KisRunnableBasedStrokeStrategy.h" #include "KisRunnableStrokeJobData.h" #include "KisRunnableStrokeJobUtils.h" #include "KisRunnableStrokeJobsInterface.h" +#include "KisBusyWaitBroker.h" + + // #define SANITY_CHECKS #ifdef SANITY_CHECKS #define SANITY_CHECK_LOCKED(name) \ if (!locked()) warnKrita() << "Locking policy failed:" << name \ << "has been called without the image" \ "being locked"; #else #define SANITY_CHECK_LOCKED(name) #endif struct KisImageSPStaticRegistrar { KisImageSPStaticRegistrar() { qRegisterMetaType("KisImageSP"); } }; static KisImageSPStaticRegistrar __registrar; class KisImage::KisImagePrivate { public: KisImagePrivate(KisImage *_q, qint32 w, qint32 h, const KoColorSpace *c, KisUndoStore *undo, KisImageAnimationInterface *_animationInterface) : q(_q) , lockedForReadOnly(false) , width(w) , height(h) , colorSpace(c ? c : KoColorSpaceRegistry::instance()->rgb8()) , nserver(1) , undoStore(undo ? undo : new KisDumbUndoStore()) , legacyUndoAdapter(undoStore.data(), _q) , postExecutionUndoAdapter(undoStore.data(), _q) , signalRouter(_q) , animationInterface(_animationInterface) , scheduler(_q, _q) , axesCenter(QPointF(0.5, 0.5)) { { KisImageConfig cfg(true); if (cfg.enableProgressReporting()) { scheduler.setProgressProxy(&compositeProgressProxy); } // Each of these lambdas defines a new factory function. scheduler.setLod0ToNStrokeStrategyFactory( [=](bool forgettable) { return KisLodSyncPair( new KisSyncLodCacheStrokeStrategy(KisImageWSP(q), forgettable), KisSyncLodCacheStrokeStrategy::createJobsData(KisImageWSP(q))); }); scheduler.setSuspendUpdatesStrokeStrategyFactory( [=]() { return KisSuspendResumePair( new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), true), KisSuspendProjectionUpdatesStrokeStrategy::createSuspendJobsData(KisImageWSP(q))); }); scheduler.setResumeUpdatesStrokeStrategyFactory( [=]() { return KisSuspendResumePair( new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), false), KisSuspendProjectionUpdatesStrokeStrategy::createResumeJobsData(KisImageWSP(q))); }); } connect(q, SIGNAL(sigImageModified()), KisMemoryStatisticsServer::instance(), SLOT(notifyImageChanged())); } ~KisImagePrivate() { /** * Stop animation interface. It may use the rootLayer. */ delete animationInterface; /** * First delete the nodes, while strokes * and undo are still alive */ rootLayer.clear(); } KisImage *q; quint32 lockCount = 0; bool lockedForReadOnly; qint32 width; qint32 height; double xres = 1.0; double yres = 1.0; const KoColorSpace * colorSpace; KisProofingConfigurationSP proofingConfig; KisSelectionSP deselectedGlobalSelection; KisGroupLayerSP rootLayer; // The layers are contained in here KisSelectionMaskSP targetOverlaySelectionMask; // the overlay switching stroke will try to switch into this mask KisSelectionMaskSP overlaySelectionMask; QList compositions; KisNodeSP isolatedRootNode; bool wrapAroundModePermitted = false; KisNameServer nserver; QScopedPointer undoStore; KisLegacyUndoAdapter legacyUndoAdapter; KisPostExecutionUndoAdapter postExecutionUndoAdapter; vKisAnnotationSP annotations; QAtomicInt disableUIUpdateSignals; KisLocklessStack savedDisabledUIUpdates; KisProjectionUpdatesFilterSP projectionUpdatesFilter; KisImageSignalRouter signalRouter; KisImageAnimationInterface *animationInterface; KisUpdateScheduler scheduler; QAtomicInt disableDirtyRequests; KisCompositeProgressProxy compositeProgressProxy; bool blockLevelOfDetail = false; QPointF axesCenter; bool allowMasksOnRootNode = false; bool tryCancelCurrentStrokeAsync(); void notifyProjectionUpdatedInPatches(const QRect &rc, QVector &jobs); void convertImageColorSpaceImpl(const KoColorSpace *dstColorSpace, bool convertLayers, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags); struct SetImageProjectionColorSpace; }; KisImage::KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace * colorSpace, const QString& name) : QObject(0) , KisShared() , m_d(new KisImagePrivate(this, width, height, colorSpace, undoStore, new KisImageAnimationInterface(this))) { // make sure KisImage belongs to the GUI thread moveToThread(qApp->thread()); connect(this, SIGNAL(sigInternalStopIsolatedModeRequested()), SLOT(stopIsolatedMode())); setObjectName(name); setRootLayer(new KisGroupLayer(this, "root", OPACITY_OPAQUE_U8)); } KisImage::~KisImage() { dbgImage << "deleting kisimage" << objectName(); /** * Request the tools to end currently running strokes */ waitForDone(); delete m_d; disconnect(); // in case Qt gets confused } KisImageSP KisImage::fromQImage(const QImage &image, KisUndoStore *undoStore) { const KoColorSpace *colorSpace = 0; switch (image.format()) { case QImage::Format_Invalid: case QImage::Format_Mono: case QImage::Format_MonoLSB: colorSpace = KoColorSpaceRegistry::instance()->graya8(); break; case QImage::Format_Indexed8: case QImage::Format_RGB32: case QImage::Format_ARGB32: case QImage::Format_ARGB32_Premultiplied: colorSpace = KoColorSpaceRegistry::instance()->rgb8(); break; case QImage::Format_RGB16: colorSpace = KoColorSpaceRegistry::instance()->rgb16(); break; case QImage::Format_ARGB8565_Premultiplied: case QImage::Format_RGB666: case QImage::Format_ARGB6666_Premultiplied: case QImage::Format_RGB555: case QImage::Format_ARGB8555_Premultiplied: case QImage::Format_RGB888: case QImage::Format_RGB444: case QImage::Format_ARGB4444_Premultiplied: case QImage::Format_RGBX8888: case QImage::Format_RGBA8888: case QImage::Format_RGBA8888_Premultiplied: colorSpace = KoColorSpaceRegistry::instance()->rgb8(); break; case QImage::Format_BGR30: case QImage::Format_A2BGR30_Premultiplied: case QImage::Format_RGB30: case QImage::Format_A2RGB30_Premultiplied: colorSpace = KoColorSpaceRegistry::instance()->rgb8(); break; case QImage::Format_Alpha8: colorSpace = KoColorSpaceRegistry::instance()->alpha8(); break; case QImage::Format_Grayscale8: colorSpace = KoColorSpaceRegistry::instance()->graya8(); break; #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) case QImage::Format_Grayscale16: colorSpace = KoColorSpaceRegistry::instance()->graya16(); break; #endif #if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0) case QImage::Format_RGBX64: case QImage::Format_RGBA64: case QImage::Format_RGBA64_Premultiplied: colorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), 0); break; #endif default: colorSpace = 0; } KisImageSP img = new KisImage(undoStore, image.width(), image.height(), colorSpace, i18n("Imported Image")); KisPaintLayerSP layer = new KisPaintLayer(img, img->nextLayerName(), 255); layer->paintDevice()->convertFromQImage(image, 0, 0, 0); img->addNode(layer.data(), img->rootLayer().data()); return img; } KisImage *KisImage::clone(bool exactCopy) { return new KisImage(*this, 0, exactCopy); } void KisImage::copyFromImage(const KisImage &rhs) { copyFromImageImpl(rhs, REPLACE); } void KisImage::copyFromImageImpl(const KisImage &rhs, int policy) { // make sure we choose exactly one from REPLACE and CONSTRUCT KIS_ASSERT_RECOVER_RETURN((policy & REPLACE) != (policy & CONSTRUCT)); // only when replacing do we need to emit signals #define EMIT_IF_NEEDED if (!(policy & REPLACE)) {} else emit if (policy & REPLACE) { // if we are constructing the image, these are already set if (m_d->width != rhs.width() || m_d->height != rhs.height()) { m_d->width = rhs.width(); m_d->height = rhs.height(); emit sigSizeChanged(QPointF(), QPointF()); } if (m_d->colorSpace != rhs.colorSpace()) { m_d->colorSpace = rhs.colorSpace(); emit sigColorSpaceChanged(m_d->colorSpace); } } // from KisImage::KisImage(const KisImage &, KisUndoStore *, bool) setObjectName(rhs.objectName()); if (m_d->xres != rhs.m_d->xres || m_d->yres != rhs.m_d->yres) { m_d->xres = rhs.m_d->xres; m_d->yres = rhs.m_d->yres; EMIT_IF_NEEDED sigResolutionChanged(m_d->xres, m_d->yres); } m_d->allowMasksOnRootNode = rhs.m_d->allowMasksOnRootNode; if (rhs.m_d->proofingConfig) { KisProofingConfigurationSP proofingConfig(new KisProofingConfiguration(*rhs.m_d->proofingConfig)); if (policy & REPLACE) { setProofingConfiguration(proofingConfig); } else { m_d->proofingConfig = proofingConfig; } } KisNodeSP newRoot = rhs.root()->clone(); newRoot->setGraphListener(this); newRoot->setImage(this); m_d->rootLayer = dynamic_cast(newRoot.data()); setRoot(newRoot); bool exactCopy = policy & EXACT_COPY; if (exactCopy || rhs.m_d->isolatedRootNode || rhs.m_d->overlaySelectionMask) { QQueue linearizedNodes; KisLayerUtils::recursiveApplyNodes(rhs.root(), [&linearizedNodes](KisNodeSP node) { linearizedNodes.enqueue(node); }); KisLayerUtils::recursiveApplyNodes(newRoot, [&linearizedNodes, exactCopy, &rhs, this](KisNodeSP node) { KisNodeSP refNode = linearizedNodes.dequeue(); if (exactCopy) { node->setUuid(refNode->uuid()); } if (rhs.m_d->isolatedRootNode && rhs.m_d->isolatedRootNode == refNode) { m_d->isolatedRootNode = node; } if (rhs.m_d->overlaySelectionMask && KisNodeSP(rhs.m_d->overlaySelectionMask) == refNode) { m_d->targetOverlaySelectionMask = dynamic_cast(node.data()); m_d->overlaySelectionMask = m_d->targetOverlaySelectionMask; m_d->rootLayer->notifyChildMaskChanged(); } }); } KisLayerUtils::recursiveApplyNodes(newRoot, [](KisNodeSP node) { dbgImage << "Node: " << (void *)node.data(); }); m_d->compositions.clear(); Q_FOREACH (KisLayerCompositionSP comp, rhs.m_d->compositions) { m_d->compositions << toQShared(new KisLayerComposition(*comp, this)); } EMIT_IF_NEEDED sigLayersChangedAsync(); m_d->nserver = rhs.m_d->nserver; vKisAnnotationSP newAnnotations; Q_FOREACH (KisAnnotationSP annotation, rhs.m_d->annotations) { newAnnotations << annotation->clone(); } m_d->annotations = newAnnotations; KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->projectionUpdatesFilter); KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableUIUpdateSignals); KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableDirtyRequests); m_d->blockLevelOfDetail = rhs.m_d->blockLevelOfDetail; #undef EMIT_IF_NEEDED } KisImage::KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy) : KisNodeFacade(), KisNodeGraphListener(), KisShared(), m_d(new KisImagePrivate(this, rhs.width(), rhs.height(), rhs.colorSpace(), undoStore ? undoStore : new KisDumbUndoStore(), new KisImageAnimationInterface(*rhs.animationInterface(), this))) { // make sure KisImage belongs to the GUI thread moveToThread(qApp->thread()); connect(this, SIGNAL(sigInternalStopIsolatedModeRequested()), SLOT(stopIsolatedMode())); copyFromImageImpl(rhs, CONSTRUCT | (exactCopy ? EXACT_COPY : 0)); } void KisImage::aboutToAddANode(KisNode *parent, int index) { KisNodeGraphListener::aboutToAddANode(parent, index); SANITY_CHECK_LOCKED("aboutToAddANode"); } void KisImage::nodeHasBeenAdded(KisNode *parent, int index) { KisNodeGraphListener::nodeHasBeenAdded(parent, index); SANITY_CHECK_LOCKED("nodeHasBeenAdded"); m_d->signalRouter.emitNodeHasBeenAdded(parent, index); } void KisImage::aboutToRemoveANode(KisNode *parent, int index) { KisNodeSP deletedNode = parent->at(index); if (!dynamic_cast(deletedNode.data()) && deletedNode == m_d->isolatedRootNode) { emit sigInternalStopIsolatedModeRequested(); } KisNodeGraphListener::aboutToRemoveANode(parent, index); SANITY_CHECK_LOCKED("aboutToRemoveANode"); m_d->signalRouter.emitAboutToRemoveANode(parent, index); } void KisImage::nodeChanged(KisNode* node) { KisNodeGraphListener::nodeChanged(node); requestStrokeEnd(); m_d->signalRouter.emitNodeChanged(node); } void KisImage::invalidateAllFrames() { invalidateFrames(KisTimeRange::infinite(0), QRect()); } void KisImage::setOverlaySelectionMask(KisSelectionMaskSP mask) { if (m_d->targetOverlaySelectionMask == mask) return; m_d->targetOverlaySelectionMask = mask; struct UpdateOverlaySelectionStroke : public KisSimpleStrokeStrategy { UpdateOverlaySelectionStroke(KisImageSP image) : KisSimpleStrokeStrategy(QLatin1String("update-overlay-selection-mask"), kundo2_noi18n("update-overlay-selection-mask")), m_image(image) { this->enableJob(JOB_INIT, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); setClearsRedoOnStart(false); } void initStrokeCallback() { KisSelectionMaskSP oldMask = m_image->m_d->overlaySelectionMask; KisSelectionMaskSP newMask = m_image->m_d->targetOverlaySelectionMask; if (oldMask == newMask) return; KIS_SAFE_ASSERT_RECOVER_RETURN(!newMask || newMask->graphListener() == m_image); m_image->m_d->overlaySelectionMask = newMask; if (oldMask || newMask) { m_image->m_d->rootLayer->notifyChildMaskChanged(); } if (oldMask) { m_image->m_d->rootLayer->setDirtyDontResetAnimationCache(oldMask->extent()); } if (newMask) { newMask->setDirty(); } m_image->undoAdapter()->emitSelectionChanged(); } private: KisImageSP m_image; }; KisStrokeId id = startStroke(new UpdateOverlaySelectionStroke(this)); endStroke(id); } KisSelectionMaskSP KisImage::overlaySelectionMask() const { return m_d->overlaySelectionMask; } bool KisImage::hasOverlaySelectionMask() const { return m_d->overlaySelectionMask; } KisSelectionSP KisImage::globalSelection() const { KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask(); if (selectionMask) { return selectionMask->selection(); } else { return 0; } } void KisImage::setGlobalSelection(KisSelectionSP globalSelection) { KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask(); if (!globalSelection) { if (selectionMask) { removeNode(selectionMask); } } else { if (!selectionMask) { selectionMask = new KisSelectionMask(this); selectionMask->initSelection(m_d->rootLayer); addNode(selectionMask); // If we do not set the selection now, the setActive call coming next // can be very, very expensive, depending on the size of the image. selectionMask->setSelection(globalSelection); selectionMask->setActive(true); } else { selectionMask->setSelection(globalSelection); } KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->rootLayer->childCount() > 0); KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->rootLayer->selectionMask()); } m_d->deselectedGlobalSelection = 0; m_d->legacyUndoAdapter.emitSelectionChanged(); } void KisImage::deselectGlobalSelection() { KisSelectionSP savedSelection = globalSelection(); setGlobalSelection(0); m_d->deselectedGlobalSelection = savedSelection; } bool KisImage::canReselectGlobalSelection() { return m_d->deselectedGlobalSelection; } void KisImage::reselectGlobalSelection() { if(m_d->deselectedGlobalSelection) { setGlobalSelection(m_d->deselectedGlobalSelection); } } QString KisImage::nextLayerName(const QString &_baseName) const { QString baseName = _baseName; if (m_d->nserver.currentSeed() == 0) { m_d->nserver.number(); return i18n("background"); } if (baseName.isEmpty()) { baseName = i18n("Layer"); } return QString("%1 %2").arg(baseName).arg(m_d->nserver.number()); } void KisImage::rollBackLayerName() { m_d->nserver.rollback(); } KisCompositeProgressProxy* KisImage::compositeProgressProxy() { return &m_d->compositeProgressProxy; } bool KisImage::locked() const { return m_d->lockCount != 0; } void KisImage::barrierLock(bool readOnly) { if (!locked()) { requestStrokeEnd(); + KisBusyWaitBroker::instance()->notifyWaitOnImageStarted(this); m_d->scheduler.barrierLock(); + KisBusyWaitBroker::instance()->notifyWaitOnImageEnded(this); m_d->lockedForReadOnly = readOnly; } else { m_d->lockedForReadOnly &= readOnly; } m_d->lockCount++; } bool KisImage::tryBarrierLock(bool readOnly) { bool result = true; if (!locked()) { result = m_d->scheduler.tryBarrierLock(); m_d->lockedForReadOnly = readOnly; } if (result) { m_d->lockCount++; m_d->lockedForReadOnly &= readOnly; } return result; } bool KisImage::isIdle(bool allowLocked) { return (allowLocked || !locked()) && m_d->scheduler.isIdle(); } void KisImage::lock() { if (!locked()) { requestStrokeEnd(); + KisBusyWaitBroker::instance()->notifyWaitOnImageStarted(this); m_d->scheduler.lock(); + KisBusyWaitBroker::instance()->notifyWaitOnImageEnded(this); } m_d->lockCount++; m_d->lockedForReadOnly = false; } void KisImage::unlock() { Q_ASSERT(locked()); if (locked()) { m_d->lockCount--; if (m_d->lockCount == 0) { m_d->scheduler.unlock(!m_d->lockedForReadOnly); } } } void KisImage::blockUpdates() { m_d->scheduler.blockUpdates(); } void KisImage::unblockUpdates() { m_d->scheduler.unblockUpdates(); } void KisImage::setSize(const QSize& size) { m_d->width = size.width(); m_d->height = size.height(); } void KisImage::resizeImageImpl(const QRect& newRect, bool cropLayers) { if (newRect == bounds() && !cropLayers) return; KUndo2MagicString actionName = cropLayers ? kundo2_i18n("Crop Image") : kundo2_i18n("Resize Image"); KisImageSignalVector emitSignals; emitSignals << ComplexSizeChangedSignal(newRect, newRect.size()); emitSignals << ModifiedSignal; KisCropSavedExtraData *extraData = new KisCropSavedExtraData(cropLayers ? KisCropSavedExtraData::CROP_IMAGE : KisCropSavedExtraData::RESIZE_IMAGE, newRect); KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | KisProcessingApplicator::NO_UI_UPDATES, emitSignals, actionName, extraData); if (cropLayers || !newRect.topLeft().isNull()) { KisProcessingVisitorSP visitor = new KisCropProcessingVisitor(newRect, cropLayers, true); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } applicator.applyCommand(new KisImageResizeCommand(this, newRect.size())); applicator.end(); } void KisImage::resizeImage(const QRect& newRect) { resizeImageImpl(newRect, false); } void KisImage::cropImage(const QRect& newRect) { resizeImageImpl(newRect, true); } void KisImage::cropNode(KisNodeSP node, const QRect& newRect) { bool isLayer = qobject_cast(node.data()); KUndo2MagicString actionName = isLayer ? kundo2_i18n("Crop Layer") : kundo2_i18n("Crop Mask"); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisCropSavedExtraData *extraData = new KisCropSavedExtraData(KisCropSavedExtraData::CROP_LAYER, newRect, node); KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName, extraData); KisProcessingVisitorSP visitor = new KisCropProcessingVisitor(newRect, true, false); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } void KisImage::scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy) { bool resolutionChanged = xres != xRes() && yres != yRes(); bool sizeChanged = size != this->size(); if (!resolutionChanged && !sizeChanged) return; KisImageSignalVector emitSignals; if (resolutionChanged) emitSignals << ResolutionChangedSignal; if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), size); emitSignals << ModifiedSignal; KUndo2MagicString actionName = sizeChanged ? kundo2_i18n("Scale Image") : kundo2_i18n("Change Image Resolution"); KisProcessingApplicator::ProcessingFlags signalFlags = (resolutionChanged || sizeChanged) ? KisProcessingApplicator::NO_UI_UPDATES : KisProcessingApplicator::NONE; KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | signalFlags, emitSignals, actionName); qreal sx = qreal(size.width()) / this->size().width(); qreal sy = qreal(size.height()) / this->size().height(); QTransform shapesCorrection; if (resolutionChanged) { shapesCorrection = QTransform::fromScale(xRes() / xres, yRes() / yres); } KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(sx, sy, 0, 0, QPointF(), 0, 0, 0, filterStrategy, shapesCorrection); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); if (resolutionChanged) { KUndo2Command *parent = new KisResetShapesCommand(m_d->rootLayer); new KisImageSetResolutionCommand(this, xres, yres, parent); applicator.applyCommand(parent); } if (sizeChanged) { applicator.applyCommand(new KisImageResizeCommand(this, size)); } applicator.end(); } void KisImage::scaleNode(KisNodeSP node, const QPointF ¢er, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy, KisSelectionSP selection) { KUndo2MagicString actionName(kundo2_i18n("Scale Layer")); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; QPointF offset; { KisTransformWorker worker(0, scaleX, scaleY, 0, 0, 0, 0, 0.0, 0, 0, 0, 0); QTransform transform = worker.transform(); offset = center - transform.map(center); } KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName); KisTransformProcessingVisitor *visitor = new KisTransformProcessingVisitor(scaleX, scaleY, 0, 0, QPointF(), 0, offset.x(), offset.y(), filterStrategy); visitor->setSelection(selection); if (selection) { applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); } else { applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } applicator.end(); } void KisImage::rotateImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, double radians, bool resizeImage, KisSelectionSP selection) { // we can either transform (and resize) the whole image or // transform a selection, we cannot do both at the same time KIS_SAFE_ASSERT_RECOVER(!(bool(selection) && resizeImage)) { selection = 0; } const QRect baseBounds = resizeImage ? bounds() : selection ? selection->selectedExactRect() : rootNode->exactBounds(); QPointF offset; QSize newSize; { KisTransformWorker worker(0, 1.0, 1.0, 0, 0, 0, 0, radians, 0, 0, 0, 0); QTransform transform = worker.transform(); if (resizeImage) { QRect newRect = transform.mapRect(baseBounds); newSize = newRect.size(); offset = -newRect.topLeft(); } else { QPointF origin = QRectF(baseBounds).center(); newSize = size(); offset = -(transform.map(origin) - origin); } } bool sizeChanged = resizeImage && (newSize.width() != baseBounds.width() || newSize.height() != baseBounds.height()); // These signals will be emitted after processing is done KisImageSignalVector emitSignals; if (sizeChanged) emitSignals << ComplexSizeChangedSignal(baseBounds, newSize); emitSignals << ModifiedSignal; // These flags determine whether updates are transferred to the UI during processing KisProcessingApplicator::ProcessingFlags signalFlags = sizeChanged ? KisProcessingApplicator::NO_UI_UPDATES : KisProcessingApplicator::NONE; KisProcessingApplicator applicator(this, rootNode, KisProcessingApplicator::RECURSIVE | signalFlags, emitSignals, actionName); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bicubic"); KisTransformProcessingVisitor *visitor = new KisTransformProcessingVisitor(1.0, 1.0, 0.0, 0.0, QPointF(), radians, offset.x(), offset.y(), filter); if (selection) { visitor->setSelection(selection); } if (selection) { applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); } else { applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } if (sizeChanged) { applicator.applyCommand(new KisImageResizeCommand(this, newSize)); } applicator.end(); } void KisImage::rotateImage(double radians) { rotateImpl(kundo2_i18n("Rotate Image"), root(), radians, true, 0); } void KisImage::rotateNode(KisNodeSP node, double radians, KisSelectionSP selection) { if (node->inherits("KisMask")) { rotateImpl(kundo2_i18n("Rotate Mask"), node, radians, false, selection); } else { rotateImpl(kundo2_i18n("Rotate Layer"), node, radians, false, selection); } } void KisImage::shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double angleX, double angleY, KisSelectionSP selection) { const QRect baseBounds = resizeImage ? bounds() : selection ? selection->selectedExactRect() : rootNode->exactBounds(); const QPointF origin = QRectF(baseBounds).center(); //angleX, angleY are in degrees const qreal pi = 3.1415926535897932385; const qreal deg2rad = pi / 180.0; qreal tanX = tan(angleX * deg2rad); qreal tanY = tan(angleY * deg2rad); QPointF offset; QSize newSize; { KisTransformWorker worker(0, 1.0, 1.0, tanX, tanY, origin.x(), origin.y(), 0, 0, 0, 0, 0); QRect newRect = worker.transform().mapRect(baseBounds); newSize = newRect.size(); if (resizeImage) offset = -newRect.topLeft(); } if (newSize == baseBounds.size()) return; KisImageSignalVector emitSignals; if (resizeImage) emitSignals << ComplexSizeChangedSignal(baseBounds, newSize); emitSignals << ModifiedSignal; KisProcessingApplicator::ProcessingFlags signalFlags = KisProcessingApplicator::RECURSIVE; if (resizeImage) signalFlags |= KisProcessingApplicator::NO_UI_UPDATES; KisProcessingApplicator applicator(this, rootNode, signalFlags, emitSignals, actionName); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bilinear"); KisTransformProcessingVisitor *visitor = new KisTransformProcessingVisitor(1.0, 1.0, tanX, tanY, origin, 0, offset.x(), offset.y(), filter); if (selection) { visitor->setSelection(selection); } if (selection) { applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); } else { applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } if (resizeImage) { applicator.applyCommand(new KisImageResizeCommand(this, newSize)); } applicator.end(); } void KisImage::shearNode(KisNodeSP node, double angleX, double angleY, KisSelectionSP selection) { if (node->inherits("KisMask")) { shearImpl(kundo2_i18n("Shear Mask"), node, false, angleX, angleY, selection); } else { shearImpl(kundo2_i18n("Shear Layer"), node, false, angleX, angleY, selection); } } void KisImage::shear(double angleX, double angleY) { shearImpl(kundo2_i18n("Shear Image"), m_d->rootLayer, true, angleX, angleY, 0); } void KisImage::convertLayerColorSpace(KisNodeSP node, const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { if (!node->projectionLeaf()->isLayer()) return; const KoColorSpace *srcColorSpace = node->colorSpace(); if (!dstColorSpace || *srcColorSpace == *dstColorSpace) return; KUndo2MagicString actionName = kundo2_i18n("Convert Layer Color Space"); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE | KisProcessingApplicator::NO_UI_UPDATES, emitSignals, actionName); applicator.applyVisitor( new KisConvertColorSpaceProcessingVisitor( srcColorSpace, dstColorSpace, renderingIntent, conversionFlags), KisStrokeJobData::CONCURRENT); applicator.end(); } struct KisImage::KisImagePrivate::SetImageProjectionColorSpace : public KisCommandUtils::FlipFlopCommand { SetImageProjectionColorSpace(const KoColorSpace *cs, KisImageWSP image, State initialState, KUndo2Command *parent = 0) : KisCommandUtils::FlipFlopCommand(initialState, parent), m_cs(cs), m_image(image) { } void partA() override { KisImageSP image = m_image; if (image) { image->setProjectionColorSpace(m_cs); } } private: const KoColorSpace *m_cs; KisImageWSP m_image; }; void KisImage::KisImagePrivate::convertImageColorSpaceImpl(const KoColorSpace *dstColorSpace, bool convertLayers, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { const KoColorSpace *srcColorSpace = this->colorSpace; if (!dstColorSpace || *srcColorSpace == *dstColorSpace) return; const KUndo2MagicString actionName = convertLayers ? kundo2_i18n("Convert Image Color Space") : kundo2_i18n("Convert Projection Color Space"); KisImageSignalVector emitSignals; emitSignals << ColorSpaceChangedSignal; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(q, this->rootLayer, KisProcessingApplicator::RECURSIVE | KisProcessingApplicator::NO_UI_UPDATES, emitSignals, actionName); applicator.applyCommand( new KisImagePrivate::SetImageProjectionColorSpace(dstColorSpace, KisImageWSP(q), KisCommandUtils::FlipFlopCommand::INITIALIZING), KisStrokeJobData::BARRIER); if (convertLayers) { applicator.applyVisitor( new KisConvertColorSpaceProcessingVisitor( srcColorSpace, dstColorSpace, renderingIntent, conversionFlags), KisStrokeJobData::CONCURRENT); } else { applicator.applyCommand( new KisDoSomethingCommand< KisDoSomethingCommandOps::ResetOp, KisGroupLayerSP> (this->rootLayer, false)); applicator.applyCommand( new KisDoSomethingCommand< KisDoSomethingCommandOps::ResetOp, KisGroupLayerSP> (this->rootLayer, true)); } applicator.applyCommand( new KisImagePrivate::SetImageProjectionColorSpace(srcColorSpace, KisImageWSP(q), KisCommandUtils::FlipFlopCommand::FINALIZING), KisStrokeJobData::BARRIER); applicator.end(); } void KisImage::convertImageColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { m_d->convertImageColorSpaceImpl(dstColorSpace, true, renderingIntent, conversionFlags); } void KisImage::convertImageProjectionColorSpace(const KoColorSpace *dstColorSpace) { m_d->convertImageColorSpaceImpl(dstColorSpace, false, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } bool KisImage::assignLayerProfile(KisNodeSP node, const KoColorProfile *profile) { const KoColorSpace *srcColorSpace = node->colorSpace(); if (!node->projectionLeaf()->isLayer()) return false; if (!profile || *srcColorSpace->profile() == *profile) return false; KUndo2MagicString actionName = kundo2_i18n("Assign Profile to Layer"); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; const KoColorSpace *dstColorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); if (!dstColorSpace) return false; KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE | KisProcessingApplicator::NO_UI_UPDATES, emitSignals, actionName); applicator.applyVisitor( new KisAssignProfileProcessingVisitor( srcColorSpace, dstColorSpace), KisStrokeJobData::CONCURRENT); applicator.end(); return true; } bool KisImage::assignImageProfile(const KoColorProfile *profile, bool blockAllUpdates) { if (!profile) return false; const KoColorSpace *srcColorSpace = m_d->colorSpace; bool imageProfileIsSame = *srcColorSpace->profile() == *profile; imageProfileIsSame &= !KisLayerUtils::recursiveFindNode(m_d->rootLayer, [profile] (KisNodeSP node) { return *node->colorSpace()->profile() != *profile; }); if (imageProfileIsSame) { dbgImage << "Trying to set the same image profile again" << ppVar(srcColorSpace->profile()->name()) << ppVar(profile->name()); return true; } KUndo2MagicString actionName = kundo2_i18n("Assign Profile"); KisImageSignalVector emitSignals; emitSignals << ProfileChangedSignal; emitSignals << ModifiedSignal; const KoColorSpace *dstColorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); if (!dstColorSpace) return false; KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | (!blockAllUpdates ? KisProcessingApplicator::NO_UI_UPDATES : KisProcessingApplicator::NO_IMAGE_UPDATES), emitSignals, actionName); applicator.applyCommand( new KisImagePrivate::SetImageProjectionColorSpace(dstColorSpace, KisImageWSP(this), KisCommandUtils::FlipFlopCommand::INITIALIZING), KisStrokeJobData::BARRIER); applicator.applyVisitor( new KisAssignProfileProcessingVisitor( srcColorSpace, dstColorSpace), KisStrokeJobData::CONCURRENT); applicator.applyCommand( new KisImagePrivate::SetImageProjectionColorSpace(srcColorSpace, KisImageWSP(this), KisCommandUtils::FlipFlopCommand::FINALIZING), KisStrokeJobData::BARRIER); applicator.end(); return true; } void KisImage::setProjectionColorSpace(const KoColorSpace * colorSpace) { m_d->colorSpace = colorSpace; } const KoColorSpace * KisImage::colorSpace() const { return m_d->colorSpace; } const KoColorProfile * KisImage::profile() const { return colorSpace()->profile(); } double KisImage::xRes() const { return m_d->xres; } double KisImage::yRes() const { return m_d->yres; } void KisImage::setResolution(double xres, double yres) { m_d->xres = xres; m_d->yres = yres; m_d->signalRouter.emitNotification(ResolutionChangedSignal); } QPointF KisImage::documentToPixel(const QPointF &documentCoord) const { return QPointF(documentCoord.x() * xRes(), documentCoord.y() * yRes()); } QPoint KisImage::documentToImagePixelFloored(const QPointF &documentCoord) const { QPointF pixelCoord = documentToPixel(documentCoord); return QPoint(qFloor(pixelCoord.x()), qFloor(pixelCoord.y())); } QRectF KisImage::documentToPixel(const QRectF &documentRect) const { return QRectF(documentToPixel(documentRect.topLeft()), documentToPixel(documentRect.bottomRight())); } QPointF KisImage::pixelToDocument(const QPointF &pixelCoord) const { return QPointF(pixelCoord.x() / xRes(), pixelCoord.y() / yRes()); } QPointF KisImage::pixelToDocument(const QPoint &pixelCoord) const { return QPointF((pixelCoord.x() + 0.5) / xRes(), (pixelCoord.y() + 0.5) / yRes()); } QRectF KisImage::pixelToDocument(const QRectF &pixelCoord) const { return QRectF(pixelToDocument(pixelCoord.topLeft()), pixelToDocument(pixelCoord.bottomRight())); } qint32 KisImage::width() const { return m_d->width; } qint32 KisImage::height() const { return m_d->height; } KisGroupLayerSP KisImage::rootLayer() const { Q_ASSERT(m_d->rootLayer); return m_d->rootLayer; } KisPaintDeviceSP KisImage::projection() const { if (m_d->isolatedRootNode) { return m_d->isolatedRootNode->projection(); } Q_ASSERT(m_d->rootLayer); KisPaintDeviceSP projection = m_d->rootLayer->projection(); Q_ASSERT(projection); return projection; } qint32 KisImage::nlayers() const { QStringList list; list << "KisLayer"; KisCountVisitor visitor(list, KoProperties()); m_d->rootLayer->accept(visitor); return visitor.count(); } qint32 KisImage::nHiddenLayers() const { QStringList list; list << "KisLayer"; KoProperties properties; properties.setProperty("visible", false); KisCountVisitor visitor(list, properties); m_d->rootLayer->accept(visitor); return visitor.count(); } void KisImage::flatten(KisNodeSP activeNode) { KisLayerUtils::flattenImage(this, activeNode); } void KisImage::mergeMultipleLayers(QList mergedNodes, KisNodeSP putAfter) { if (!KisLayerUtils::tryMergeSelectionMasks(this, mergedNodes, putAfter)) { KisLayerUtils::mergeMultipleLayers(this, mergedNodes, putAfter); } } void KisImage::mergeDown(KisLayerSP layer, const KisMetaData::MergeStrategy* strategy) { KisLayerUtils::mergeDown(this, layer, strategy); } void KisImage::flattenLayer(KisLayerSP layer) { KisLayerUtils::flattenLayer(this, layer); } void KisImage::setModified() { m_d->signalRouter.emitNotification(ModifiedSignal); } QImage KisImage::convertToQImage(QRect imageRect, const KoColorProfile * profile) { qint32 x; qint32 y; qint32 w; qint32 h; imageRect.getRect(&x, &y, &w, &h); return convertToQImage(x, y, w, h, profile); } QImage KisImage::convertToQImage(qint32 x, qint32 y, qint32 w, qint32 h, const KoColorProfile * profile) { KisPaintDeviceSP dev = projection(); if (!dev) return QImage(); QImage image = dev->convertToQImage(const_cast(profile), x, y, w, h, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return image; } QImage KisImage::convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile) { if (scaledImageSize.isEmpty()) { return QImage(); } KisPaintDeviceSP dev = new KisPaintDevice(colorSpace()); KisPainter gc; gc.copyAreaOptimized(QPoint(0, 0), projection(), dev, bounds()); gc.end(); double scaleX = qreal(scaledImageSize.width()) / width(); double scaleY = qreal(scaledImageSize.height()) / height(); QPointer updater = new KoDummyUpdater(); KisTransformWorker worker(dev, scaleX, scaleY, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, updater, KisFilterStrategyRegistry::instance()->value("Bicubic")); worker.run(); delete updater; return dev->convertToQImage(profile); } void KisImage::notifyLayersChanged() { m_d->signalRouter.emitNotification(LayersChangedSignal); } QRect KisImage::bounds() const { return QRect(0, 0, width(), height()); } QRect KisImage::effectiveLodBounds() const { QRect boundRect = bounds(); const int lod = currentLevelOfDetail(); if (lod > 0) { KisLodTransform t(lod); boundRect = t.map(boundRect); } return boundRect; } KisPostExecutionUndoAdapter* KisImage::postExecutionUndoAdapter() const { const int lod = currentLevelOfDetail(); return lod > 0 ? m_d->scheduler.lodNPostExecutionUndoAdapter() : &m_d->postExecutionUndoAdapter; } const KUndo2Command* KisImage::lastExecutedCommand() const { return m_d->undoStore->presentCommand(); } void KisImage::setUndoStore(KisUndoStore *undoStore) { m_d->legacyUndoAdapter.setUndoStore(undoStore); m_d->postExecutionUndoAdapter.setUndoStore(undoStore); m_d->undoStore.reset(undoStore); } KisUndoStore* KisImage::undoStore() { return m_d->undoStore.data(); } KisUndoAdapter* KisImage::undoAdapter() const { return &m_d->legacyUndoAdapter; } void KisImage::setDefaultProjectionColor(const KoColor &color) { KIS_ASSERT_RECOVER_RETURN(m_d->rootLayer); m_d->rootLayer->setDefaultProjectionColor(color); } KoColor KisImage::defaultProjectionColor() const { KIS_ASSERT_RECOVER(m_d->rootLayer) { return KoColor(Qt::transparent, m_d->colorSpace); } return m_d->rootLayer->defaultProjectionColor(); } void KisImage::setRootLayer(KisGroupLayerSP rootLayer) { emit sigInternalStopIsolatedModeRequested(); KoColor defaultProjectionColor(Qt::transparent, m_d->colorSpace); if (m_d->rootLayer) { m_d->rootLayer->setGraphListener(0); m_d->rootLayer->disconnect(); KisPaintDeviceSP original = m_d->rootLayer->original(); defaultProjectionColor = original->defaultPixel(); } m_d->rootLayer = rootLayer; m_d->rootLayer->disconnect(); m_d->rootLayer->setGraphListener(this); m_d->rootLayer->setImage(this); setRoot(m_d->rootLayer.data()); this->setDefaultProjectionColor(defaultProjectionColor); } void KisImage::addAnnotation(KisAnnotationSP annotation) { // Find the icc annotation, if there is one vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == annotation->type()) { *it = annotation; return; } ++it; } m_d->annotations.push_back(annotation); } KisAnnotationSP KisImage::annotation(const QString& type) { vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == type) { return *it; } ++it; } return KisAnnotationSP(0); } void KisImage::removeAnnotation(const QString& type) { vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == type) { m_d->annotations.erase(it); return; } ++it; } } vKisAnnotationSP_it KisImage::beginAnnotations() { return m_d->annotations.begin(); } vKisAnnotationSP_it KisImage::endAnnotations() { return m_d->annotations.end(); } void KisImage::notifyAboutToBeDeleted() { emit sigAboutToBeDeleted(); } KisImageSignalRouter* KisImage::signalRouter() { return &m_d->signalRouter; } void KisImage::waitForDone() { requestStrokeEnd(); + KisBusyWaitBroker::instance()->notifyWaitOnImageStarted(this); m_d->scheduler.waitForDone(); + KisBusyWaitBroker::instance()->notifyWaitOnImageEnded(this); } KisStrokeId KisImage::startStroke(KisStrokeStrategy *strokeStrategy) { /** * Ask open strokes to end gracefully. All the strokes clients * (including the one calling this method right now) will get * a notification that they should probably end their strokes. * However this is purely their choice whether to end a stroke * or not. */ if (strokeStrategy->requestsOtherStrokesToEnd()) { requestStrokeEnd(); } /** * Some of the strokes can cancel their work with undoing all the * changes they did to the paint devices. The problem is that undo * stack will know nothing about it. Therefore, just notify it * explicitly */ if (strokeStrategy->clearsRedoOnStart()) { m_d->undoStore->purgeRedoState(); } return m_d->scheduler.startStroke(strokeStrategy); } void KisImage::KisImagePrivate::notifyProjectionUpdatedInPatches(const QRect &rc, QVector &jobs) { KisImageConfig imageConfig(true); int patchWidth = imageConfig.updatePatchWidth(); int patchHeight = imageConfig.updatePatchHeight(); for (int y = 0; y < rc.height(); y += patchHeight) { for (int x = 0; x < rc.width(); x += patchWidth) { QRect patchRect(x, y, patchWidth, patchHeight); patchRect &= rc; KritaUtils::addJobConcurrent(jobs, std::bind(&KisImage::notifyProjectionUpdated, q, patchRect)); } } } bool KisImage::startIsolatedMode(KisNodeSP node) { struct StartIsolatedModeStroke : public KisRunnableBasedStrokeStrategy { StartIsolatedModeStroke(KisNodeSP node, KisImageSP image) : KisRunnableBasedStrokeStrategy(QLatin1String("start-isolated-mode"), kundo2_noi18n("start-isolated-mode")), m_node(node), m_image(image) { this->enableJob(JOB_INIT, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); this->enableJob(JOB_DOSTROKE, true); setClearsRedoOnStart(false); } void initStrokeCallback() { // pass-though node don't have any projection prepared, so we should // explicitly regenerate it before activating isolated mode. m_node->projectionLeaf()->explicitlyRegeneratePassThroughProjection(); m_image->m_d->isolatedRootNode = m_node; emit m_image->sigIsolatedModeChanged(); // the GUI uses our thread to do the color space conversion so we // need to emit this signal in multiple threads QVector jobs; m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds(), jobs); this->runnableJobsInterface()->addRunnableJobs(jobs); m_image->invalidateAllFrames(); } private: KisNodeSP m_node; KisImageSP m_image; }; KisStrokeId id = startStroke(new StartIsolatedModeStroke(node, this)); endStroke(id); return true; } void KisImage::stopIsolatedMode() { if (!m_d->isolatedRootNode) return; struct StopIsolatedModeStroke : public KisRunnableBasedStrokeStrategy { StopIsolatedModeStroke(KisImageSP image) : KisRunnableBasedStrokeStrategy(QLatin1String("stop-isolated-mode"), kundo2_noi18n("stop-isolated-mode")), m_image(image) { this->enableJob(JOB_INIT); this->enableJob(JOB_DOSTROKE, true); setClearsRedoOnStart(false); } void initStrokeCallback() { if (!m_image->m_d->isolatedRootNode) return; //KisNodeSP oldRootNode = m_image->m_d->isolatedRootNode; m_image->m_d->isolatedRootNode = 0; emit m_image->sigIsolatedModeChanged(); m_image->invalidateAllFrames(); // the GUI uses our thread to do the color space conversion so we // need to emit this signal in multiple threads QVector jobs; m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds(), jobs); this->runnableJobsInterface()->addRunnableJobs(jobs); // TODO: Substitute notifyProjectionUpdated() with this code // when update optimization is implemented // // QRect updateRect = bounds() | oldRootNode->extent(); // oldRootNode->setDirty(updateRect); } private: KisImageSP m_image; }; KisStrokeId id = startStroke(new StopIsolatedModeStroke(this)); endStroke(id); } KisNodeSP KisImage::isolatedModeRoot() const { return m_d->isolatedRootNode; } void KisImage::addJob(KisStrokeId id, KisStrokeJobData *data) { KisUpdateTimeMonitor::instance()->reportJobStarted(data); m_d->scheduler.addJob(id, data); } void KisImage::endStroke(KisStrokeId id) { m_d->scheduler.endStroke(id); } bool KisImage::cancelStroke(KisStrokeId id) { return m_d->scheduler.cancelStroke(id); } bool KisImage::KisImagePrivate::tryCancelCurrentStrokeAsync() { return scheduler.tryCancelCurrentStrokeAsync(); } void KisImage::requestUndoDuringStroke() { emit sigUndoDuringStrokeRequested(); } void KisImage::requestStrokeCancellation() { if (!m_d->tryCancelCurrentStrokeAsync()) { emit sigStrokeCancellationRequested(); } } UndoResult KisImage::tryUndoUnfinishedLod0Stroke() { return m_d->scheduler.tryUndoLastStrokeAsync(); } void KisImage::requestStrokeEnd() { emit sigStrokeEndRequested(); emit sigStrokeEndRequestedActiveNodeFiltered(); } void KisImage::requestStrokeEndActiveNode() { emit sigStrokeEndRequested(); } void KisImage::refreshGraph(KisNodeSP root) { refreshGraph(root, bounds(), bounds()); } void KisImage::refreshGraph(KisNodeSP root, const QRect &rc, const QRect &cropRect) { if (!root) root = m_d->rootLayer; m_d->animationInterface->notifyNodeChanged(root.data(), rc, true); m_d->scheduler.fullRefresh(root, rc, cropRect); } void KisImage::initialRefreshGraph() { /** * NOTE: Tricky part. We set crop rect to null, so the clones * will not rely on precalculated projections of their sources */ refreshGraphAsync(0, bounds(), QRect()); waitForDone(); } void KisImage::refreshGraphAsync(KisNodeSP root) { refreshGraphAsync(root, bounds(), bounds()); } void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc) { refreshGraphAsync(root, rc, bounds()); } void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect) { if (!root) root = m_d->rootLayer; m_d->animationInterface->notifyNodeChanged(root.data(), rc, true); m_d->scheduler.fullRefreshAsync(root, rc, cropRect); } void KisImage::requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect) { KIS_ASSERT_RECOVER_RETURN(pseudoFilthy); m_d->animationInterface->notifyNodeChanged(pseudoFilthy.data(), rc, false); m_d->scheduler.updateProjectionNoFilthy(pseudoFilthy, rc, cropRect); } void KisImage::addSpontaneousJob(KisSpontaneousJob *spontaneousJob) { m_d->scheduler.addSpontaneousJob(spontaneousJob); } bool KisImage::hasUpdatesRunning() const { return m_d->scheduler.hasUpdatesRunning(); } void KisImage::setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter) { // update filters are *not* recursive! KIS_ASSERT_RECOVER_NOOP(!filter || !m_d->projectionUpdatesFilter); m_d->projectionUpdatesFilter = filter; } KisProjectionUpdatesFilterSP KisImage::projectionUpdatesFilter() const { return m_d->projectionUpdatesFilter; } void KisImage::disableDirtyRequests() { setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP(new KisDropAllProjectionUpdatesFilter())); } void KisImage::enableDirtyRequests() { setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP()); } void KisImage::disableUIUpdates() { m_d->disableUIUpdateSignals.ref(); } void KisImage::notifyBatchUpdateStarted() { m_d->signalRouter.emitNotifyBatchUpdateStarted(); } void KisImage::notifyBatchUpdateEnded() { m_d->signalRouter.emitNotifyBatchUpdateEnded(); } void KisImage::notifyUIUpdateCompleted(const QRect &rc) { notifyProjectionUpdated(rc); } QVector KisImage::enableUIUpdates() { m_d->disableUIUpdateSignals.deref(); QRect rect; QVector postponedUpdates; while (m_d->savedDisabledUIUpdates.pop(rect)) { postponedUpdates.append(rect); } return postponedUpdates; } void KisImage::notifyProjectionUpdated(const QRect &rc) { KisUpdateTimeMonitor::instance()->reportUpdateFinished(rc); if (!m_d->disableUIUpdateSignals) { int lod = currentLevelOfDetail(); QRect dirtyRect = !lod ? rc : KisLodTransform::upscaledRect(rc, lod); if (dirtyRect.isEmpty()) return; emit sigImageUpdated(dirtyRect); } else { m_d->savedDisabledUIUpdates.push(rc); } } void KisImage::setWorkingThreadsLimit(int value) { m_d->scheduler.setThreadsLimit(value); } int KisImage::workingThreadsLimit() const { return m_d->scheduler.threadsLimit(); } void KisImage::notifySelectionChanged() { /** * The selection is calculated asynchronously, so it is not * handled by disableUIUpdates() and other special signals of * KisImageSignalRouter */ m_d->legacyUndoAdapter.emitSelectionChanged(); /** * Editing of selection masks doesn't necessary produce a * setDirty() call, so in the end of the stroke we need to request * direct update of the UI's cache. */ if (m_d->isolatedRootNode && dynamic_cast(m_d->isolatedRootNode.data())) { notifyProjectionUpdated(bounds()); } } void KisImage::requestProjectionUpdateImpl(KisNode *node, const QVector &rects, const QRect &cropRect) { if (rects.isEmpty()) return; m_d->scheduler.updateProjection(node, rects, cropRect); } void KisImage::requestProjectionUpdate(KisNode *node, const QVector &rects, bool resetAnimationCache) { if (m_d->projectionUpdatesFilter && m_d->projectionUpdatesFilter->filter(this, node, rects, resetAnimationCache)) { return; } if (resetAnimationCache) { m_d->animationInterface->notifyNodeChanged(node, rects, false); } /** * Here we use 'permitted' instead of 'active' intentively, * because the updates may come after the actual stroke has been * finished. And having some more updates for the stroke not * supporting the wrap-around mode will not make much harm. */ if (m_d->wrapAroundModePermitted) { QVector allSplitRects; const QRect boundRect = effectiveLodBounds(); Q_FOREACH (const QRect &rc, rects) { KisWrappedRect splitRect(rc, boundRect); allSplitRects.append(splitRect); } requestProjectionUpdateImpl(node, allSplitRects, boundRect); } else { requestProjectionUpdateImpl(node, rects, bounds()); } KisNodeGraphListener::requestProjectionUpdate(node, rects, resetAnimationCache); } void KisImage::invalidateFrames(const KisTimeRange &range, const QRect &rect) { m_d->animationInterface->invalidateFrames(range, rect); } void KisImage::requestTimeSwitch(int time) { m_d->animationInterface->requestTimeSwitchNonGUI(time); } KisNode *KisImage::graphOverlayNode() const { return m_d->overlaySelectionMask.data(); } QList KisImage::compositions() { return m_d->compositions; } void KisImage::addComposition(KisLayerCompositionSP composition) { m_d->compositions.append(composition); } void KisImage::removeComposition(KisLayerCompositionSP composition) { m_d->compositions.removeAll(composition); } bool checkMasksNeedConversion(KisNodeSP root, const QRect &bounds) { KisSelectionMask *mask = dynamic_cast(root.data()); if (mask && (!bounds.contains(mask->paintDevice()->exactBounds()) || mask->selection()->hasShapeSelection())) { return true; } KisNodeSP node = root->firstChild(); while (node) { if (checkMasksNeedConversion(node, bounds)) { return true; } node = node->nextSibling(); } return false; } void KisImage::setWrapAroundModePermitted(bool value) { if (m_d->wrapAroundModePermitted != value) { requestStrokeEnd(); } m_d->wrapAroundModePermitted = value; if (m_d->wrapAroundModePermitted && checkMasksNeedConversion(root(), bounds())) { KisProcessingApplicator applicator(this, root(), KisProcessingApplicator::RECURSIVE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Crop Selections")); KisProcessingVisitorSP visitor = new KisCropSelectionsProcessingVisitor(bounds()); applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } } bool KisImage::wrapAroundModePermitted() const { return m_d->wrapAroundModePermitted; } bool KisImage::wrapAroundModeActive() const { return m_d->wrapAroundModePermitted && m_d->scheduler.wrapAroundModeSupported(); } void KisImage::setDesiredLevelOfDetail(int lod) { if (m_d->blockLevelOfDetail) { qWarning() << "WARNING: KisImage::setDesiredLevelOfDetail()" << "was called while LoD functionality was being blocked!"; return; } m_d->scheduler.setDesiredLevelOfDetail(lod); } int KisImage::currentLevelOfDetail() const { if (m_d->blockLevelOfDetail) { return 0; } return m_d->scheduler.currentLevelOfDetail(); } void KisImage::setLevelOfDetailBlocked(bool value) { KisImageBarrierLockerRaw l(this); if (value && !m_d->blockLevelOfDetail) { m_d->scheduler.setDesiredLevelOfDetail(0); } m_d->blockLevelOfDetail = value; } void KisImage::explicitRegenerateLevelOfDetail() { if (!m_d->blockLevelOfDetail) { m_d->scheduler.explicitRegenerateLevelOfDetail(); } } bool KisImage::levelOfDetailBlocked() const { return m_d->blockLevelOfDetail; } void KisImage::nodeCollapsedChanged(KisNode * node) { Q_UNUSED(node); emit sigNodeCollapsedChanged(); } KisImageAnimationInterface* KisImage::animationInterface() const { return m_d->animationInterface; } void KisImage::setProofingConfiguration(KisProofingConfigurationSP proofingConfig) { m_d->proofingConfig = proofingConfig; emit sigProofingConfigChanged(); } KisProofingConfigurationSP KisImage::proofingConfiguration() const { if (m_d->proofingConfig) { return m_d->proofingConfig; } return KisProofingConfigurationSP(); } QPointF KisImage::mirrorAxesCenter() const { return m_d->axesCenter; } void KisImage::setMirrorAxesCenter(const QPointF &value) const { m_d->axesCenter = value; } void KisImage::setAllowMasksOnRootNode(bool value) { m_d->allowMasksOnRootNode = value; } bool KisImage::allowMasksOnRootNode() const { return m_d->allowMasksOnRootNode; } diff --git a/libs/image/kis_legacy_undo_adapter.cpp b/libs/image/kis_legacy_undo_adapter.cpp index 8cdcd64be7..e988a85a8c 100644 --- a/libs/image/kis_legacy_undo_adapter.cpp +++ b/libs/image/kis_legacy_undo_adapter.cpp @@ -1,77 +1,75 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_legacy_undo_adapter.h" #include "kis_image.h" KisLegacyUndoAdapter::KisLegacyUndoAdapter(KisUndoStore *undoStore, KisImageWSP image) : KisUndoAdapter(undoStore, image.data()), m_image(image), m_macroCounter(0) { } const KUndo2Command* KisLegacyUndoAdapter::presentCommand() { return undoStore()->presentCommand(); } void KisLegacyUndoAdapter::undoLastCommand() { undoStore()->undoLastCommand(); } void KisLegacyUndoAdapter::addCommand(KUndo2Command *command) { if(!command) return; if(m_macroCounter) { undoStore()->addCommand(command); } else { - // TODO: add feedback m_image->barrierLock(); undoStore()->addCommand(command); m_image->unlock(); } } void KisLegacyUndoAdapter::beginMacro(const KUndo2MagicString& macroName) { if(!m_macroCounter) { - // TODO: add feedback m_image->barrierLock(); } m_macroCounter++; undoStore()->beginMacro(macroName); } void KisLegacyUndoAdapter::endMacro() { m_macroCounter--; if(!m_macroCounter) { m_image->unlock(); } undoStore()->endMacro(); } diff --git a/libs/image/tests/kis_layer_test.cpp b/libs/image/tests/kis_layer_test.cpp index 4d8cb34f23..2c5cf8f97e 100644 --- a/libs/image/tests/kis_layer_test.cpp +++ b/libs/image/tests/kis_layer_test.cpp @@ -1,315 +1,315 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_layer_test.h" #include #include #include #include #include #include #include "kis_paint_device.h" #include "kis_selection.h" #include "kis_filter_mask.h" #include "kis_transparency_mask.h" #include "kis_group_layer.h" #include "kis_paint_layer.h" #include "filter/kis_filter.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" void KisLayerTest::testCreation() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 512, 512, colorSpace, "layer test"); - image->lock(); + image->waitForDone(); KisLayerSP layer = new TestLayer(image, "test", OPACITY_OPAQUE_U8); QCOMPARE(layer->name(), QString("test")); QCOMPARE(layer->opacity(), OPACITY_OPAQUE_U8); QCOMPARE(layer->image().data(), image.data()); QCOMPARE(layer->colorSpace(), image->colorSpace()); QCOMPARE(layer->visible(), true); QCOMPARE(layer->userLocked(), false); QCOMPARE(layer->temporary(), false); image->addNode(layer, image->rootLayer()); QBitArray channels(4); channels.fill(true); channels.setBit(1, false); layer->setChannelFlags(channels); QVERIFY(layer->channelFlags().count() == 4); QCOMPARE(layer->channelFlags().at(0), true); QCOMPARE(layer->channelFlags().at(1), false); QCOMPARE(layer->channelFlags().at(2), true); QCOMPARE(layer->channelFlags().at(3), true); layer->setOpacity(OPACITY_TRANSPARENT_U8); QCOMPARE(layer->opacity(), OPACITY_TRANSPARENT_U8); layer->setPercentOpacity(100); QCOMPARE(layer->opacity(), OPACITY_OPAQUE_U8); layer->setPercentOpacity(0); QCOMPARE(layer->opacity(), OPACITY_TRANSPARENT_U8); } void KisLayerTest::testOrdering() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 512, 512, colorSpace, "layer test"); - image->lock(); + image->waitForDone(); KisLayerSP layer1 = new TestLayer(image, "layer1", OPACITY_OPAQUE_U8); KisLayerSP layer2 = new TestLayer(image, "layer2", OPACITY_OPAQUE_U8); KisLayerSP layer3 = new TestLayer(image, "layer3", OPACITY_OPAQUE_U8); QVERIFY(layer1->name() == "layer1"); QVERIFY(layer2->name() == "layer2"); QVERIFY(layer3->name() == "layer3"); /* +---------+ | layer 2 | | layer 3 | | layer 1 | |root | +---------+ */ QVERIFY(image->addNode(layer1, image->rootLayer())); QVERIFY(image->addNode(layer2, image->rootLayer())); QVERIFY(image->addNode(layer3, image->rootLayer(), layer1)); QCOMPARE((int) image->nlayers(), 4); QVERIFY(layer1->parent() == image->root()); QVERIFY(layer2->parent() == image->root()); QVERIFY(layer3->parent() == image->root()); QVERIFY(image->rootLayer()->firstChild() == layer1.data()); QVERIFY(image->rootLayer()->lastChild() == layer2.data()); QVERIFY(image->rootLayer()->at(0) == layer1.data()); QVERIFY(image->rootLayer()->at(1) == layer3.data()); QVERIFY(image->rootLayer()->at(2) == layer2.data()); QVERIFY(image->rootLayer()->index(layer1) == 0); QVERIFY(image->rootLayer()->index(layer3) == 1); QVERIFY(image->rootLayer()->index(layer2) == 2); QVERIFY(layer3->prevSibling() == layer1.data()); QVERIFY(layer2->prevSibling() == layer3.data()); QVERIFY(layer1->prevSibling() == 0); QVERIFY(layer3->nextSibling() == layer2.data()); QVERIFY(layer2->nextSibling() == 0); QVERIFY(layer1->nextSibling() == layer3.data()); /* +---------+ | layer 3 | | layer 2 | | layer 1 | |root | +---------+ */ QVERIFY(image->moveNode(layer2, image->rootLayer(), layer1)); QVERIFY(image->rootLayer()->at(0) == layer1.data()); QVERIFY(image->rootLayer()->at(1) == layer2.data()); QVERIFY(image->rootLayer()->at(2) == layer3.data()); QVERIFY(image->rootLayer()->firstChild() == layer1.data()); QVERIFY(image->rootLayer()->lastChild() == layer3.data()); QVERIFY(image->rootLayer()->index(layer1) == 0); QVERIFY(image->rootLayer()->index(layer2) == 1); QVERIFY(image->rootLayer()->index(layer3) == 2); QVERIFY(layer3->prevSibling() == layer2.data()); QVERIFY(layer2->prevSibling() == layer1.data()); QVERIFY(layer1->prevSibling() == 0); QVERIFY(layer3->nextSibling() == 0); QVERIFY(layer2->nextSibling() == layer3.data()); QVERIFY(layer1->nextSibling() == layer2.data()); } void KisLayerTest::testMoveNode() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 512, 512, colorSpace, "layer test"); - image->lock(); + image->waitForDone(); KisLayerSP node1 = new TestLayer(image, "layer1", OPACITY_OPAQUE_U8); KisLayerSP node2 = new TestLayer(image, "layer2", OPACITY_OPAQUE_U8); KisLayerSP node3 = new TestLayer(image, "layer3", OPACITY_OPAQUE_U8); node1->setName("node1"); node2->setName("node2"); node3->setName("node3"); QVERIFY(image->addNode(node1)); QVERIFY(image->addNode(node2)); QVERIFY(image->addNode(node3)); QVERIFY(image->root()->at(0) == node1.data()); QVERIFY(image->root()->at(1) == node2.data()); QVERIFY(image->root()->at(2) == node3.data()); QVERIFY(image->moveNode(node3, image->root(), node1)); QVERIFY(image->root()->at(0) == node1.data()); QVERIFY(image->root()->at(1) == node3.data()); QVERIFY(image->root()->at(2) == node2.data()); } void KisLayerTest::testMoveLayer() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 512, 512, colorSpace, "layer test"); - image->lock(); + image->waitForDone(); KisLayerSP node1 = new TestLayer(image, "layer1", OPACITY_OPAQUE_U8); KisLayerSP node2 = new TestLayer(image, "layer2", OPACITY_OPAQUE_U8); KisLayerSP node3 = new TestLayer(image, "layer3", OPACITY_OPAQUE_U8); node1->setName("node1"); node2->setName("node2"); node3->setName("node3"); QVERIFY(image->addNode(node1)); QVERIFY(image->addNode(node2)); QVERIFY(image->addNode(node3)); QVERIFY(image->root()->at(0) == node1.data()); QVERIFY(image->root()->at(1) == node2.data()); QVERIFY(image->root()->at(2) == node3.data()); QVERIFY(image->moveNode(node3, image->rootLayer(), node1)); QVERIFY(image->root()->at(0) == node1.data()); QVERIFY(image->root()->at(1) == node3.data()); QVERIFY(image->root()->at(2) == node2.data()); } /* +----------+ |root | | paint 1 | | fmask2 | | fmask1 | +----------+ */ void KisLayerTest::testMasksChangeRect() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 512, 512, colorSpace, "walker test"); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); image->addNode(paintLayer1, image->rootLayer()); KisFilterMaskSP filterMask1 = new KisFilterMask(); KisFilterMaskSP filterMask2 = new KisFilterMask(); KisFilterSP filter = KisFilterRegistry::instance()->value("blur"); Q_ASSERT(filter); KisFilterConfigurationSP configuration1 = filter->defaultConfiguration(); KisFilterConfigurationSP configuration2 = filter->defaultConfiguration(); filterMask1->setFilter(configuration1); filterMask2->setFilter(configuration2); image->addNode(filterMask1, paintLayer1); image->addNode(filterMask2, paintLayer1); QVERIFY(paintLayer1->hasEffectMasks()); QRect testRect(10, 10, 100, 100); QRect resultRect; resultRect = paintLayer1->changeRect(testRect, KisNode::N_FILTHY); QVERIFY2(resultRect == QRect(0, 0, 120, 120), "KisNode::N_FILTHY node should take masks into account"); resultRect = paintLayer1->changeRect(testRect, KisNode::N_ABOVE_FILTHY); QVERIFY2(resultRect == testRect, "KisNode::N_ABOVE_FILTHY node should NOT take " "masks into account"); /** * KisNode::N_BELOW_FILTHY, KisNode::N_FILTHY_PROJECTION * should not be use by the caller, because the walker * should not visit these node on a forward way. * So the behavior here is undefined. * * resultRect = paintLayer1->changeRect(testRect, KisNode::N_BELOW_FILTHY); * resultRect = paintLayer1->changeRect(testRect, KisNode::N_FILTHY_PROJECTION); */ } void KisLayerTest::testMoveLayerWithMaskThreaded() { /** * This test ensures that the layer's original() can be moved * while its projection is still being updated */ const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 2000, 2000, colorSpace, "walker test"); KisLayerSP paintLayer = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); image->addNode(paintLayer, image->rootLayer()); paintLayer->paintDevice()->fill(image->bounds(), KoColor(Qt::black, colorSpace)); KisTransparencyMaskSP transpMask = new KisTransparencyMask(); transpMask->initSelection(paintLayer); image->addNode(transpMask, paintLayer); for(int i = 0; i < 100; i++) { paintLayer->setDirty(); QTest::qSleep(1 + (qrand() & 63)); paintLayer->setX((i*67) % 1873); paintLayer->setY((i*23) % 1873); } } QTEST_MAIN(KisLayerTest) diff --git a/libs/image/tests/kis_lazy_brush_test.cpp b/libs/image/tests/kis_lazy_brush_test.cpp index 4bd59adb8c..96316de0e9 100644 --- a/libs/image/tests/kis_lazy_brush_test.cpp +++ b/libs/image/tests/kis_lazy_brush_test.cpp @@ -1,1400 +1,1414 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_lazy_brush_test.h" #include #include "kis_debug.h" #include "kis_fill_painter.h" #include #include #include #include #include #include #include #include #include #include "lazybrush/kis_lazy_fill_graph.h" #if 0 int doSomething() { using namespace boost; typedef adjacency_list_traits < vecS, vecS, directedS > Traits; typedef adjacency_list < vecS, vecS, directedS, property < vertex_name_t, std::string, property < vertex_index_t, long, property < vertex_color_t, boost::default_color_type, property < vertex_distance_t, long, property < vertex_predecessor_t, Traits::edge_descriptor > > > > >, property < edge_capacity_t, long, property < edge_residual_capacity_t, long, property < edge_reverse_t, Traits::edge_descriptor > > > > Graph; Graph g; property_map < Graph, edge_capacity_t >::type capacity = get(edge_capacity, g); property_map < Graph, edge_residual_capacity_t >::type residual_capacity = get(edge_residual_capacity, g); property_map < Graph, edge_reverse_t >::type rev = get(edge_reverse, g); Traits::vertex_descriptor s, t; read_dimacs_max_flow(g, capacity, rev, s, t); std::vector color(num_vertices(g)); std::vector distance(num_vertices(g)); long flow = boykov_kolmogorov_max_flow(g ,s, t); std::cout << "c The total flow:" << std::endl; std::cout << "s " << flow << std::endl << std::endl; std::cout << "c flow values:" << std::endl; graph_traits < Graph >::vertex_iterator u_iter, u_end; graph_traits < Graph >::out_edge_iterator ei, e_end; for (boost::tie(u_iter, u_end) = vertices(g); u_iter != u_end; ++u_iter) { qDebug() << ppVar(get(vertex_color, g)[*u_iter]); for (boost::tie(ei, e_end) = out_edges(*u_iter, g); ei != e_end; ++ei) { if (capacity[*ei] > 0) { std::cout << "f " << *u_iter << " " << target(*ei, g) << " " << (capacity[*ei] - residual_capacity[*ei]) << std::endl; } } } return EXIT_SUCCESS; } #include #include #include template class ImageToCapacityMap { typedef typename boost::graph_traits::vertex_descriptor VertexDescriptor; typedef typename boost::graph_traits::edge_descriptor EdgeDescriptor; public: typedef EdgeDescriptor key_type; typedef float value_type; typedef const float& reference; typedef boost::readable_property_map_tag category; ImageToCapacityMap(Graph &graph, const QImage &image) : m_graph(graph), m_image(image) { m_bits = reinterpret_cast(m_image.constBits()); m_minIntensity = std::numeric_limits::max(); m_maxIntensity = std::numeric_limits::min(); int totalInt = 0; const int numPixels = m_image.width() * m_image.height(); m_intensities.resize(numPixels); for (int i = 0; i < numPixels; i++) { const int v = qGray(m_bits[i]); m_intensities[i] = v; qDebug() << ppVar(i) << ppVar(v); totalInt += v; if (m_minIntensity > v) m_minIntensity = v; if (m_maxIntensity < v) m_maxIntensity = v; } qDebug() << ppVar(totalInt); m_pixelSize = 4; m_rowStride = m_image.width() * m_pixelSize; qDebug() << ppVar(m_rowStride) << ppVar(m_image.size()) << ppVar(m_image.bytesPerLine()); } Graph &m_graph; QImage m_image; QVector m_intensities; const QRgb *m_bits; int m_rowStride; int m_pixelSize; int m_minIntensity; int m_maxIntensity; }; template typename ImageToCapacityMap::value_type get(const ImageToCapacityMap &map, const typename ImageToCapacityMap::key_type &key) { typedef typename boost::graph_traits::vertex_descriptor VertexDescriptor; VertexDescriptor src = source(key, map.m_graph); VertexDescriptor tgt = target(key, map.m_graph); const int x0 = src[0]; const int y0 = src[1]; const int x1 = tgt[0]; const int y1 = tgt[1]; const int k = 2 * (map.m_image.width() + map.m_image.height()); const int maxDimension = qMax(map.m_image.width(), map.m_image.height()); //if (x0 <= 5 && x1 <= 5 && y0 == 0 && y1 ==0) return 255; //if (x0 >= 5 && x1 >= 5 && y0 == 8 && y1 ==8) return 255; //const QRgb *p0 = map.m_bits + y0 * map.m_rowStride + x0 * map.m_pixelSize; //const QRgb *p1 = map.m_bits + y1 * map.m_rowStride + x1 * map.m_pixelSize; //float value = 255.0 - qAbs(qGray(*p0) - qGray(*p1)); if ((!x0 && !y0) || (!x1 && !y1) || (x0 == map.m_image.width() - 1 && y0 == map.m_image.height() - 1) || (x1 == map.m_image.width() - 1 && y1 == map.m_image.height() - 1)) { qreal value = maxDimension * k; qDebug() << x0 << y0 << "->" << x1 << y1 << value; return value; } const int i0 = map.m_intensities[x0 + y0 * map.m_image.width()]; const int i1 = map.m_intensities[x1 + y1 * map.m_image.width()]; const int diff = qAbs(i0 - i1); qreal normDiff = qreal(diff) / (map.m_maxIntensity - map.m_minIntensity); float value = 1.0 + k * (1.0 - normDiff) + qMin(y0, y1); qDebug() << x0 << y0 << "->" << x1 << y1 << value;// << ppVar(normDiff); return value; } template ImageToCapacityMap MakeImageToCapacityMap(Graph &graph, const QImage &image) { return ImageToCapacityMap(graph, image); } int doSomethingElse() { const unsigned int D = 2; typedef boost::grid_graph Graph; typedef boost::graph_traits::vertex_descriptor VertexDescriptor; typedef boost::graph_traits::edge_descriptor EdgeDescriptor;//ADDED typedef boost::graph_traits::vertices_size_type VertexIndex; typedef boost::graph_traits::edges_size_type EdgeIndex; QImage image(QSize(9,9), QImage::Format_ARGB32); QPainter gc(&image); gc.fillRect(image.rect(), Qt::white); gc.fillRect(QRect(0,4,2,1), Qt::blue); //gc.fillRect(QRect(0,5,2,1), Qt::blue); gc.fillRect(QRect(6,4,3,1), Qt::blue); gc.end(); image.save("graph_img.png"); boost::array lengths = { { image.width(), image.height() } }; Graph graph(lengths, false); std::vector groups(num_vertices(graph), 0); std::vector residual_capacity(num_edges(graph), 0); auto capacityMap = MakeImageToCapacityMap(graph, image); float capSum = 0; BGL_FORALL_EDGES(e,graph,Graph) { VertexDescriptor src = source(e,graph); VertexDescriptor tgt = target(e,graph); //VertexIndex source_idx = get(boost::vertex_index,graph,src); //VertexIndex target_idx = get(boost::vertex_index,graph,tgt); //EdgeIndex edge_idx = get(boost::edge_index,graph,e); // qDebug() << "(" << src[0] << src[1] << ")" // << "->" // << "(" << tgt[0] << tgt[1] << ")" // << get(capacityMap, e); //capSum += get(capacityMap, e); } //qDebug() << ppVar(capSum); BGL_FORALL_VERTICES(v,graph,Graph) { //qDebug() << ppVar(v[0]) << ppVar(v[1]); /* VertexDescriptor src = source(e,graph); VertexDescriptor tgt = target(e,graph); VertexIndex source_idx = get(boost::vertex_index,graph,src); VertexIndex target_idx = get(boost::vertex_index,graph,tgt); EdgeIndex edge_idx = get(boost::edge_index,graph,e); capacity[edge_idx] = 255.0f - fabs(pixel_intensity[source_idx]-pixel_intensity[target_idx]); //you should change this to your "gradiant or intensity or something" reverse_edges[edge_idx]=edge(tgt,src,graph).first;//ADDED */ } const int t_index = image.width() * image.height() - 1; VertexDescriptor s=vertex(0,graph), t=vertex(t_index,graph); float maxFlow = boykov_kolmogorov_max_flow(graph, capacityMap, make_iterator_property_map(&residual_capacity[0], get(boost::edge_index, graph)), get(boost::edge_reverse, graph), make_iterator_property_map(&groups[0], get(boost::vertex_index, graph)), get(boost::vertex_index, graph), s, t ); qDebug() << ppVar(maxFlow); const int cell = 10; const int half = cell / 2; QImage result(QSize(cell * lengths[0], cell * lengths[1]), QImage::Format_ARGB32); QPainter resultPainter(&result); BGL_FORALL_VERTICES(v, graph, Graph) { const int x = v[0]; const int y = v[1]; VertexIndex vertex_idx = get(boost::vertex_index, graph, v); int label = groups[vertex_idx]; QColor color = label == 0 ? Qt::blue : label == 4 ? Qt::green : label == 1 ? Qt::gray : Qt::red; QRect rc(cell * x, cell * y, cell, cell); resultPainter.fillRect(rc, color); } BGL_FORALL_EDGES(e,graph,Graph) { EdgeIndex egdeIndex = get(boost::edge_index, graph, e); const int cap = residual_capacity[egdeIndex]; QColor color(Qt::black); if (cap != 0) { const int fullCap = get(capacityMap, e); const int gray = qreal(cap) / fullCap * 50.0; color.setAlpha(gray); } VertexDescriptor src = source(e,graph); VertexDescriptor tgt = target(e,graph); QPoint p0(half + cell * src[0], half + cell * src[1]); QPoint p1(half + cell * tgt[0], half + cell * tgt[1]); resultPainter.setPen(QPen(color, 2)); resultPainter.drawLine(p0, p1); // qDebug() << "(" << src[0] << src[1] << ")" // << "->" // << "(" << tgt[0] << tgt[1] << ")" // << residual_capacity[egdeIndex]; } result.save("result.png"); return 0; } void KisLazyBrushTest::test() { doSomethingElse(); } #endif /*0*/ bool verifyNormalVertex(const KisLazyFillGraph::vertex_descriptor &v, int x, int y) { bool result = v.type == KisLazyFillGraph::vertex_descriptor::NORMAL && v.x == x && v.y == y; if (!result) { qDebug() << ppVar(v) << ppVar(x) << ppVar(y); } return result; } bool verifyVertexIndex(KisLazyFillGraph &g, KisLazyFillGraph::vertex_descriptor v, KisLazyFillGraph::vertices_size_type index) { bool result = true; const KisLazyFillGraph::vertices_size_type actualIndex = g.index_of(v); const KisLazyFillGraph::vertex_descriptor actualVertex = g.vertex_at(index); if (index >= 0) { result &= v == actualVertex; } result &= index == actualIndex; if (!result) { qDebug() << "Vertex failed:"; qDebug() << v << "->" << actualIndex << "( expected:" << index << ")"; qDebug() << index << "->" << actualVertex << "( expected:" << v << ")"; } return result; } bool verifyEdgeIndex(KisLazyFillGraph &g, KisLazyFillGraph::edge_descriptor v, KisLazyFillGraph::edges_size_type index) { bool result = true; const KisLazyFillGraph::edges_size_type actualIndex = g.index_of(v); const KisLazyFillGraph::edge_descriptor actualEdge = g.edge_at(index); if (index >= 0) { result &= v == actualEdge; } result &= index == actualIndex; if (!result) { qDebug() << "Edge failed:"; qDebug() << v << "->" << actualIndex << "( expected:" << index << ")"; qDebug() << index << "->" << actualEdge << "( expected:" << v << ")"; } return result; } void KisLazyBrushTest::testGraph() { QRect mainRect(10, 10, 100, 100); QRect aLabelRect(30, 20, 20, 20); QRect bLabelRect(60, 60, 20, 20); KisLazyFillGraph g(mainRect, aLabelRect, bLabelRect); const int numVertices = g.num_vertices(); const int numEdges = g.num_edges(); qDebug() << ppVar(numVertices); qDebug() << ppVar(numEdges); QCOMPARE(numVertices, 10002); QCOMPARE(numEdges, 41200); for (int i = 0; i < numVertices; i++) { KisLazyFillGraph::vertex_descriptor vertex = g.vertex_at(i); int newVertexIndex = g.index_of(vertex); QCOMPARE(newVertexIndex, i); } for (int i = 0; i < numEdges; i++) { KisLazyFillGraph::edge_descriptor edge = g.edge_at(i); int newEdgeIndex = g.index_of(edge); QCOMPARE(newEdgeIndex, i); } KisLazyFillGraph::vertex_descriptor v1(10, 10); KisLazyFillGraph::vertex_descriptor v2(11, 10); KisLazyFillGraph::vertex_descriptor v3(10, 11); KisLazyFillGraph::vertex_descriptor v4(11, 11); KisLazyFillGraph::vertex_descriptor v5(12, 10); KisLazyFillGraph::vertex_descriptor v6(31, 21); KisLazyFillGraph::vertex_descriptor v7(61, 61); KisLazyFillGraph::vertex_descriptor v8(9, 10); KisLazyFillGraph::vertex_descriptor vA(0, 0, KisLazyFillGraph::vertex_descriptor::LABEL_A); KisLazyFillGraph::vertex_descriptor vB(0, 0, KisLazyFillGraph::vertex_descriptor::LABEL_B); // Verify vertex index mapping QVERIFY(verifyVertexIndex(g, v1, 0)); QVERIFY(verifyVertexIndex(g, v2, 1)); QVERIFY(verifyVertexIndex(g, v3, 100)); QVERIFY(verifyVertexIndex(g, v4, 101)); QVERIFY(verifyVertexIndex(g, v5, 2)); QVERIFY(verifyVertexIndex(g, v6, 1121)); QVERIFY(verifyVertexIndex(g, v7, 5151)); QVERIFY(verifyVertexIndex(g, v8, -1)); QVERIFY(verifyVertexIndex(g, vA, numVertices - 2)); QVERIFY(verifyVertexIndex(g, vB, numVertices - 1)); // Verify edge index mapping QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, v2), 0)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v3, v4), 99)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, v3), 19800)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v2, v4), 19801)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v2, v1), 9900)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v4, v3), 9999)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v3, v1), 29700)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v4, v2), 29701)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vA, vA), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vB, vB), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, v8), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, v4), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v4, v1), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, v5), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v6, vA), 39621)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vA, v6), 40021)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, vA), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v7, vA), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vA, vB), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vB, vA), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v7, vB), 40421)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vB, v7), 40821)); QCOMPARE(g.out_degree(v1), long(2)); QVERIFY(verifyNormalVertex(g.out_edge_at(v1, 0).second, 11, 10)); QVERIFY(verifyNormalVertex(g.out_edge_at(v1, 1).second, 10, 11)); QCOMPARE(g.out_degree(v4), long(4)); QVERIFY(verifyNormalVertex(g.out_edge_at(v4, 0).second, 10, 11)); QVERIFY(verifyNormalVertex(g.out_edge_at(v4, 1).second, 11, 10)); QVERIFY(verifyNormalVertex(g.out_edge_at(v4, 2).second, 12, 11)); QVERIFY(verifyNormalVertex(g.out_edge_at(v4, 3).second, 11, 12)); QCOMPARE(g.out_degree(v6), long(5)); QVERIFY(verifyNormalVertex(g.out_edge_at(v6, 0).second, 30, 21)); QVERIFY(verifyNormalVertex(g.out_edge_at(v6, 1).second, 31, 20)); QVERIFY(verifyNormalVertex(g.out_edge_at(v6, 2).second, 32, 21)); QVERIFY(verifyNormalVertex(g.out_edge_at(v6, 3).second, 31, 22)); QCOMPARE(g.out_edge_at(v6, 4).second, vA); QCOMPARE(g.out_degree(v7), long(5)); QVERIFY(verifyNormalVertex(g.out_edge_at(v7, 0).second, 60, 61)); QVERIFY(verifyNormalVertex(g.out_edge_at(v7, 1).second, 61, 60)); QVERIFY(verifyNormalVertex(g.out_edge_at(v7, 2).second, 62, 61)); QVERIFY(verifyNormalVertex(g.out_edge_at(v7, 3).second, 61, 62)); QCOMPARE(g.out_edge_at(v7, 4).second, vB); QCOMPARE(g.out_degree(vA), long(400)); QVERIFY(verifyNormalVertex(g.out_edge_at(vA, 0).second, 30, 20)); QVERIFY(verifyNormalVertex(g.out_edge_at(vA, 1).second, 31, 20)); } void KisLazyBrushTest::testGraphMultilabel() { QRect mainRect(10, 10, 100, 100); QRect aLabelRect1(30, 20, 20, 20); QRect aLabelRect2(70, 30, 20, 20); QRect bLabelRect1(60, 60, 20, 20); QRect bLabelRect2(20, 80, 20, 20); QRect bLabelRect3(60, 40, 20, 30); - KisRegion aLabelRegion({aLabelRect1, aLabelRect2}); + QRegion aLabelRegion; + aLabelRegion += aLabelRect1; + aLabelRegion += aLabelRect2; - KisRegion bLabelRegion({bLabelRect1, bLabelRect2, bLabelRect2}); + QRegion bLabelRegion; + bLabelRegion += bLabelRect1; + bLabelRegion += bLabelRect2; + bLabelRegion += bLabelRect3; - KisLazyFillGraph g(mainRect, aLabelRegion, bLabelRegion); + KisLazyFillGraph g(mainRect, KisRegion::fromQRegion(aLabelRegion), KisRegion::fromQRegion(bLabelRegion)); const int numVertices = g.num_vertices(); const int numEdges = g.num_edges(); qDebug() << ppVar(numVertices); qDebug() << ppVar(numEdges); QCOMPARE(numVertices, 10002); QCOMPARE(numEdges, 43600); for (int i = 0; i < numVertices; i++) { KisLazyFillGraph::vertex_descriptor vertex = g.vertex_at(i); int newVertexIndex = g.index_of(vertex); QCOMPARE(newVertexIndex, i); } for (int i = 0; i < numEdges; i++) { KisLazyFillGraph::edge_descriptor edge = g.edge_at(i); int newEdgeIndex = g.index_of(edge); if (i != newEdgeIndex) { qDebug() << ppVar(edge); } QCOMPARE(newEdgeIndex, i); } typedef KisLazyFillGraph::vertex_descriptor Vert; Vert v1(10, 10); Vert v2(11, 10); Vert v3(10, 11); Vert v4(11, 11); Vert v5(12, 10); Vert v6(31, 21); Vert v7(61, 61); Vert v8(9, 10); Vert vA(0, 0, Vert::LABEL_A); Vert vB(0, 0, Vert::LABEL_B); // Verify vertex index mapping QVERIFY(verifyVertexIndex(g, v1, 0)); QVERIFY(verifyVertexIndex(g, v2, 1)); QVERIFY(verifyVertexIndex(g, v3, 100)); QVERIFY(verifyVertexIndex(g, v4, 101)); QVERIFY(verifyVertexIndex(g, v5, 2)); QVERIFY(verifyVertexIndex(g, v6, 1121)); QVERIFY(verifyVertexIndex(g, v7, 5151)); QVERIFY(verifyVertexIndex(g, v8, -1)); QVERIFY(verifyVertexIndex(g, vA, numVertices - 2)); QVERIFY(verifyVertexIndex(g, vB, numVertices - 1)); // Verify edge index mapping QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, v2), 0)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v3, v4), 99)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, v3), 19800)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v2, v4), 19801)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v2, v1), 9900)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v4, v3), 9999)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v3, v1), 29700)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v4, v2), 29701)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vA, vA), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vB, vB), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, v8), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, v4), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v4, v1), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, v5), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v6, vA), 39621)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vA, v6), 40421)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, vA), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v7, vA), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vA, vB), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vB, vA), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(v7, vB), 41621)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vB, v7), 42821)); QVERIFY(verifyEdgeIndex(g, std::make_pair(Vert(70, 30), vA), 40000)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vA, Vert(70, 30)), 40800)); QVERIFY(verifyEdgeIndex(g, std::make_pair(Vert(70, 30), vB), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vB, Vert(70, 30)), -1)); QVERIFY(verifyEdgeIndex(g, std::make_pair(Vert(70, 49), vA), 40380)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vA, Vert(70, 49)), 41180)); QVERIFY(verifyEdgeIndex(g, std::make_pair(Vert(70, 49), vB), 41390)); QVERIFY(verifyEdgeIndex(g, std::make_pair(vB, Vert(70, 49)), 42590)); QCOMPARE(g.out_degree(v1), long(2)); QVERIFY(verifyNormalVertex(g.out_edge_at(v1, 0).second, 11, 10)); QVERIFY(verifyNormalVertex(g.out_edge_at(v1, 1).second, 10, 11)); QCOMPARE(g.out_degree(v4), long(4)); QVERIFY(verifyNormalVertex(g.out_edge_at(v4, 0).second, 10, 11)); QVERIFY(verifyNormalVertex(g.out_edge_at(v4, 1).second, 11, 10)); QVERIFY(verifyNormalVertex(g.out_edge_at(v4, 2).second, 12, 11)); QVERIFY(verifyNormalVertex(g.out_edge_at(v4, 3).second, 11, 12)); QCOMPARE(g.out_degree(v6), long(5)); QVERIFY(verifyNormalVertex(g.out_edge_at(v6, 0).second, 30, 21)); QVERIFY(verifyNormalVertex(g.out_edge_at(v6, 1).second, 31, 20)); QVERIFY(verifyNormalVertex(g.out_edge_at(v6, 2).second, 32, 21)); QVERIFY(verifyNormalVertex(g.out_edge_at(v6, 3).second, 31, 22)); QCOMPARE(g.out_edge_at(v6, 4).second, vA); QCOMPARE(g.out_degree(v7), long(5)); QVERIFY(verifyNormalVertex(g.out_edge_at(v7, 0).second, 60, 61)); QVERIFY(verifyNormalVertex(g.out_edge_at(v7, 1).second, 61, 60)); QVERIFY(verifyNormalVertex(g.out_edge_at(v7, 2).second, 62, 61)); QVERIFY(verifyNormalVertex(g.out_edge_at(v7, 3).second, 61, 62)); QCOMPARE(g.out_edge_at(v7, 4).second, vB); QCOMPARE(g.out_degree(vA), long(800)); QVERIFY(verifyNormalVertex(g.out_edge_at(vA, 0).second, 30, 20)); QVERIFY(verifyNormalVertex(g.out_edge_at(vA, 1).second, 31, 20)); // check second island QVERIFY(verifyNormalVertex(g.out_edge_at(vA, 400).second, 70, 30)); QVERIFY(verifyNormalVertex(g.out_edge_at(vA, 401).second, 71, 30)); } void KisLazyBrushTest::testGraphStandardIterators() { QRect mainRect(10, 10, 100, 100); QRect aLabelRect(30, 20, 20, 20); QRect bLabelRect(60, 60, 20, 20); KisLazyFillGraph g(mainRect, aLabelRect, bLabelRect); BGL_FORALL_VERTICES(v, g, KisLazyFillGraph) { int index = g.index_of(v); QVERIFY(index >= 0); KisLazyFillGraph::vertex_descriptor newVertex = g.vertex_at(index); QCOMPARE(newVertex, v); } BGL_FORALL_EDGES(e, g, KisLazyFillGraph) { int index = g.index_of(e); QVERIFY(index >= 0); KisLazyFillGraph::edge_descriptor newEdge = g.edge_at(index); QCOMPARE(newEdge, e); } } void KisLazyBrushTest::testGraphConcepts() { BOOST_CONCEPT_ASSERT(( VertexListGraphConcept )); //to have vertices(), num_vertices(), BOOST_CONCEPT_ASSERT(( EdgeListGraphConcept )); //to have edges() BOOST_CONCEPT_ASSERT(( IncidenceGraphConcept )); //to have source(), target() and out_edges() BOOST_CONCEPT_ASSERT(( AdjacencyGraphConcept )); // to have adjacent_vertices(v, g) BOOST_CONCEPT_ASSERT(( AdjacencyMatrixConcept )); // to have edge(u, v, g) } template class ComplexCapacityMap { typedef ComplexCapacityMap type; typedef typename boost::graph_traits::vertex_descriptor VertexDescriptor; typedef typename boost::graph_traits::edge_descriptor EdgeDescriptor; public: typedef EdgeDescriptor key_type; typedef float value_type; typedef const float& reference; typedef boost::readable_property_map_tag category; ComplexCapacityMap(Graph &graph, const QImage &mainImage, const QImage &aLabelImage, const QImage &bLabelImage) : m_graph(graph), m_mainImage(mainImage), m_aLabelImage(aLabelImage), m_bLabelImage(bLabelImage) { const QRgb *bits = reinterpret_cast(m_mainImage.constBits()); m_minIntensity = std::numeric_limits::max(); m_maxIntensity = std::numeric_limits::min(); const int numPixels = m_mainImage.width() * m_mainImage.height(); m_intensities.resize(numPixels); for (int i = 0; i < numPixels; i++) { const int value = qGray(bits[i]); m_intensities[i] = value; if (m_minIntensity > value) m_minIntensity = value; if (m_maxIntensity < value) m_maxIntensity = value; } m_pixelSize = 4; m_rowStride = m_mainImage.width() * m_pixelSize; } friend value_type get(const type &map, const key_type &key) { VertexDescriptor src = source(key, map.m_graph); VertexDescriptor dst = target(key, map.m_graph); bool srcLabelA = src.type == VertexDescriptor::LABEL_A; bool srcLabelB = src.type == VertexDescriptor::LABEL_B; bool dstLabelA = dst.type == VertexDescriptor::LABEL_A; bool dstLabelB = dst.type == VertexDescriptor::LABEL_B; if (srcLabelA || srcLabelB) { std::swap(src, dst); std::swap(srcLabelA, dstLabelA); std::swap(srcLabelB, dstLabelB); } Q_ASSERT(!srcLabelA && !srcLabelB); const int k = 2 * (map.m_mainImage.width() + map.m_mainImage.height()); //const int maxDimension = qMax(map.m_mainImage.width(), map.m_mainImage.height()); float value = 0.0; if (dstLabelA) { const int xOffset = map.m_aLabelImage.offset().x(); const int yOffset = map.m_aLabelImage.offset().y(); const int x = src.x - xOffset; const int y = src.y - yOffset; //qDebug() << "Label A:" << ppVar(src.x) << ppVar(src.y) << ppVar(xOffset) << ppVar(yOffset); QRgb pixel = map.m_aLabelImage.pixel(x, y); const int i0 = qAlpha(pixel); value = i0 / 255.0 * k; } else if (dstLabelB) { const int xOffset = map.m_bLabelImage.offset().x(); const int yOffset = map.m_bLabelImage.offset().y(); const int x = src.x - xOffset; const int y = src.y - yOffset; //qDebug() << "Label B:" << ppVar(src.x) << ppVar(src.y) << ppVar(xOffset) << ppVar(yOffset); QRgb pixel = map.m_bLabelImage.pixel(x, y); const int i0 = qAlpha(pixel); value = i0 / 255.0 * k; } else { const int xOffset = map.m_mainImage.offset().x(); const int yOffset = map.m_mainImage.offset().y(); const int i0 = map.m_intensities[(src.x - xOffset) + (src.y - xOffset) * map.m_mainImage.width()]; const int i1 = map.m_intensities[(dst.x - xOffset) + (dst.y - yOffset) * map.m_mainImage.width()]; const int diff = qAbs(i0 - i1); qreal normDiff = qreal(diff) / (map.m_maxIntensity - map.m_minIntensity); value = 1.0 + k * (1.0 - normDiff); } return value; } private: Graph &m_graph; QImage m_mainImage; QImage m_aLabelImage; QImage m_bLabelImage; QVector m_intensities; int m_minIntensity; int m_maxIntensity; int m_rowStride; int m_pixelSize; }; template ComplexCapacityMap MakeComplexCapacityMap(Graph &graph, const QImage &mainImage, const QImage &aLabelImage, const QImage &bLabelImage) { return ComplexCapacityMap(graph, mainImage, aLabelImage, bLabelImage); } void KisLazyBrushTest::testCutOnGraph() { BOOST_CONCEPT_ASSERT(( ReadablePropertyMapConcept, KisLazyFillGraph::edge_descriptor> )); QRect mainRect(0, 0, 27, 27); const int scribbleASize = 18; const int scribbleBSize = 19; const int holeSize = 7; const int obstacleVOffset = -5; const int asymVOffset = 1; const int obstacleSize = (mainRect.width() - holeSize) / 2; QRect o1Rect(0, mainRect.height() / 2 + obstacleVOffset + asymVOffset, obstacleSize, 1); QRect o2Rect(mainRect.width() - obstacleSize, mainRect.height() / 2 + obstacleVOffset - asymVOffset, obstacleSize, 1); QRect aLabelRect(0, 0, scribbleASize, 1); QRect bLabelRect(3, mainRect.bottom(), scribbleBSize, 1); KisLazyFillGraph graph(mainRect, aLabelRect, bLabelRect); QImage mainImage(mainRect.size(), QImage::Format_ARGB32); { QPainter gc(&mainImage); gc.fillRect(mainRect, Qt::white); gc.fillRect(o1Rect, Qt::blue); gc.fillRect(o2Rect, Qt::blue); } QImage aLabelImage(aLabelRect.size(), QImage::Format_ARGB32); { QPainter gc(&aLabelImage); gc.fillRect(QRect(QPoint(), aLabelRect.size()), Qt::red); aLabelImage.setOffset(aLabelRect.topLeft()); qDebug() << ppVar(aLabelImage.offset()); } QImage bLabelImage(bLabelRect.size(), QImage::Format_ARGB32); { QPainter gc(&bLabelImage); gc.fillRect(QRect(QPoint(), bLabelRect.size()), Qt::red); bLabelImage.setOffset(bLabelRect.topLeft()); } std::vector groups(num_vertices(graph), 0); std::vector residual_capacity(num_edges(graph), 0); auto capacityMap = MakeComplexCapacityMap(graph, mainImage, aLabelImage, bLabelImage); std::vector::vertices_size_type> distance_vec(num_vertices(graph), 0); std::vector::edge_descriptor> predecessor_vec(num_vertices(graph)); auto vertexIndexMap = get(boost::vertex_index, graph); typedef KisLazyFillGraph::vertex_descriptor VertexDescriptor; VertexDescriptor s(0, 0, VertexDescriptor::LABEL_A); VertexDescriptor t(0, 0, VertexDescriptor::LABEL_B); float maxFlow = boykov_kolmogorov_max_flow(graph, capacityMap, make_iterator_property_map(&residual_capacity[0], get(boost::edge_index, graph)), get(boost::edge_reverse, graph), make_iterator_property_map(&predecessor_vec[0], vertexIndexMap), make_iterator_property_map(&groups[0], vertexIndexMap), make_iterator_property_map(&distance_vec[0], vertexIndexMap), vertexIndexMap, s, t); qDebug() << ppVar(maxFlow); const int cell = 10; const int half = cell / 2; QImage result(cell * mainRect.size(), QImage::Format_ARGB32); QPainter resultPainter(&result); BGL_FORALL_VERTICES(v, graph, KisLazyFillGraph) { long vertex_idx = get(boost::vertex_index, graph, v); int label = groups[vertex_idx]; QColor color = label == 0 ? Qt::blue : label == 4 ? Qt::green : label == 1 ? Qt::gray : Qt::red; QRect rc(cell * v.x, cell * v.y, cell, cell); resultPainter.fillRect(rc, color); } BGL_FORALL_EDGES(e,graph,KisLazyFillGraph) { long egdeIndex = get(boost::edge_index, graph, e); const int cap = residual_capacity[egdeIndex]; QColor color(Qt::black); if (cap != 0) { const int fullCap = get(capacityMap, e); const int gray = qreal(cap) / fullCap * 50.0; color.setAlpha(gray); } VertexDescriptor src = source(e,graph); VertexDescriptor tgt = target(e,graph); QPoint p0(half + cell * src.x, half + cell * src.y); QPoint p1(half + cell * tgt.x, half + cell * tgt.y); resultPainter.setPen(QPen(color, 2)); resultPainter.drawLine(p0, p1); // qDebug() << "(" << src[0] << src[1] << ")" // << "->" // << "(" << tgt[0] << tgt[1] << ")" // << residual_capacity[egdeIndex]; } resultPainter.save(); resultPainter.setTransform(QTransform::fromScale(cell, cell)); resultPainter.setBrush(Qt::transparent); resultPainter.setPen(QPen(Qt::yellow, 0)); resultPainter.drawRect(o1Rect); resultPainter.drawRect(o2Rect); resultPainter.setPen(QPen(Qt::red, 0)); resultPainter.drawRect(aLabelRect); resultPainter.drawRect(bLabelRect); resultPainter.restore(); result.save("result.png"); } #include "lazybrush/kis_lazy_fill_capacity_map.h" #include #include "testutil.h" #include void writeColors(KisLazyFillGraph &graph, const std::vector &groups, KisPaintDeviceSP dst) { KisSequentialIterator dstIt(dst, graph.rect()); KoColor blue(Qt::blue, dst->colorSpace()); KoColor green(Qt::red, dst->colorSpace()); KoColor red(Qt::red, dst->colorSpace()); KoColor gray(Qt::gray, dst->colorSpace()); const int pixelSize = dst->colorSpace()->pixelSize(); while (dstIt.nextPixel()) { KisLazyFillGraph::vertex_descriptor v(dstIt.x(), dstIt.y()); long vertex_idx = get(boost::vertex_index, graph, v); int label = groups[vertex_idx]; KoColor &color = label == 0 ? blue : label == 4 ? green : label == 1 ? gray : red; quint8 *dstPtr = dstIt.rawData(); memcpy(dstPtr, color.data(), pixelSize); } } void writeStat(KisLazyFillGraph &graph, const std::vector &groups, const std::vector &residual_capacity, KisLazyFillCapacityMap &capacityMap) { typedef KisLazyFillGraph::vertex_descriptor VertexDescriptor; const int cell = 10; const int half = cell / 2; QImage result(cell * graph.size(), QImage::Format_ARGB32); QPainter resultPainter(&result); BGL_FORALL_VERTICES(v, graph, KisLazyFillGraph) { if (v.type != VertexDescriptor::NORMAL) continue; long vertex_idx = get(boost::vertex_index, graph, v); int label = groups[vertex_idx]; QColor color = label == 0 ? Qt::blue : label == 4 ? Qt::green : label == 1 ? Qt::gray : Qt::red; QRect rc(cell * v.x, cell * v.y, cell, cell); resultPainter.fillRect(rc, color); } BGL_FORALL_EDGES(e,graph,KisLazyFillGraph) { long egdeIndex = get(boost::edge_index, graph, e); const int cap = residual_capacity[egdeIndex]; const int fullCap = get(capacityMap, e); QColor color(Qt::red); if (cap > 0 || fullCap == 0) continue; /* if (fullCap != 0) { const int gray = fullCap / capacityMap.maxCapacity() * 50.0; color.setAlpha(gray); } else { continue; } */ VertexDescriptor src = source(e,graph); VertexDescriptor tgt = target(e,graph); if (src.type != VertexDescriptor::NORMAL || tgt.type != VertexDescriptor::NORMAL) { /* VertexDescriptor &v = src.type != VertexDescriptor::NORMAL ? tgt : src; QPoint p0(half + cell * v.x, half + cell * v.y); resultPainter.setPen(QPen(color, 4)); resultPainter.drawEllipse(p0, 0.5 * half, 0.5 * half); */ } else { QPoint p0(half + cell * src.x, half + cell * src.y); QPoint p1(half + cell * tgt.x, half + cell * tgt.y); resultPainter.setPen(QPen(color, 4)); resultPainter.drawLine(p0, p1); } } BGL_FORALL_EDGES(e,graph,KisLazyFillGraph) { long egdeIndex = get(boost::edge_index, graph, e); const int cap = residual_capacity[egdeIndex]; QColor color(Qt::black); if (cap != 0) { const int fullCap = get(capacityMap, e); const int gray = qreal(cap) / fullCap * 50.0; color.setAlpha(gray); } else { continue; } VertexDescriptor src = source(e,graph); VertexDescriptor tgt = target(e,graph); if (src.type != VertexDescriptor::NORMAL || tgt.type != VertexDescriptor::NORMAL) { VertexDescriptor &v = src.type != VertexDescriptor::NORMAL ? tgt : src; QPoint p0(half + cell * v.x, half + cell * v.y); resultPainter.setPen(QPen(color, 2)); resultPainter.drawEllipse(QPointF(p0), 0.5 * half, 0.5 * half); } else { QPoint p0(half + cell * src.x, half + cell * src.y); QPoint p1(half + cell * tgt.x, half + cell * tgt.y); resultPainter.setPen(QPen(color, 2)); resultPainter.drawLine(p0, p1); } } resultPainter.save(); resultPainter.setTransform(QTransform::fromScale(cell, cell)); resultPainter.setBrush(Qt::transparent); //resultPainter.setPen(QPen(Qt::yellow, 0)); //resultPainter.drawRect(o1Rect); //resultPainter.drawRect(o2Rect); //resultPainter.setPen(QPen(Qt::red, 0)); //resultPainter.drawRect(aLabelRect); //resultPainter.drawRect(bLabelRect); resultPainter.restore(); result.save("result.png"); } #include "kis_paint_device_debug_utils.h" #include "kis_gaussian_kernel.h" #include "krita_utils.h" KisPaintDeviceSP loadTestImage(const QString &name, bool convertToAlpha) { QImage image(TestUtil::fetchDataFileLazy(name)); KisPaintDeviceSP dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); dev->convertFromQImage(image, 0); if (convertToAlpha) { dev = KisPainter::convertToAlphaAsAlpha(dev); } return dev; } #include "lazybrush/kis_lazy_fill_tools.h" void KisLazyBrushTest::testCutOnGraphDevice() { BOOST_CONCEPT_ASSERT(( ReadablePropertyMapConcept )); KisPaintDeviceSP mainDev = loadTestImage("fill2_main.png", false); KisPaintDeviceSP aLabelDev = loadTestImage("fill2_a.png", true); KisPaintDeviceSP bLabelDev = loadTestImage("fill2_b.png", true); KisPaintDeviceSP filteredMainDev = KisPainter::convertToAlphaAsGray(mainDev); const QRect filterRect = filteredMainDev->exactBounds(); // KisGaussianKernel::applyLoG(filteredMainDev, // filterRect, // 5, // QBitArray(), 0); // KisLazyFillTools::normalizeAndInvertAlpha8Device(filteredMainDev, filterRect); KIS_DUMP_DEVICE_2(filteredMainDev, filterRect, "2filtered", "dd"); KoColor color(Qt::red, mainDev->colorSpace()); KisPaintDeviceSP resultColoring = new KisPaintDevice(mainDev->colorSpace()); KisPaintDeviceSP maskDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); maskDevice->fill(QRect(0,0,640,40), KoColor(Qt::gray, maskDevice->colorSpace())); KisLazyFillTools::cutOneWay(color, filteredMainDev, aLabelDev, bLabelDev, resultColoring, maskDevice, filterRect); KIS_DUMP_DEVICE_2(resultColoring, filterRect, "00result", "dd"); KIS_DUMP_DEVICE_2(maskDevice, filterRect, "01mask", "dd"); KIS_DUMP_DEVICE_2(mainDev, filterRect, "1main", "dd"); KIS_DUMP_DEVICE_2(aLabelDev, filterRect, "3aLabel", "dd"); KIS_DUMP_DEVICE_2(bLabelDev, filterRect, "4bLabel", "dd"); #if 0 KisLazyFillCapacityMap capacityMap(filteredMainDev, aLabelDev, bLabelDev); KisLazyFillGraph &graph = capacityMap.graph(); std::vector groups(num_vertices(graph), 0); std::vector residual_capacity(num_edges(graph), 0); std::vector::vertices_size_type> distance_vec(num_vertices(graph), 0); std::vector::edge_descriptor> predecessor_vec(num_vertices(graph)); auto vertexIndexMap = get(boost::vertex_index, graph); typedef KisLazyFillGraph::vertex_descriptor VertexDescriptor; VertexDescriptor s(VertexDescriptor::LABEL_A); VertexDescriptor t(VertexDescriptor::LABEL_B); float maxFlow = boykov_kolmogorov_max_flow(graph, capacityMap, make_iterator_property_map(&residual_capacity[0], get(boost::edge_index, graph)), get(boost::edge_reverse, graph), make_iterator_property_map(&predecessor_vec[0], vertexIndexMap), make_iterator_property_map(&groups[0], vertexIndexMap), make_iterator_property_map(&distance_vec[0], vertexIndexMap), vertexIndexMap, s, t); qDebug() << ppVar(maxFlow); KisPaintDeviceSP resultColoring = new KisPaintDevice(*mainDev); writeColors(graph, groups, resultColoring); KIS_DUMP_DEVICE_2(resultColoring, graph.rect(), "0result", "dd"); KIS_DUMP_DEVICE_2(mainDev, graph.rect(), "1main", "dd"); KIS_DUMP_DEVICE_2(aLabelDev, graph.rect(), "3aLabel", "dd"); KIS_DUMP_DEVICE_2(bLabelDev, graph.rect(), "4bLabel", "dd"); writeStat(graph, groups, residual_capacity, capacityMap); #endif } #include "lazybrush/kis_multiway_cut.h" +#include "testing_timed_default_bounds.h" void KisLazyBrushTest::testCutOnGraphDeviceMulti() { BOOST_CONCEPT_ASSERT(( ReadablePropertyMapConcept )); KisPaintDeviceSP mainDev = loadTestImage("fill4_main.png", false); KisPaintDeviceSP aLabelDev = loadTestImage("fill4_a.png", true); KisPaintDeviceSP bLabelDev = loadTestImage("fill4_b.png", true); KisPaintDeviceSP cLabelDev = loadTestImage("fill4_c.png", true); KisPaintDeviceSP dLabelDev = loadTestImage("fill4_d.png", true); KisPaintDeviceSP eLabelDev = loadTestImage("fill4_e.png", true); - KisPaintDeviceSP filteredMainDev = KisPainter::convertToAlphaAsGray(mainDev); + KisDefaultBoundsBaseSP bounds = new TestUtil::TestingTimedDefaultBounds(mainDev->exactBounds()); + mainDev->setDefaultBounds(bounds); + filteredMainDev->setDefaultBounds(bounds); + const QRect filterRect = filteredMainDev->exactBounds(); KisGaussianKernel::applyLoG(filteredMainDev, filterRect, 2, 1.0, QBitArray(), 0); KisLazyFillTools::normalizeAndInvertAlpha8Device(filteredMainDev, filterRect); KisPaintDeviceSP resultColoring = new KisPaintDevice(mainDev->colorSpace()); KisMultiwayCut cut(filteredMainDev, resultColoring, filterRect); cut.addKeyStroke(aLabelDev, KoColor(Qt::red, mainDev->colorSpace())); cut.addKeyStroke(bLabelDev, KoColor(Qt::green, mainDev->colorSpace())); cut.addKeyStroke(cLabelDev, KoColor(Qt::blue, mainDev->colorSpace())); cut.addKeyStroke(dLabelDev, KoColor(Qt::yellow, mainDev->colorSpace())); cut.addKeyStroke(eLabelDev, KoColor(Qt::magenta, mainDev->colorSpace())); cut.run(); KIS_DUMP_DEVICE_2(resultColoring, filterRect, "00result", "dd"); KIS_DUMP_DEVICE_2(mainDev, filterRect, "1main", "dd"); KIS_DUMP_DEVICE_2(filteredMainDev, filterRect, "2filtered", "dd"); } void KisLazyBrushTest::testLoG() { QImage mainImage(TestUtil::fetchDataFileLazy("fill1_main.png")); QVERIFY(!mainImage.isNull()); KisPaintDeviceSP mainDev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); mainDev->convertFromQImage(mainImage, 0); QRect rect = mainDev->exactBounds(); // KisPaintDeviceSP mainDev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); // const QRect rect(0,0,10,10); // const QRect fillRect(0,0,5,10); // KoColor bg(Qt::white, mainDev->colorSpace()); // KoColor fg(Qt::black, mainDev->colorSpace()); // mainDev->fill(rect, bg); // mainDev->fill(fillRect, fg); KisPaintDeviceSP filteredMainDev = KisPainter::convertToAlphaAsGray(mainDev); + + KisDefaultBoundsBaseSP bounds = new TestUtil::TestingTimedDefaultBounds(mainDev->exactBounds()); + mainDev->setDefaultBounds(bounds); + filteredMainDev->setDefaultBounds(bounds); + KisGaussianKernel::applyLoG(filteredMainDev, rect, 4.0, 1.0, QBitArray(), 0); KisLazyFillTools::normalizeAndInvertAlpha8Device(filteredMainDev, rect); KIS_DUMP_DEVICE_2(mainDev, rect, "1main", "dd"); KIS_DUMP_DEVICE_2(filteredMainDev, rect, "2filtered", "dd"); } void KisLazyBrushTest::testSplitIntoConnectedComponents() { const QRect rc1(10, 10, 10, 10); const QRect rc2(30, 10, 10, 10); const QRect boundingRect(0,0,100,100); KisPaintDeviceSP dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); dev->fill(rc1, KoColor(Qt::red, dev->colorSpace())); dev->fill(rc2, KoColor(Qt::green, dev->colorSpace())); QCOMPARE(dev->exactBounds(), rc1 | rc2); QVector points = KisLazyFillTools::splitIntoConnectedComponents(dev, boundingRect); qDebug() << ppVar(points); QCOMPARE(points.size(), 2); QCOMPARE(points[0], QPoint(10,10)); QCOMPARE(points[1], QPoint(30,10)); } void KisLazyBrushTest::testEstimateTransparentPixels() { KisPaintDeviceSP dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); const QRect totalRect(0,0,50,100); qreal value = 0.0; value = KritaUtils::estimatePortionOfTransparentPixels(dev, totalRect, 0.1); QCOMPARE(value, 1.0); dev->fill(QRect(0,0,25,50), KoColor(Qt::red, dev->colorSpace())); value = KritaUtils::estimatePortionOfTransparentPixels(dev, totalRect, 0.1); QVERIFY(qAbs(value - 0.75) < 0.05); dev->fill(QRect(25,0,25,50), KoColor(Qt::green, dev->colorSpace())); value = KritaUtils::estimatePortionOfTransparentPixels(dev, totalRect, 0.1); QVERIFY(qAbs(value - 0.5) < 0.05); dev->fill(QRect(25,50,25,50), KoColor(Qt::blue, dev->colorSpace())); value = KritaUtils::estimatePortionOfTransparentPixels(dev, totalRect, 0.1); QVERIFY(qAbs(value - 0.25) < 0.05); dev->fill(QRect(0,50,25,50), KoColor(Qt::blue, dev->colorSpace())); value = KritaUtils::estimatePortionOfTransparentPixels(dev, totalRect, 0.1); QCOMPARE(value, 0.0); } void KisLazyBrushTest::multiwayCutBenchmark() { BOOST_CONCEPT_ASSERT(( ReadablePropertyMapConcept )); const KoColor fillColor(Qt::black, KoColorSpaceRegistry::instance()->rgb8()); KisPaintDeviceSP mainDev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); QRect mainRect(0,0,512,512); QPainterPath path; path.moveTo(100, 100); path.lineTo(400, 100); path.lineTo(400, 400); path.lineTo(100, 400); path.lineTo(100, 120); KisFillPainter gc(mainDev); gc.setPaintColor(fillColor); gc.drawPainterPath(path, QPen(Qt::white, 10)); gc.fillRect(QRect(250, 100, 15, 120), fillColor); gc.fillRect(QRect(250, 280, 15, 120), fillColor); gc.fillRect(QRect(100, 250, 120, 15), fillColor); gc.fillRect(QRect(280, 250, 120, 15), fillColor); //KIS_DUMP_DEVICE_2(mainDev, mainRect, "1main", "dd"); KisPaintDeviceSP aLabelDev = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); aLabelDev->fill(QRect(110, 110, 30,30), KoColor(Qt::black, KoColorSpaceRegistry::instance()->alpha8())); KisPaintDeviceSP bLabelDev = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); bLabelDev->fill(QRect(370, 110, 20,20), KoColor(Qt::black, KoColorSpaceRegistry::instance()->alpha8())); KisPaintDeviceSP cLabelDev = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); cLabelDev->fill(QRect(370, 370, 20,20), KoColor(Qt::black, KoColorSpaceRegistry::instance()->alpha8())); KisPaintDeviceSP dLabelDev = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); dLabelDev->fill(QRect(110, 370, 20,20), KoColor(Qt::black, KoColorSpaceRegistry::instance()->alpha8())); KisPaintDeviceSP eLabelDev = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); eLabelDev->fill(QRect(0, 0, 200,20), KoColor(Qt::black, KoColorSpaceRegistry::instance()->alpha8())); KisPaintDeviceSP filteredMainDev = KisPainter::convertToAlphaAsAlpha(mainDev); KisLazyFillTools::normalizeAndInvertAlpha8Device(filteredMainDev, mainRect); KisPaintDeviceSP resultColoring = new KisPaintDevice(mainDev->colorSpace()); KisMultiwayCut cut(filteredMainDev, resultColoring, mainRect); cut.addKeyStroke(aLabelDev, KoColor(Qt::red, mainDev->colorSpace())); cut.addKeyStroke(bLabelDev, KoColor(Qt::green, mainDev->colorSpace())); cut.addKeyStroke(cLabelDev, KoColor(Qt::blue, mainDev->colorSpace())); cut.addKeyStroke(dLabelDev, KoColor(Qt::yellow, mainDev->colorSpace())); cut.addKeyStroke(eLabelDev, KoColor(Qt::transparent, mainDev->colorSpace())); QBENCHMARK_ONCE { cut.run(); } // KIS_DUMP_DEVICE_2(resultColoring, mainRect, "00result", "dd"); // KIS_DUMP_DEVICE_2(mainDev, mainRect, "1main", "dd"); // KIS_DUMP_DEVICE_2(filteredMainDev, mainRect, "2filtered", "dd"); } QTEST_MAIN(KisLazyBrushTest) diff --git a/libs/image/tests/kis_simple_update_queue_test.cpp b/libs/image/tests/kis_simple_update_queue_test.cpp index bc8c6398ff..2b522c88ae 100644 --- a/libs/image/tests/kis_simple_update_queue_test.cpp +++ b/libs/image/tests/kis_simple_update_queue_test.cpp @@ -1,280 +1,280 @@ /* * Copyright (c) 2010 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_simple_update_queue_test.h" #include #include #include #include "kis_group_layer.h" #include "kis_paint_layer.h" #include "kis_adjustment_layer.h" #include "filter/kis_filter.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "kis_selection.h" #include "kis_update_job_item.h" #include "kis_simple_update_queue.h" #include "scheduler_utils.h" #include "lod_override.h" void KisSimpleUpdateQueueTest::testJobProcessing() { KisTestableUpdaterContext context(2); QRect imageRect(0,0,200,200); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, imageRect.width(), imageRect.height(), cs, "merge test"); KisPaintLayerSP paintLayer = new KisPaintLayer(image, "test", OPACITY_OPAQUE_U8); - image->lock(); + image->barrierLock(); image->addNode(paintLayer); image->unlock(); QRect dirtyRect1(0,0,50,100); QRect dirtyRect2(0,0,100,100); QRect dirtyRect3(50,0,50,100); QRect dirtyRect4(150,150,50,50); QRect dirtyRect5(dirtyRect4); // theoretically, should be merged with 4 QVector jobs; KisWalkersList walkersList; /** * Process the queue and look what has been added into * the updater context */ KisTestableSimpleUpdateQueue queue; queue.addUpdateJob(paintLayer, dirtyRect1, imageRect, 0); queue.addUpdateJob(paintLayer, dirtyRect2, imageRect, 0); queue.addUpdateJob(paintLayer, dirtyRect3, imageRect, 0); queue.addUpdateJob(paintLayer, dirtyRect4, imageRect, 0); { TestUtil::LodOverride l(1, image); queue.addUpdateJob(paintLayer, dirtyRect5, imageRect, 1); } queue.processQueue(context); jobs = context.getJobs(); QVERIFY(checkWalker(jobs[0]->walker(), dirtyRect2)); QVERIFY(checkWalker(jobs[1]->walker(), dirtyRect4)); QCOMPARE(jobs.size(), 2); QCOMPARE(context.currentLevelOfDetail(), 0); walkersList = queue.getWalkersList(); QCOMPARE(walkersList.size(), 1); QVERIFY(checkWalker(walkersList[0], dirtyRect5, 1)); } void KisSimpleUpdateQueueTest::testSplitUpdate() { testSplit(false); } void KisSimpleUpdateQueueTest::testSplitFullRefresh() { testSplit(true); } void KisSimpleUpdateQueueTest::testSplit(bool useFullRefresh) { QRect imageRect(0,0,1024,1024); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, imageRect.width(), imageRect.height(), cs, "merge test"); KisPaintLayerSP paintLayer = new KisPaintLayer(image, "test", OPACITY_OPAQUE_U8); - image->lock(); + image->barrierLock(); image->addNode(paintLayer); image->unlock(); QRect dirtyRect1(0,0,1000,1000); KisTestableSimpleUpdateQueue queue; KisWalkersList& walkersList = queue.getWalkersList(); if(!useFullRefresh) { queue.addUpdateJob(paintLayer, dirtyRect1, imageRect, 0); } else { queue.addFullRefreshJob(paintLayer, dirtyRect1, imageRect, 0); } QCOMPARE(walkersList.size(), 4); QVERIFY(checkWalker(walkersList[0], QRect(0,0,512,512))); QVERIFY(checkWalker(walkersList[1], QRect(512,0,488,512))); QVERIFY(checkWalker(walkersList[2], QRect(0,512,512,488))); QVERIFY(checkWalker(walkersList[3], QRect(512,512,488,488))); queue.optimize(); //must change nothing QCOMPARE(walkersList.size(), 4); QVERIFY(checkWalker(walkersList[0], QRect(0,0,512,512))); QVERIFY(checkWalker(walkersList[1], QRect(512,0,488,512))); QVERIFY(checkWalker(walkersList[2], QRect(0,512,512,488))); QVERIFY(checkWalker(walkersList[3], QRect(512,512,488,488))); } void KisSimpleUpdateQueueTest::testChecksum() { QRect imageRect(0,0,512,512); QRect dirtyRect(100,100,100,100); const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, imageRect.width(), imageRect.height(), colorSpace, "test"); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisAdjustmentLayerSP adjustmentLayer = new KisAdjustmentLayer(image, "adj", 0, 0); - image->lock(); + image->barrierLock(); image->addNode(paintLayer1, image->rootLayer()); image->addNode(adjustmentLayer, image->rootLayer()); image->unlock(); KisFilterSP filter = KisFilterRegistry::instance()->value("blur"); Q_ASSERT(filter); KisFilterConfigurationSP configuration = filter->defaultConfiguration(); KisTestableSimpleUpdateQueue queue; KisWalkersList& walkersList = queue.getWalkersList(); { TestUtil::LodOverride l(1, image); queue.addUpdateJob(adjustmentLayer, dirtyRect, imageRect, 1); QCOMPARE(walkersList[0]->checksumValid(), true); QCOMPARE(walkersList[0]->levelOfDetail(), 1); } adjustmentLayer->setFilter(configuration); { TestUtil::LodOverride l(1, image); QCOMPARE(walkersList[0]->checksumValid(), false); } QVector jobs; KisTestableUpdaterContext context(2); { TestUtil::LodOverride l(1, image); queue.processQueue(context); } jobs = context.getJobs(); { TestUtil::LodOverride l(1, image); QCOMPARE(jobs[0]->walker()->checksumValid(), true); } } void KisSimpleUpdateQueueTest::testMixingTypes() { QRect imageRect(0,0,1024,1024); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, imageRect.width(), imageRect.height(), cs, "merge test"); KisPaintLayerSP paintLayer = new KisPaintLayer(image, "test", OPACITY_OPAQUE_U8); - image->lock(); + image->barrierLock(); image->addNode(paintLayer); image->unlock(); QRect dirtyRect1(0,0,200,200); QRect dirtyRect2(0,0,200,200); QRect dirtyRect3(20,20,200,200); KisTestableSimpleUpdateQueue queue; KisWalkersList& walkersList = queue.getWalkersList(); queue.addUpdateJob(paintLayer, dirtyRect1, imageRect, 0); queue.addFullRefreshJob(paintLayer, dirtyRect2, imageRect, 0); queue.addFullRefreshJob(paintLayer, dirtyRect3, imageRect, 0); queue.addUpdateNoFilthyJob(paintLayer, dirtyRect1, imageRect, 0); QCOMPARE(walkersList.size(), 3); QVERIFY(checkWalker(walkersList[0], QRect(0,0,200,200))); QVERIFY(checkWalker(walkersList[1], QRect(0,0,220,220))); QVERIFY(checkWalker(walkersList[2], QRect(0,0,200,200))); QCOMPARE(walkersList[0]->type(), KisBaseRectsWalker::UPDATE); QCOMPARE(walkersList[1]->type(), KisBaseRectsWalker::FULL_REFRESH); QCOMPARE(walkersList[2]->type(), KisBaseRectsWalker::UPDATE_NO_FILTHY); } void KisSimpleUpdateQueueTest::testSpontaneousJobsCompression() { KisTestableSimpleUpdateQueue queue; KisSpontaneousJobsList &jobsList = queue.getSpontaneousJobsList(); QVERIFY(queue.isEmpty()); QCOMPARE(queue.sizeMetric(), 0); QVERIFY(jobsList.isEmpty()); // we cannot use scoped pointers for the jobs, since they become owned by the queue KisSpontaneousJob *job1 = new KisNoopSpontaneousJob(false); queue.addSpontaneousJob(job1); QVERIFY(!queue.isEmpty()); QCOMPARE(queue.sizeMetric(), 1); QCOMPARE(jobsList.size(), 1); QCOMPARE(jobsList[0], job1); KisSpontaneousJob *job2 = new KisNoopSpontaneousJob(false); queue.addSpontaneousJob(job2); QVERIFY(!queue.isEmpty()); QCOMPARE(queue.sizeMetric(), 2); QCOMPARE(jobsList.size(), 2); QCOMPARE(jobsList[0], job1); QCOMPARE(jobsList[1], job2); KisSpontaneousJob *job3 = new KisNoopSpontaneousJob(true); queue.addSpontaneousJob(job3); QVERIFY(!queue.isEmpty()); QCOMPARE(queue.sizeMetric(), 1); QCOMPARE(jobsList.size(), 1); QCOMPARE(jobsList[0], job3); } QTEST_MAIN(KisSimpleUpdateQueueTest) diff --git a/libs/image/tests/kis_update_scheduler_test.cpp b/libs/image/tests/kis_update_scheduler_test.cpp index bda1e77872..da7456918a 100644 --- a/libs/image/tests/kis_update_scheduler_test.cpp +++ b/libs/image/tests/kis_update_scheduler_test.cpp @@ -1,431 +1,431 @@ /* * Copyright (c) 2010 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_update_scheduler_test.h" #include #include #include #include "kis_group_layer.h" #include "kis_paint_layer.h" #include "kis_adjustment_layer.h" #include "filter/kis_filter.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "kis_selection.h" #include "scheduler_utils.h" #include "kis_update_scheduler.h" #include "kis_updater_context.h" #include "kis_update_job_item.h" #include "kis_simple_update_queue.h" #include "../../sdk/tests/testutil.h" KisImageSP KisUpdateSchedulerTest::buildTestingImage() { QImage sourceImage1(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa.png"); QImage sourceImage2(QString(FILES_DATA_DIR) + QDir::separator() + "inverted_hakonepa.png"); QRect imageRect = QRect(QPoint(0,0), sourceImage1.size()); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, imageRect.width(), imageRect.height(), cs, "merge test"); KisFilterSP filter = KisFilterRegistry::instance()->value("blur"); Q_ASSERT(filter); KisFilterConfigurationSP configuration = filter->defaultConfiguration(); Q_ASSERT(configuration); KisPaintLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisPaintLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8 / 3); KisLayerSP blur1 = new KisAdjustmentLayer(image, "blur1", configuration, 0); paintLayer1->paintDevice()->convertFromQImage(sourceImage1, 0, 0, 0); paintLayer2->paintDevice()->convertFromQImage(sourceImage2, 0, 0, 0); - image->lock(); + image->barrierLock(); image->addNode(paintLayer1); image->addNode(paintLayer2); image->addNode(blur1); image->unlock(); return image; } void KisUpdateSchedulerTest::testMerge() { KisImageSP image = buildTestingImage(); QRect imageRect = image->bounds(); KisNodeSP rootLayer = image->rootLayer(); KisNodeSP paintLayer1 = rootLayer->firstChild(); QCOMPARE(paintLayer1->name(), QString("paint1")); KisUpdateScheduler scheduler(image.data()); /** * Test synchronous Full Refresh */ scheduler.fullRefresh(rootLayer, image->bounds(), image->bounds()); QCOMPARE(rootLayer->exactBounds(), image->bounds()); QImage resultFRProjection = rootLayer->projection()->convertToQImage(0); resultFRProjection.save(QString(FILES_OUTPUT_DIR) + QDir::separator() + "scheduler_fr_merge_result.png"); /** * Test incremental updates */ rootLayer->projection()->clear(); const qint32 num = 4; qint32 width = imageRect.width() / num; qint32 lastWidth = imageRect.width() - width; QVector dirtyRects(num); for(qint32 i = 0; i < num-1; i++) { dirtyRects[i] = QRect(width*i, 0, width, imageRect.height()); } dirtyRects[num-1] = QRect(width*(num-1), 0, lastWidth, imageRect.height()); for(qint32 i = 0; i < num; i+=2) { scheduler.updateProjection(paintLayer1, dirtyRects[i], image->bounds()); } for(qint32 i = 1; i < num; i+=2) { scheduler.updateProjection(paintLayer1, dirtyRects[i], image->bounds()); } scheduler.waitForDone(); QCOMPARE(rootLayer->exactBounds(), image->bounds()); QImage resultDirtyProjection = rootLayer->projection()->convertToQImage(0); resultDirtyProjection.save(QString(FILES_OUTPUT_DIR) + QDir::separator() + "scheduler_dp_merge_result.png"); QPoint pt; QVERIFY(TestUtil::compareQImages(pt, resultFRProjection, resultDirtyProjection)); } void KisUpdateSchedulerTest::benchmarkOverlappedMerge() { KisImageSP image = buildTestingImage(); KisNodeSP rootLayer = image->rootLayer(); KisNodeSP paintLayer1 = rootLayer->firstChild(); QRect imageRect = image->bounds(); QCOMPARE(paintLayer1->name(), QString("paint1")); QCOMPARE(imageRect, QRect(0,0,640,441)); KisUpdateScheduler scheduler(image.data()); const qint32 xShift = 10; const qint32 yShift = 0; const qint32 numShifts = 64; QBENCHMARK{ QRect dirtyRect(0, 0, 200, imageRect.height()); for(int i = 0; i < numShifts; i++) { // dbgKrita << dirtyRect; scheduler.updateProjection(paintLayer1, dirtyRect, image->bounds()); dirtyRect.translate(xShift, yShift); } scheduler.waitForDone(); } } void KisUpdateSchedulerTest::testLocking() { KisImageSP image = buildTestingImage(); KisNodeSP rootLayer = image->rootLayer(); KisNodeSP paintLayer1 = rootLayer->firstChild(); QRect imageRect = image->bounds(); QCOMPARE(paintLayer1->name(), QString("paint1")); QCOMPARE(imageRect, QRect(0,0,640,441)); KisTestableUpdateScheduler scheduler(image.data(), 2); KisUpdaterContext *context = scheduler.updaterContext(); QVERIFY(context); QVector jobs; QRect dirtyRect1(0,0,50,100); QRect dirtyRect2(0,0,100,100); QRect dirtyRect3(50,0,50,100); QRect dirtyRect4(150,150,50,50); scheduler.updateProjection(paintLayer1, imageRect, imageRect); jobs = context->getJobs(); QCOMPARE(jobs[0]->isRunning(), true); QCOMPARE(jobs[1]->isRunning(), false); QVERIFY(checkWalker(jobs[0]->walker(), imageRect)); context->clear(); scheduler.lock(); scheduler.updateProjection(paintLayer1, dirtyRect1, imageRect); scheduler.updateProjection(paintLayer1, dirtyRect2, imageRect); scheduler.updateProjection(paintLayer1, dirtyRect3, imageRect); scheduler.updateProjection(paintLayer1, dirtyRect4, imageRect); jobs = context->getJobs(); QCOMPARE(jobs[0]->isRunning(), false); QCOMPARE(jobs[1]->isRunning(), false); scheduler.unlock(); jobs = context->getJobs(); QCOMPARE(jobs[0]->isRunning(), true); QCOMPARE(jobs[1]->isRunning(), true); QVERIFY(checkWalker(jobs[0]->walker(), dirtyRect2)); QVERIFY(checkWalker(jobs[1]->walker(), dirtyRect4)); } void KisUpdateSchedulerTest::testExclusiveStrokes() { KisImageSP image = buildTestingImage(); KisNodeSP rootLayer = image->rootLayer(); KisNodeSP paintLayer1 = rootLayer->firstChild(); QRect imageRect = image->bounds(); QCOMPARE(paintLayer1->name(), QString("paint1")); QCOMPARE(imageRect, QRect(0,0,640,441)); QRect dirtyRect1(0,0,50,100); KisTestableUpdateScheduler scheduler(image.data(), 2); KisUpdaterContext *context = scheduler.updaterContext(); QVector jobs; scheduler.updateProjection(paintLayer1, dirtyRect1, imageRect); jobs = context->getJobs(); QCOMPARE(jobs[0]->isRunning(), true); QCOMPARE(jobs[1]->isRunning(), false); QVERIFY(checkWalker(jobs[0]->walker(), dirtyRect1)); KisStrokeId id = scheduler.startStroke(new KisTestingStrokeStrategy(QLatin1String("excl_"), true, false)); jobs = context->getJobs(); QCOMPARE(jobs[0]->isRunning(), true); QCOMPARE(jobs[1]->isRunning(), false); QVERIFY(checkWalker(jobs[0]->walker(), dirtyRect1)); context->clear(); scheduler.endStroke(id); jobs = context->getJobs(); QCOMPARE(jobs[0]->isRunning(), true); QCOMPARE(jobs[1]->isRunning(), false); COMPARE_NAME(jobs[0], "excl_init"); scheduler.updateProjection(paintLayer1, dirtyRect1, imageRect); jobs = context->getJobs(); QCOMPARE(jobs[0]->isRunning(), true); QCOMPARE(jobs[1]->isRunning(), false); COMPARE_NAME(jobs[0], "excl_init"); context->clear(); scheduler.processQueues(); jobs = context->getJobs(); QCOMPARE(jobs[0]->isRunning(), true); QCOMPARE(jobs[1]->isRunning(), false); COMPARE_NAME(jobs[0], "excl_finish"); context->clear(); scheduler.processQueues(); jobs = context->getJobs(); QCOMPARE(jobs[0]->isRunning(), true); QCOMPARE(jobs[1]->isRunning(), false); QVERIFY(checkWalker(jobs[0]->walker(), dirtyRect1)); } void KisUpdateSchedulerTest::testEmptyStroke() { KisImageSP image = buildTestingImage(); KisStrokeId id = image->startStroke(new KisStrokeStrategy(QLatin1String())); image->addJob(id, 0); image->endStroke(id); image->waitForDone(); } #include "kis_lazy_wait_condition.h" void KisUpdateSchedulerTest::testLazyWaitCondition() { { dbgKrita << "Not initialized"; KisLazyWaitCondition condition; QVERIFY(!condition.wait(50)); } { dbgKrita << "Initialized, not awake"; KisLazyWaitCondition condition; condition.initWaiting(); QVERIFY(!condition.wait(50)); condition.endWaiting(); } { dbgKrita << "Initialized, awake"; KisLazyWaitCondition condition; condition.initWaiting(); condition.wakeAll(); QVERIFY(condition.wait(50)); condition.endWaiting(); } { dbgKrita << "Initialized, not awake, then awake"; KisLazyWaitCondition condition; condition.initWaiting(); QVERIFY(!condition.wait(50)); condition.wakeAll(); QVERIFY(condition.wait(50)); condition.endWaiting(); } { dbgKrita << "Doublewait"; KisLazyWaitCondition condition; condition.initWaiting(); condition.initWaiting(); QVERIFY(!condition.wait(50)); condition.wakeAll(); QVERIFY(condition.wait(50)); QVERIFY(condition.wait(50)); condition.endWaiting(); } } #define NUM_THREADS 10 #define NUM_CYCLES 500 #define NTH_CHECK 3 class UpdatesBlockTester : public QRunnable { public: UpdatesBlockTester(KisUpdateScheduler *scheduler, KisNodeSP node) : m_scheduler(scheduler), m_node(node) { } void run() override { for (int i = 0; i < NUM_CYCLES; i++) { if(i % NTH_CHECK == 0) { m_scheduler->blockUpdates(); QTest::qSleep(1); // a bit of salt for crashiness ;) Q_ASSERT(!m_scheduler->haveUpdatesRunning()); m_scheduler->unblockUpdates(); } else { QRect updateRect(0,0,100,100); updateRect.moveTopLeft(QPoint((i%10)*100, (i%10)*100)); m_scheduler->updateProjection(m_node, updateRect, QRect(0,0,1100,1100)); } } } private: KisUpdateScheduler *m_scheduler; KisNodeSP m_node; }; void KisUpdateSchedulerTest::testBlockUpdates() { KisImageSP image = buildTestingImage(); KisNodeSP rootLayer = image->rootLayer(); KisNodeSP paintLayer1 = rootLayer->firstChild(); KisUpdateScheduler scheduler(image.data()); QThreadPool threadPool; threadPool.setMaxThreadCount(NUM_THREADS); for(int i = 0; i< NUM_THREADS; i++) { UpdatesBlockTester *tester = new UpdatesBlockTester(&scheduler, paintLayer1); threadPool.start(tester); } threadPool.waitForDone(); } #include "kis_update_time_monitor.h" void KisUpdateSchedulerTest::testTimeMonitor() { QVector dirtyRects; KisUpdateTimeMonitor::instance()->startStrokeMeasure(); KisUpdateTimeMonitor::instance()->reportMouseMove(QPointF(100, 0)); KisUpdateTimeMonitor::instance()->reportJobStarted((void*) 10); QTest::qSleep(300); KisUpdateTimeMonitor::instance()->reportJobStarted((void*) 20); QTest::qSleep(100); dirtyRects << QRect(10,10,10,10); KisUpdateTimeMonitor::instance()->reportJobFinished((void*) 10, dirtyRects); QTest::qSleep(100); dirtyRects.clear(); dirtyRects << QRect(30,30,10,10); KisUpdateTimeMonitor::instance()->reportJobFinished((void*) 20, dirtyRects); QTest::qSleep(500); KisUpdateTimeMonitor::instance()->reportUpdateFinished(QRect(10,10,10,10)); QTest::qSleep(300); KisUpdateTimeMonitor::instance()->reportUpdateFinished(QRect(30,30,10,10)); KisUpdateTimeMonitor::instance()->reportMouseMove(QPointF(130, 0)); KisUpdateTimeMonitor::instance()->endStrokeMeasure(); } void KisUpdateSchedulerTest::testLodSync() { KisImageSP image = buildTestingImage(); KisNodeSP rootLayer = image->root(); KisNodeSP paintLayer1 = rootLayer->firstChild(); QCOMPARE(paintLayer1->name(), QString("paint1")); image->setLevelOfDetailBlocked(false); image->setDesiredLevelOfDetail(2); image->explicitRegenerateLevelOfDetail(); image->waitForDone(); } QTEST_MAIN(KisUpdateSchedulerTest) diff --git a/libs/image/tests/kis_updater_context_test.cpp b/libs/image/tests/kis_updater_context_test.cpp index 3559b8128a..e39817c5bb 100644 --- a/libs/image/tests/kis_updater_context_test.cpp +++ b/libs/image/tests/kis_updater_context_test.cpp @@ -1,249 +1,249 @@ /* * Copyright (c) 2010 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_updater_context_test.h" #include #include #include #include #include "kis_paint_layer.h" #include "kis_merge_walker.h" #include "kis_updater_context.h" #include "kis_image.h" #include "scheduler_utils.h" #include "lod_override.h" #include "config-limit-long-tests.h" void KisUpdaterContextTest::testJobInterference() { KisTestableUpdaterContext context(3); QRect imageRect(0,0,100,100); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, imageRect.width(), imageRect.height(), cs, "merge test"); KisPaintLayerSP paintLayer = new KisPaintLayer(image, "test", OPACITY_OPAQUE_U8); - image->lock(); + image->barrierLock(); image->addNode(paintLayer); image->unlock(); QRect dirtyRect1(0,0,50,100); KisBaseRectsWalkerSP walker1 = new KisMergeWalker(imageRect); walker1->collectRects(paintLayer, dirtyRect1); context.lock(); context.addMergeJob(walker1); context.unlock(); // overlapping job --- forbidden { QRect dirtyRect(30,0,100,100); KisBaseRectsWalkerSP walker = new KisMergeWalker(imageRect); walker->collectRects(paintLayer, dirtyRect); context.lock(); QVERIFY(!context.isJobAllowed(walker)); context.unlock(); } // not overlapping job --- allowed { QRect dirtyRect(60,0,100,100); KisBaseRectsWalkerSP walker = new KisMergeWalker(imageRect); walker->collectRects(paintLayer, dirtyRect); context.lock(); QVERIFY(context.isJobAllowed(walker)); context.unlock(); } // not overlapping job, conflicting LOD --- forbidden { TestUtil::LodOverride l(1, image); QCOMPARE(paintLayer->paintDevice()->defaultBounds()->currentLevelOfDetail(), 1); QRect dirtyRect(60,0,100,100); KisBaseRectsWalkerSP walker = new KisMergeWalker(imageRect); walker->collectRects(paintLayer, dirtyRect); context.lock(); QVERIFY(!context.isJobAllowed(walker)); context.unlock(); } } void KisUpdaterContextTest::testSnapshot() { KisTestableUpdaterContext context(3); QRect imageRect(0,0,100,100); KisBaseRectsWalkerSP walker1 = new KisMergeWalker(imageRect); qint32 numMergeJobs = -777; qint32 numStrokeJobs = -777; context.lock(); context.getJobsSnapshot(numMergeJobs, numStrokeJobs); QCOMPARE(numMergeJobs, 0); QCOMPARE(numStrokeJobs, 0); QCOMPARE(context.currentLevelOfDetail(), -1); context.addMergeJob(walker1); context.getJobsSnapshot(numMergeJobs, numStrokeJobs); QCOMPARE(numMergeJobs, 1); QCOMPARE(numStrokeJobs, 0); QCOMPARE(context.currentLevelOfDetail(), 0); KisStrokeJobData *data = new KisStrokeJobData(KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); QScopedPointer strategy( new KisNoopDabStrategy("test")); context.addStrokeJob(new KisStrokeJob(strategy.data(), data, 0, true)); context.getJobsSnapshot(numMergeJobs, numStrokeJobs); QCOMPARE(numMergeJobs, 1); QCOMPARE(numStrokeJobs, 1); QCOMPARE(context.currentLevelOfDetail(), 0); context.addSpontaneousJob(new KisNoopSpontaneousJob()); context.getJobsSnapshot(numMergeJobs, numStrokeJobs); QCOMPARE(numMergeJobs, 2); QCOMPARE(numStrokeJobs, 1); QCOMPARE(context.currentLevelOfDetail(), 0); context.unlock(); { context.lock(); context.clear(); context.getJobsSnapshot(numMergeJobs, numStrokeJobs); QCOMPARE(numMergeJobs, 0); QCOMPARE(numStrokeJobs, 0); QCOMPARE(context.currentLevelOfDetail(), -1); data = new KisStrokeJobData(KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); context.addStrokeJob(new KisStrokeJob(strategy.data(), data, 2, true)); context.getJobsSnapshot(numMergeJobs, numStrokeJobs); QCOMPARE(numMergeJobs, 0); QCOMPARE(numStrokeJobs, 1); QCOMPARE(context.currentLevelOfDetail(), 2); context.unlock(); } } #define NUM_THREADS 10 #ifdef LIMIT_LONG_TESTS # define NUM_JOBS 60 #else # define NUM_JOBS 6000 #endif #define EXCLUSIVE_NTH 3 #define NUM_CHECKS 10 #define CHECK_DELAY 3 // ms class ExclusivenessCheckerStrategy : public KisStrokeJobStrategy { public: ExclusivenessCheckerStrategy(QAtomicInt &counter, QAtomicInt &hadConcurrency) : m_counter(counter), m_hadConcurrency(hadConcurrency) { } void run(KisStrokeJobData *data) override { Q_UNUSED(data); m_counter.ref(); for(int i = 0; i < NUM_CHECKS; i++) { if(data->isExclusive()) { Q_ASSERT(m_counter == 1); } else if (m_counter > 1) { m_hadConcurrency.ref(); } QTest::qSleep(CHECK_DELAY); } m_counter.deref(); } QString debugId() const override { return "ExclusivenessCheckerStrategy"; } private: QAtomicInt &m_counter; QAtomicInt &m_hadConcurrency; }; void KisUpdaterContextTest::stressTestExclusiveJobs() { KisUpdaterContext context(NUM_THREADS); QAtomicInt counter; QAtomicInt hadConcurrency; for(int i = 0; i < NUM_JOBS; i++) { if(context.hasSpareThread()) { bool isExclusive = i % EXCLUSIVE_NTH == 0; KisStrokeJobData *data = new KisStrokeJobData(KisStrokeJobData::SEQUENTIAL, isExclusive ? KisStrokeJobData::EXCLUSIVE : KisStrokeJobData::NORMAL); KisStrokeJobStrategy *strategy = new ExclusivenessCheckerStrategy(counter, hadConcurrency); context.addStrokeJob(new KisStrokeJob(strategy, data, 0, true)); } else { QTest::qSleep(CHECK_DELAY); } } context.waitForDone(); QVERIFY(!counter); dbgKrita << "Concurrency observed:" << hadConcurrency << "/" << NUM_CHECKS * NUM_JOBS; } QTEST_MAIN(KisUpdaterContextTest) diff --git a/libs/image/tests/kis_walkers_test.cpp b/libs/image/tests/kis_walkers_test.cpp index d238582c49..83aef57ae7 100644 --- a/libs/image/tests/kis_walkers_test.cpp +++ b/libs/image/tests/kis_walkers_test.cpp @@ -1,1294 +1,1294 @@ /* * Copyright (c) 2009 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_walkers_test.h" #include "kis_base_rects_walker.h" #include "kis_refresh_subtree_walker.h" #include "kis_full_refresh_walker.h" #include #include #include #include "kis_paint_layer.h" #include "kis_group_layer.h" #include "kis_clone_layer.h" #include "kis_adjustment_layer.h" #include "kis_selection.h" #include "filter/kis_filter.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "kis_filter_mask.h" #include "kis_transparency_mask.h" #include //#define DEBUG_VISITORS QString nodeTypeString(KisMergeWalker::NodePosition position); QString nodeTypePostfix(KisMergeWalker::NodePosition position); /************** Test Implementation Of A Walker *********************/ class KisTestWalker : public KisMergeWalker { public: KisTestWalker() :KisMergeWalker(QRect()) { } QStringList popResult() { QStringList order(m_order); m_order.clear(); return order; } using KisMergeWalker::startTrip; protected: void registerChangeRect(KisProjectionLeafSP node, NodePosition position) override { Q_UNUSED(position); QString postfix; if(!node->isLayer()) { postfix = "[skipped as not-a-layer]"; } #ifdef DEBUG_VISITORS qDebug()<< "FW:"<< node->node()->name() <<'\t'<< nodeTypeString(position) << postfix; #endif if(postfix.isEmpty()) { m_order.append(node->node()->name()); } } void registerNeedRect(KisProjectionLeafSP node, NodePosition position) override { QString postfix; if(!node->isLayer()) { postfix = "[skipped as not-a-layer]"; } #ifdef DEBUG_VISITORS qDebug()<< "BW:"<< node->node()->name() <<'\t'<< nodeTypeString(position) << postfix; #endif if(postfix.isEmpty()) { m_order.append(node->node()->name() + nodeTypePostfix(position)); } } protected: QStringList m_order; }; /************** Debug And Verify Code *******************************/ struct UpdateTestJob { QString updateAreaName; KisNodeSP startNode; QRect updateRect; QString referenceString; QRect accessRect; bool changeRectVaries; bool needRectVaries; }; void reportStartWith(QString nodeName, QRect rect = QRect()) { qDebug(); if(!rect.isEmpty()) qDebug() << "Start with:" << nodeName << rect; else qDebug() << "Start with:" << nodeName; } QString nodeTypeString(KisMergeWalker::NodePosition position) { QString string; if(position & KisMergeWalker::N_TOPMOST) string="TOP"; else if(position & KisMergeWalker::N_BOTTOMMOST) string="BOT"; else string="NOR"; if(position & KisMergeWalker::N_ABOVE_FILTHY) string+="_ABOVE "; else if(position & KisMergeWalker::N_FILTHY) string+="_FILTH*"; else if(position & KisMergeWalker::N_FILTHY_PROJECTION) string+="_PROJE*"; else if(position & KisMergeWalker::N_FILTHY_ORIGINAL) string+="_ORIGI*_WARNINIG!!!: NOT USED"; else if(position & KisMergeWalker::N_BELOW_FILTHY) string+="_BELOW "; else if(position & KisMergeWalker::N_EXTRA) string+="_EXTRA*"; else qFatal("Impossible happened"); return string; } QString nodeTypePostfix(KisMergeWalker::NodePosition position) { QString string('_'); if(position & KisMergeWalker::N_TOPMOST) string += 'T'; else if(position & KisMergeWalker::N_BOTTOMMOST) string += 'B'; else string += 'N'; if(position & KisMergeWalker::N_ABOVE_FILTHY) string += 'A'; else if(position & KisMergeWalker::N_FILTHY) string += 'F'; else if(position & KisMergeWalker::N_FILTHY_PROJECTION) string += 'P'; else if(position & KisMergeWalker::N_FILTHY_ORIGINAL) string += 'O'; else if(position & KisMergeWalker::N_BELOW_FILTHY) string += 'B'; else if(position & KisMergeWalker::N_EXTRA) string += 'E'; else qFatal("Impossible happened"); return string; } void KisWalkersTest::verifyResult(KisBaseRectsWalker &walker, struct UpdateTestJob &job) { QStringList list; if(!job.referenceString.isEmpty()) { list = job.referenceString.split(','); } verifyResult(walker, list, job.accessRect, job.changeRectVaries, job.needRectVaries); } void KisWalkersTest::verifyResult(KisBaseRectsWalker &walker, QStringList reference, QRect accessRect, bool changeRectVaries, bool needRectVaries) { KisMergeWalker::LeafStack &list = walker.leafStack(); QStringList::const_iterator iter = reference.constBegin(); if(reference.size() != list.size()) { qDebug() << "*** Seems like the walker returned stack of wrong size" << "( ref:" << reference.size() << "act:" << list.size() << ")"; qDebug() << "*** We are going to crash soon... just wait..."; } Q_FOREACH (const KisMergeWalker::JobItem &item, list) { #ifdef DEBUG_VISITORS qDebug() << item.m_leaf->node()->name() << '\t' << item.m_applyRect << '\t' << nodeTypeString(item.m_position); #endif QCOMPARE(item.m_leaf->node()->name(), *iter); iter++; } #ifdef DEBUG_VISITORS qDebug() << "Result AR:\t" << walker.accessRect(); #endif QCOMPARE(walker.accessRect(), accessRect); QCOMPARE(walker.changeRectVaries(), changeRectVaries); QCOMPARE(walker.needRectVaries(), needRectVaries); } /************** Actual Testing **************************************/ /* +----------+ |root | | layer 5 | | group | | paint 4 | | paint 3 | | adj | | paint 2 | | paint 1 | +----------+ */ void KisWalkersTest::testUsualVisiting() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 512, 512, colorSpace, "walker test"); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); KisLayerSP paintLayer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE_U8); KisLayerSP paintLayer4 = new KisPaintLayer(image, "paint4", OPACITY_OPAQUE_U8); KisLayerSP paintLayer5 = new KisPaintLayer(image, "paint5", OPACITY_OPAQUE_U8); KisLayerSP groupLayer = new KisGroupLayer(image, "group", OPACITY_OPAQUE_U8); KisLayerSP adjustmentLayer = new KisAdjustmentLayer(image, "adj", 0, 0); image->addNode(paintLayer1, image->rootLayer()); image->addNode(groupLayer, image->rootLayer()); image->addNode(paintLayer5, image->rootLayer()); image->addNode(paintLayer2, groupLayer); image->addNode(adjustmentLayer, groupLayer); image->addNode(paintLayer3, groupLayer); image->addNode(paintLayer4, groupLayer); KisTestWalker walker; { QString order("paint3,paint4,group,paint5,root," "root_TF,paint5_TA,group_NF,paint1_BB," "paint4_TA,paint3_NF,adj_NB,paint2_BB"); QStringList orderList = order.split(','); reportStartWith("paint3"); walker.startTrip(paintLayer3->projectionLeaf()); QVERIFY(walker.popResult() == orderList); } { QString order("adj,paint3,paint4,group,paint5,root," "root_TF,paint5_TA,group_NF,paint1_BB," "paint4_TA,paint3_NA,adj_NF,paint2_BB"); QStringList orderList = order.split(','); reportStartWith("adj"); walker.startTrip(adjustmentLayer->projectionLeaf()); QVERIFY(walker.popResult() == orderList); } { QString order("group,paint5,root," "root_TF,paint5_TA,group_NF,paint1_BB"); QStringList orderList = order.split(','); reportStartWith("group"); walker.startTrip(groupLayer->projectionLeaf()); QVERIFY(walker.popResult() == orderList); } } /* +----------+ |root | | layer 5 | | group | | mask 1 | | paint 4 | | paint 3 | | adj | | paint 2 | | paint 1 | +----------+ */ void KisWalkersTest::testVisitingWithTopmostMask() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 512, 512, colorSpace, "walker test"); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); KisLayerSP paintLayer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE_U8); KisLayerSP paintLayer4 = new KisPaintLayer(image, "paint4", OPACITY_OPAQUE_U8); KisLayerSP paintLayer5 = new KisPaintLayer(image, "paint5", OPACITY_OPAQUE_U8); KisLayerSP groupLayer = new KisGroupLayer(image, "group", OPACITY_OPAQUE_U8); KisLayerSP adjustmentLayer = new KisAdjustmentLayer(image, "adj", 0, 0); KisFilterMaskSP filterMask1 = new KisFilterMask(); filterMask1->initSelection(groupLayer); KisFilterSP filter = KisFilterRegistry::instance()->value("blur"); KIS_ASSERT(filter); KisFilterConfigurationSP configuration1 = filter->defaultConfiguration(); filterMask1->setFilter(configuration1); image->addNode(paintLayer1, image->rootLayer()); image->addNode(groupLayer, image->rootLayer()); image->addNode(paintLayer5, image->rootLayer()); image->addNode(paintLayer2, groupLayer); image->addNode(adjustmentLayer, groupLayer); image->addNode(paintLayer3, groupLayer); image->addNode(paintLayer4, groupLayer); // nasty mask! image->addNode(filterMask1, groupLayer); /** * The results must be the same as for testUsualVisiting */ KisTestWalker walker; { QString order("paint3,paint4,group,paint5,root," "root_TF,paint5_TA,group_NF,paint1_BB," "paint4_TA,paint3_NF,adj_NB,paint2_BB"); QStringList orderList = order.split(','); reportStartWith("paint3"); walker.startTrip(paintLayer3->projectionLeaf()); QVERIFY(walker.popResult() == orderList); } { QString order("adj,paint3,paint4,group,paint5,root," "root_TF,paint5_TA,group_NF,paint1_BB," "paint4_TA,paint3_NA,adj_NF,paint2_BB"); QStringList orderList = order.split(','); reportStartWith("adj"); walker.startTrip(adjustmentLayer->projectionLeaf()); QVERIFY(walker.popResult() == orderList); } { QString order("group,paint5,root," "root_TF,paint5_TA,group_NF,paint1_BB"); QStringList orderList = order.split(','); reportStartWith("group"); walker.startTrip(groupLayer->projectionLeaf()); QVERIFY(walker.popResult() == orderList); } } /* +----------+ |root | | layer 5 | | cplx 2 | | group | | paint 4 | | paint 3 | | cplx 1 | | paint 2 | | paint 1 | +----------+ */ void KisWalkersTest::testMergeVisiting() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 512, 512, colorSpace, "walker test"); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); KisLayerSP paintLayer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE_U8); KisLayerSP paintLayer4 = new KisPaintLayer(image, "paint4", OPACITY_OPAQUE_U8); KisLayerSP paintLayer5 = new KisPaintLayer(image, "paint5", OPACITY_OPAQUE_U8); KisLayerSP groupLayer = new KisGroupLayer(image, "group", OPACITY_OPAQUE_U8); KisLayerSP complexRectsLayer1 = new ComplexRectsLayer(image, "cplx1", OPACITY_OPAQUE_U8); KisLayerSP complexRectsLayer2 = new ComplexRectsLayer(image, "cplx2", OPACITY_OPAQUE_U8); image->addNode(paintLayer1, image->rootLayer()); image->addNode(groupLayer, image->rootLayer()); image->addNode(complexRectsLayer2, image->rootLayer()); image->addNode(paintLayer5, image->rootLayer()); image->addNode(paintLayer2, groupLayer); image->addNode(complexRectsLayer1, groupLayer); image->addNode(paintLayer3, groupLayer); image->addNode(paintLayer4, groupLayer); QRect testRect(10,10,10,10); // Empty rect to show we don't need any cropping QRect cropRect; KisMergeWalker walker(cropRect); { QString order("root,paint5,cplx2,group,paint1," "paint4,paint3,cplx1,paint2"); QStringList orderList = order.split(','); QRect accessRect(-7,-7,44,44); reportStartWith("paint3"); walker.collectRects(paintLayer3, testRect); verifyResult(walker, orderList, accessRect, true, true); } { QString order("root,paint5,cplx2,group,paint1," "paint4,paint3,cplx1,paint2"); QStringList orderList = order.split(','); QRect accessRect(-10,-10,50,50); reportStartWith("paint2"); walker.collectRects(paintLayer2, testRect); verifyResult(walker, orderList, accessRect, true, true); } { QString order("root,paint5,cplx2,group,paint1"); QStringList orderList = order.split(','); QRect accessRect(3,3,24,24); reportStartWith("paint5"); walker.collectRects(paintLayer5, testRect); verifyResult(walker, orderList, accessRect, false, true); } { /** * Test cropping */ QString order("root,paint5,cplx2,group,paint1," "paint4,paint3,cplx1,paint2"); QStringList orderList = order.split(','); QRect accessRect(0,0,40,40); reportStartWith("paint2 (with cropping)"); walker.setCropRect(image->bounds()); walker.collectRects(paintLayer2, testRect); walker.setCropRect(cropRect); verifyResult(walker, orderList, accessRect, true, true); } { /** * Test uncropped values */ QString order("root,paint5,cplx2,group,paint1," "paint4,paint3,cplx1,paint2"); QStringList orderList = order.split(','); QRect cropRect(9,9,12,12); QRect accessRect(cropRect); reportStartWith("paint2 (testing uncropped)"); walker.setCropRect(cropRect); walker.collectRects(paintLayer2, testRect); walker.setCropRect(cropRect); verifyResult(walker, orderList, accessRect, true, false); QCOMPARE(walker.uncroppedChangeRect(), QRect(4,4,22,22)); } } #include "kis_psd_layer_style.h" /* +----------+ |root | | group ls | | paint 3 | | paint 2 | | paint 1 | +----------+ */ void KisWalkersTest::testComplexGroupVisiting() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 512, 512, colorSpace, "walker test"); KisPSDLayerStyleSP style(new KisPSDLayerStyle()); { style->context()->keep_original = true; style->dropShadow()->setEffectEnabled(true); style->dropShadow()->setDistance(3); style->dropShadow()->setSpread(1); style->dropShadow()->setSize(7); style->dropShadow()->setNoise(0); style->dropShadow()->setKnocksOut(false); style->dropShadow()->setOpacity(50); style->dropShadow()->setAngle(0); } KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); KisLayerSP paintLayer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE_U8); KisLayerSP groupLayer = new KisGroupLayer(image, "groupls", OPACITY_OPAQUE_U8); image->addNode(paintLayer1, image->rootLayer()); image->addNode(groupLayer, image->rootLayer()); image->addNode(paintLayer2, groupLayer); image->addNode(paintLayer3, groupLayer); groupLayer->setLayerStyle(style); QRect testRect(10,10,10,10); // Empty rect to show we don't need any cropping QRect cropRect; KisMergeWalker walker(cropRect); { QString order("root,groupls,paint1," "paint3,paint2"); QStringList orderList = order.split(','); QRect accessRect(-8,-8,46,46); reportStartWith("paint3"); walker.collectRects(paintLayer3, testRect); verifyResult(walker, orderList, accessRect, true, true); } } /* +------------+ |root | | layer 5 | | cplx 2 | | group | | paint 4 | | cplxacc 1 | | paint 3 | | cplx 1 | | paint 2 | | paint 1 | +------------+ */ void KisWalkersTest::testComplexAccessVisiting() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 512, 512, colorSpace, "walker test"); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); KisLayerSP paintLayer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE_U8); KisLayerSP paintLayer4 = new KisPaintLayer(image, "paint4", OPACITY_OPAQUE_U8); KisLayerSP paintLayer5 = new KisPaintLayer(image, "paint5", OPACITY_OPAQUE_U8); KisLayerSP groupLayer = new KisGroupLayer(image, "group", OPACITY_OPAQUE_U8); KisLayerSP complexRectsLayer1 = new ComplexRectsLayer(image, "cplx1", OPACITY_OPAQUE_U8); KisLayerSP complexRectsLayer2 = new ComplexRectsLayer(image, "cplx2", OPACITY_OPAQUE_U8); KisLayerSP complexAccess = new ComplexAccessLayer(image, "cplxacc1", OPACITY_OPAQUE_U8); image->addNode(paintLayer1, image->rootLayer()); image->addNode(groupLayer, image->rootLayer()); image->addNode(complexRectsLayer2, image->rootLayer()); image->addNode(paintLayer5, image->rootLayer()); image->addNode(paintLayer2, groupLayer); image->addNode(complexRectsLayer1, groupLayer); image->addNode(paintLayer3, groupLayer); image->addNode(complexAccess, groupLayer); image->addNode(paintLayer4, groupLayer); QRect testRect(10,10,10,10); // Empty rect to show we don't need any cropping QRect cropRect; KisMergeWalker walker(cropRect); { QString order("root,paint5,cplx2,group,paint1," "paint4,cplxacc1,paint3,cplx1,paint2"); QStringList orderList = order.split(','); QRect accessRect = QRect(-7,-7,44,44) | QRect(0,0,30,30).translated(70,0); reportStartWith("paint3"); walker.collectRects(paintLayer3, testRect); verifyResult(walker, orderList, accessRect, true, true); } } void KisWalkersTest::checkNotification(const KisMergeWalker::CloneNotification ¬ification, const QString &name, const QRect &rect) { QCOMPARE(notification.m_layer->name(), name); QCOMPARE(notification.m_dirtyRect, rect); } /* +--------------+ |root | | paint 3 <--+ | | cplx 2 | | | group <--+ | | | cplx 1 | | | | paint 2 | | | | clone 2 -+ | | | clone 1 ---+ | | paint 1 | +--------------+ */ void KisWalkersTest::testCloneNotificationsVisiting() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 512, 512, colorSpace, "walker test"); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); KisLayerSP paintLayer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE_U8); KisLayerSP groupLayer = new KisGroupLayer(image, "group", OPACITY_OPAQUE_U8); KisLayerSP complexRectsLayer1 = new ComplexRectsLayer(image, "cplx1", OPACITY_OPAQUE_U8); KisLayerSP complexRectsLayer2 = new ComplexRectsLayer(image, "cplx2", OPACITY_OPAQUE_U8); KisLayerSP cloneLayer1 = new KisCloneLayer(paintLayer3, image, "clone1", OPACITY_OPAQUE_U8); KisLayerSP cloneLayer2 = new KisCloneLayer(groupLayer, image, "clone2", OPACITY_OPAQUE_U8); image->addNode(paintLayer1, image->rootLayer()); image->addNode(cloneLayer1, image->rootLayer()); image->addNode(cloneLayer2, image->rootLayer()); image->addNode(groupLayer, image->rootLayer()); image->addNode(complexRectsLayer2, image->rootLayer()); image->addNode(paintLayer3, image->rootLayer()); image->addNode(paintLayer2, groupLayer); image->addNode(complexRectsLayer1, groupLayer); QRect testRect(10,10,10,10); QRect cropRect(5,5,507,507); KisMergeWalker walker(cropRect); { QString order("root,paint3,cplx2,group,clone2,clone1,paint1," "cplx1,paint2"); QStringList orderList = order.split(','); QRect accessRect = QRect(5,5,35,35); reportStartWith("paint2"); walker.collectRects(paintLayer2, testRect); verifyResult(walker, orderList, accessRect, true, true); const KisMergeWalker::CloneNotificationsVector vector = walker.cloneNotifications(); QCOMPARE(vector.size(), 1); checkNotification(vector[0], "group", QRect(7,7,16,16)); } } class TestingRefreshSubtreeWalker : public KisRefreshSubtreeWalker { public: TestingRefreshSubtreeWalker(QRect cropRect) : KisRefreshSubtreeWalker(cropRect) {} UpdateType type() const override { return FULL_REFRESH; } }; /* +----------+ |root | | layer 5 | | cplx 2 | | group | | paint 4 | | paint 3 | | cplx 1 | | paint 2 | | paint 1 | +----------+ */ void KisWalkersTest::testRefreshSubtreeVisiting() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 512, 512, colorSpace, "walker test"); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); KisLayerSP paintLayer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE_U8); KisLayerSP paintLayer4 = new KisPaintLayer(image, "paint4", OPACITY_OPAQUE_U8); KisLayerSP paintLayer5 = new KisPaintLayer(image, "paint5", OPACITY_OPAQUE_U8); KisLayerSP groupLayer = new KisGroupLayer(image, "group", OPACITY_OPAQUE_U8); KisLayerSP complexRectsLayer1 = new ComplexRectsLayer(image, "cplx1", OPACITY_OPAQUE_U8); KisLayerSP complexRectsLayer2 = new ComplexRectsLayer(image, "cplx2", OPACITY_OPAQUE_U8); image->addNode(paintLayer1, image->rootLayer()); image->addNode(groupLayer, image->rootLayer()); image->addNode(complexRectsLayer2, image->rootLayer()); image->addNode(paintLayer5, image->rootLayer()); image->addNode(paintLayer2, groupLayer); image->addNode(complexRectsLayer1, groupLayer); image->addNode(paintLayer3, groupLayer); image->addNode(paintLayer4, groupLayer); QRect testRect(10,10,10,10); // Empty rect to show we don't need any cropping QRect cropRect; TestingRefreshSubtreeWalker walker(cropRect); { QString order("root,paint5,cplx2,group,paint1," "paint4,paint3,cplx1,paint2"); QStringList orderList = order.split(','); QRect accessRect(-4,-4,38,38); reportStartWith("root"); walker.collectRects(image->rootLayer(), testRect); verifyResult(walker, orderList, accessRect, false, true); } } /* +----------+ |root | | layer 5 | | cplx 2 | | group | | paint 4 | | paint 3 | | cplx 1 | | paint 2 | | paint 1 | +----------+ */ void KisWalkersTest::testFullRefreshVisiting() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 512, 512, colorSpace, "walker test"); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); KisLayerSP paintLayer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE_U8); KisLayerSP paintLayer4 = new KisPaintLayer(image, "paint4", OPACITY_OPAQUE_U8); KisLayerSP paintLayer5 = new KisPaintLayer(image, "paint5", OPACITY_OPAQUE_U8); KisLayerSP groupLayer = new KisGroupLayer(image, "group", OPACITY_OPAQUE_U8); KisLayerSP complexRectsLayer1 = new ComplexRectsLayer(image, "cplx1", OPACITY_OPAQUE_U8); KisLayerSP complexRectsLayer2 = new ComplexRectsLayer(image, "cplx2", OPACITY_OPAQUE_U8); image->addNode(paintLayer1, image->rootLayer()); image->addNode(groupLayer, image->rootLayer()); image->addNode(complexRectsLayer2, image->rootLayer()); image->addNode(paintLayer5, image->rootLayer()); image->addNode(paintLayer2, groupLayer); image->addNode(complexRectsLayer1, groupLayer); image->addNode(paintLayer3, groupLayer); image->addNode(paintLayer4, groupLayer); QRect testRect(10,10,10,10); // Empty rect to show we don't need any cropping QRect cropRect; KisFullRefreshWalker walker(cropRect); { QString order("root,paint5,cplx2,group,paint1," "group,paint4,paint3,cplx1,paint2"); QStringList orderList = order.split(','); QRect accessRect(-10,-10,50,50); reportStartWith("root"); walker.collectRects(groupLayer, testRect); verifyResult(walker, orderList, accessRect, false, true); } } /* +----------+ |root | | layer 5 | | cache1 | | group | | paint 4 | | paint 3 | | paint 2 | | paint 1 | +----------+ */ void KisWalkersTest::testCachedVisiting() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 512, 512, colorSpace, "walker test"); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); KisLayerSP paintLayer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE_U8); KisLayerSP paintLayer4 = new KisPaintLayer(image, "paint4", OPACITY_OPAQUE_U8); KisLayerSP paintLayer5 = new KisPaintLayer(image, "paint5", OPACITY_OPAQUE_U8); KisLayerSP groupLayer = new KisGroupLayer(image, "group", OPACITY_OPAQUE_U8); KisLayerSP cacheLayer1 = new CacheLayer(image, "cache1", OPACITY_OPAQUE_U8); image->addNode(paintLayer1, image->rootLayer()); image->addNode(groupLayer, image->rootLayer()); image->addNode(cacheLayer1, image->rootLayer()); image->addNode(paintLayer5, image->rootLayer()); image->addNode(paintLayer2, groupLayer); image->addNode(paintLayer3, groupLayer); image->addNode(paintLayer4, groupLayer); QRect testRect(10,10,10,10); // Empty rect to show we don't need any cropping QRect cropRect; KisMergeWalker walker(cropRect); { QString order("root,paint5,cache1,group,paint1," "paint4,paint3,paint2"); QStringList orderList = order.split(','); QRect accessRect(0,0,30,30); reportStartWith("paint3"); walker.collectRects(paintLayer3, testRect); verifyResult(walker, orderList, accessRect, true, true); } { QString order("root,paint5,cache1"); QStringList orderList = order.split(','); QRect accessRect(10,10,10,10); reportStartWith("paint5"); walker.collectRects(paintLayer5, testRect); verifyResult(walker, orderList, accessRect, false, true); } } /* +----------+ |root | | paint 2 | | paint 1 | | fmask2 | | tmask | | fmask1 | +----------+ */ void KisWalkersTest::testMasksVisiting() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 512, 512, colorSpace, "walker test"); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); image->addNode(paintLayer1, image->rootLayer()); image->addNode(paintLayer2, image->rootLayer()); KisFilterMaskSP filterMask1 = new KisFilterMask(); KisFilterMaskSP filterMask2 = new KisFilterMask(); KisTransparencyMaskSP transparencyMask = new KisTransparencyMask(); KisFilterSP filter = KisFilterRegistry::instance()->value("blur"); Q_ASSERT(filter); KisFilterConfigurationSP configuration1 = filter->defaultConfiguration(); KisFilterConfigurationSP configuration2 = filter->defaultConfiguration(); filterMask1->setFilter(configuration1); filterMask2->setFilter(configuration2); QRect selection1(10, 10, 20, 10); QRect selection2(30, 15, 10, 10); QRect selection3(20, 10, 20, 10); filterMask1->testingInitSelection(selection1, paintLayer1); transparencyMask->testingInitSelection(selection2, paintLayer1); filterMask2->testingInitSelection(selection3, paintLayer1); image->addNode(filterMask1, paintLayer1); image->addNode(transparencyMask, paintLayer1); image->addNode(filterMask2, paintLayer1); QRect testRect(5,5,30,30); // Empty rect to show we don't need any cropping QRect cropRect; KisMergeWalker walker(cropRect); { QString order("root,paint2,paint1"); QStringList orderList = order.split(','); QRect accessRect(0,0,40,40); reportStartWith("tmask"); walker.collectRects(transparencyMask, testRect); verifyResult(walker, orderList, accessRect, true, false); } KisTestWalker twalker; { QString order("paint2,root," "root_TF,paint2_TA,paint1_BP"); QStringList orderList = order.split(','); reportStartWith("tmask"); twalker.startTrip(transparencyMask->projectionLeaf()); QCOMPARE(twalker.popResult(), orderList); } } /* +----------+ |root | | paint 2 | | paint 1 | | fmask2 | | tmask | | fmask1 | +----------+ */ void KisWalkersTest::testMasksVisitingNoFilthy() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 512, 512, colorSpace, "walker test"); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); image->addNode(paintLayer1, image->rootLayer()); image->addNode(paintLayer2, image->rootLayer()); KisFilterMaskSP filterMask1 = new KisFilterMask(); KisFilterMaskSP filterMask2 = new KisFilterMask(); KisTransparencyMaskSP transparencyMask = new KisTransparencyMask(); KisFilterSP filter = KisFilterRegistry::instance()->value("blur"); Q_ASSERT(filter); KisFilterConfigurationSP configuration1 = filter->defaultConfiguration(); KisFilterConfigurationSP configuration2 = filter->defaultConfiguration(); filterMask1->setFilter(configuration1); filterMask2->setFilter(configuration2); QRect selection1(10, 10, 20, 10); QRect selection2(30, 15, 10, 10); QRect selection3(20, 10, 20, 10); filterMask1->testingInitSelection(selection1, paintLayer1); transparencyMask->testingInitSelection(selection2, paintLayer1); filterMask2->testingInitSelection(selection3, paintLayer1); image->addNode(filterMask1, paintLayer1); image->addNode(transparencyMask, paintLayer1); image->addNode(filterMask2, paintLayer1); QRect testRect(5,5,30,30); // Empty rect to show we don't need any cropping QRect cropRect; { KisMergeWalker walker(cropRect, KisMergeWalker::NO_FILTHY); QString order("root,paint2,paint1"); QStringList orderList = order.split(','); QRect accessRect(0,0,40,40); reportStartWith("tmask"); walker.collectRects(transparencyMask, testRect); verifyResult(walker, orderList, accessRect, true, false); } { KisMergeWalker walker(cropRect, KisMergeWalker::NO_FILTHY); QString order("root,paint2,paint1"); QStringList orderList = order.split(','); QRect accessRect(5,5,30,30); reportStartWith("paint1"); walker.collectRects(paintLayer1, testRect); verifyResult(walker, orderList, accessRect, false, false); } } /* +----------+ |root | | paint 2 | | paint 1 | | fmask2 | | tmask | | fmask1 | +----------+ */ void KisWalkersTest::testMasksOverlapping() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 512, 512, colorSpace, "walker test"); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); image->addNode(paintLayer1, image->rootLayer()); image->addNode(paintLayer2, image->rootLayer()); KisFilterMaskSP filterMask1 = new KisFilterMask(); KisFilterMaskSP filterMask2 = new KisFilterMask(); KisTransparencyMaskSP transparencyMask = new KisTransparencyMask(); KisFilterSP blurFilter = KisFilterRegistry::instance()->value("blur"); KisFilterSP invertFilter = KisFilterRegistry::instance()->value("invert"); Q_ASSERT(blurFilter); Q_ASSERT(invertFilter); KisFilterConfigurationSP blurConfiguration = blurFilter->defaultConfiguration(); KisFilterConfigurationSP invertConfiguration = invertFilter->defaultConfiguration(); filterMask1->setFilter(invertConfiguration); filterMask2->setFilter(blurConfiguration); QRect selection1(0, 0, 128, 128); QRect selection2(128, 0, 128, 128); QRect selection3(0, 64, 256, 128); filterMask1->testingInitSelection(selection1, paintLayer1); transparencyMask->testingInitSelection(selection2, paintLayer1); filterMask2->testingInitSelection(selection3, paintLayer1); image->addNode(filterMask1, paintLayer1); image->addNode(transparencyMask, paintLayer1); image->addNode(filterMask2, paintLayer1); // Empty rect to show we don't need any cropping QRect cropRect; QRect IMRect(10,10,50,50); QRect TMRect(135,10,40,40); QRect IMTMRect(10,10,256,40); QList updateList; { // FIXME: now we do not crop change rect if COMPOSITE_OVER is used! UpdateTestJob job = {"IM", paintLayer1, IMRect, "", QRect(0,0,0,0), true, false}; updateList.append(job); } { UpdateTestJob job = {"IM", filterMask1, IMRect, "", QRect(0,0,0,0), true, false}; updateList.append(job); } { UpdateTestJob job = {"IM", transparencyMask, IMRect, "root,paint2,paint1", QRect(5,10,60,55), true, false}; updateList.append(job); } { UpdateTestJob job = {"IM", filterMask2, IMRect, "root,paint2,paint1", IMRect, false, false}; updateList.append(job); } /******* Dirty rect: transparency mask *********/ { UpdateTestJob job = {"TM", paintLayer1, TMRect, "root,paint2,paint1", TMRect, false, false}; updateList.append(job); } { UpdateTestJob job = {"TM", filterMask1, TMRect, "root,paint2,paint1", TMRect, false, false}; updateList.append(job); } { UpdateTestJob job = {"TM", transparencyMask, TMRect, "root,paint2,paint1", TMRect, false, false}; updateList.append(job); } { UpdateTestJob job = {"TM", filterMask2, TMRect, "root,paint2,paint1", TMRect, false, false}; updateList.append(job); } /******* Dirty rect: invert + transparency mask *********/ { UpdateTestJob job = {"IMTM", paintLayer1, IMTMRect, "root,paint2,paint1", IMTMRect & selection2, true, false}; updateList.append(job); } { UpdateTestJob job = {"IMTM", filterMask1, IMTMRect, "root,paint2,paint1", IMTMRect & selection2, true, false}; updateList.append(job); } { UpdateTestJob job = {"IMTM", transparencyMask, IMTMRect, "root,paint2,paint1", IMTMRect, false, false}; updateList.append(job); } { UpdateTestJob job = {"IMTM", filterMask2, IMTMRect, "root,paint2,paint1", IMTMRect, false, false}; updateList.append(job); } Q_FOREACH (UpdateTestJob job, updateList) { KisMergeWalker walker(cropRect); reportStartWith(job.startNode->name(), job.updateRect); qDebug() << "Area:" << job.updateAreaName; walker.collectRects(job.startNode, job.updateRect); verifyResult(walker, job); } } /* +----------+ |root | | adj | | paint 1 | +----------+ */ void KisWalkersTest::testRectsChecksum() { QRect imageRect(0,0,512,512); QRect dirtyRect(100,100,100,100); const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, imageRect.width(), imageRect.height(), colorSpace, "walker test"); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisAdjustmentLayerSP adjustmentLayer = new KisAdjustmentLayer(image, "adj", 0, 0); - image->lock(); + image->barrierLock(); image->addNode(paintLayer1, image->rootLayer()); image->addNode(adjustmentLayer, image->rootLayer()); image->unlock(); KisFilterSP filter = KisFilterRegistry::instance()->value("blur"); Q_ASSERT(filter); KisFilterConfigurationSP configuration; KisMergeWalker walker(imageRect); walker.collectRects(adjustmentLayer, dirtyRect); QCOMPARE(walker.checksumValid(), true); configuration = filter->defaultConfiguration(); adjustmentLayer->setFilter(configuration); QCOMPARE(walker.checksumValid(), false); walker.recalculate(dirtyRect); QCOMPARE(walker.checksumValid(), true); configuration = filter->defaultConfiguration(); configuration->setProperty("halfWidth", 20); configuration->setProperty("halfHeight", 20); adjustmentLayer->setFilter(configuration); QCOMPARE(walker.checksumValid(), false); walker.recalculate(dirtyRect); QCOMPARE(walker.checksumValid(), true); configuration = filter->defaultConfiguration(); configuration->setProperty("halfWidth", 21); configuration->setProperty("halfHeight", 21); adjustmentLayer->setFilter(configuration); QCOMPARE(walker.checksumValid(), false); walker.recalculate(dirtyRect); QCOMPARE(walker.checksumValid(), true); } void KisWalkersTest::testGraphStructureChecksum() { QRect imageRect(0,0,512,512); QRect dirtyRect(100,100,100,100); const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, imageRect.width(), imageRect.height(), colorSpace, "walker test"); KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); - image->lock(); + image->barrierLock(); image->addNode(paintLayer1, image->rootLayer()); image->unlock(); KisMergeWalker walker(imageRect); walker.collectRects(paintLayer1, dirtyRect); QCOMPARE(walker.checksumValid(), true); - image->lock(); + image->barrierLock(); image->addNode(paintLayer2, image->rootLayer()); image->unlock(); QCOMPARE(walker.checksumValid(), false); walker.recalculate(dirtyRect); QCOMPARE(walker.checksumValid(), true); - image->lock(); + image->barrierLock(); image->moveNode(paintLayer1, image->rootLayer(), paintLayer2); image->unlock(); QCOMPARE(walker.checksumValid(), false); walker.recalculate(dirtyRect); QCOMPARE(walker.checksumValid(), true); - image->lock(); + image->barrierLock(); image->removeNode(paintLayer1); image->unlock(); QCOMPARE(walker.checksumValid(), false); walker.recalculate(dirtyRect); QCOMPARE(walker.checksumValid(), true); } KISTEST_MAIN(KisWalkersTest) diff --git a/libs/pigment/KoCmykColorSpaceMaths.h b/libs/pigment/KoCmykColorSpaceMaths.h index 46c3a89d5d..0e63940ac6 100644 --- a/libs/pigment/KoCmykColorSpaceMaths.h +++ b/libs/pigment/KoCmykColorSpaceMaths.h @@ -1,740 +1,115 @@ /* * Copyright (c) 2006,2007,2010 Cyrille Berger * Copyright (c) 2017,2020 L. E. Segovia * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCMYKCOLORSPACEMATHS_H_ #define KOCMYKCOLORSPACEMATHS_H_ #include #include #include "kritapigment_export.h" #include #include "KoChannelInfo.h" #include "KoLut.h" #include #undef _T /** * This is an empty mainWindow that needs to be "specialized" for each possible * numerical type (quint8, quint16...). * * It needs to defines some static constant fields : * - zeroValue : the zero for this numerical type * - unitValue : the maximum value of the normal dynamic range * - max : the maximum value * - min : the minimum value * - epsilon : a value close to zero but different of zero * - bits : the bit depth * * And some types : * - compositetype the type used for composite operations (usually one with * a higher bit depth) * * This class is specialized to handle the floating point bounds of the CMYK color space. */ template class KoCmykColorSpaceMathsTraits { public: }; /** * No changes for integer color spaces. */ template<> class KRITAPIGMENT_EXPORT KoCmykColorSpaceMathsTraits : public KoColorSpaceMathsTraits { }; template<> class KRITAPIGMENT_EXPORT KoCmykColorSpaceMathsTraits : public KoColorSpaceMathsTraits { }; template<> class KRITAPIGMENT_EXPORT KoCmykColorSpaceMathsTraits : public KoColorSpaceMathsTraits { }; template<> class KRITAPIGMENT_EXPORT KoCmykColorSpaceMathsTraits : public KoColorSpaceMathsTraits { }; #include #ifdef HAVE_OPENEXR #include template<> class KRITAPIGMENT_EXPORT KoCmykColorSpaceMathsTraits : public KoColorSpaceMathsTraits { public: static const half zeroValueCMYK; static const half unitValueCMYK; static const half halfValueCMYK; }; #endif template<> class KRITAPIGMENT_EXPORT KoCmykColorSpaceMathsTraits : public KoColorSpaceMathsTraits { public: static const float zeroValueCMYK; static const float unitValueCMYK; static const float halfValueCMYK; }; template<> class KRITAPIGMENT_EXPORT KoCmykColorSpaceMathsTraits : public KoColorSpaceMathsTraits { public: static const double zeroValueCMYK; static const double unitValueCMYK; static const double halfValueCMYK; }; -//template -//struct KoIntegerToFloat { -// inline float operator()(_T_ f) const -// { -// return f / float(KoColorSpaceMathsTraits<_T_>::max); -// } -//}; - -//struct KoLuts { - -// static KRITAPIGMENT_EXPORT const Ko::FullLut< KoIntegerToFloat, float, quint16> Uint16ToFloat; -// static KRITAPIGMENT_EXPORT const Ko::FullLut< KoIntegerToFloat, float, quint8> Uint8ToFloat; -//}; - -///** -// * This class defines some elementary operations used by various color -// * space. It's intended to be generic, but some specialization exists -// * either for optimization or just for being buildable. -// * -// * @param _T some numerical type with an existing trait -// * @param _Tdst some other numerical type with an existing trait, it is -// * only needed if different of _T -// */ -//template < typename _T, typename _Tdst = _T > -//class KoColorSpaceMaths -//{ -// typedef KoColorSpaceMathsTraits<_T> traits; -// typedef typename traits::compositetype src_compositetype; -// typedef typename KoColorSpaceMathsTraits<_Tdst>::compositetype dst_compositetype; - -//public: -// inline static _Tdst multiply(_T a, _Tdst b) { -// return (dst_compositetype(a)*b) / KoColorSpaceMathsTraits<_Tdst>::unitValue; -// } - -// inline static _Tdst multiply(_T a, _Tdst b, _Tdst c) { -// return (dst_compositetype(a)*b*c) / (dst_compositetype(KoColorSpaceMathsTraits<_Tdst>::unitValue) * KoColorSpaceMathsTraits<_T>::unitValue); -// } - -// /** -// * Division : (a * MAX ) / b -// * @param a -// * @param b -// */ -// inline static dst_compositetype divide(_T a, _Tdst b) { -// return (dst_compositetype(a) * KoColorSpaceMathsTraits<_Tdst>::unitValue) / b; -// } - -// /** -// * Inversion : unitValue - a -// * @param a -// */ -// inline static _T invert(_T a) { -// return traits::unitValue - a; -// } - -// /** -// * Blending : (a * alpha) + b * (1 - alpha) -// * @param a -// * @param b -// * @param alpha -// */ -// inline static _T blend(_T a, _T b, _T alpha) { -// src_compositetype c = ((src_compositetype(a) - b) * alpha) / traits::unitValue; -// return c + b; -// } - -// /** -// * This function will scale a value of type _T to fit into a _Tdst. -// */ -// inline static _Tdst scaleToA(_T a) { -// return _Tdst(dst_compositetype(a) * KoColorSpaceMathsTraits<_Tdst>::unitValue / KoColorSpaceMathsTraits<_T>::unitValue); -// } - -// inline static dst_compositetype clamp(dst_compositetype val) { -// return qBound(KoColorSpaceMathsTraits<_Tdst>::min, val, KoColorSpaceMathsTraits<_Tdst>::max); -// } - -// /** -// * Clamps the composite type on higher border only. That is a fast path -// * for scale-only transformations -// */ -// inline static _Tdst clampAfterScale(dst_compositetype val) { -// return qMin(val, KoColorSpaceMathsTraits<_Tdst>::max); -// } -//}; - -////------------------------------ double specialization ------------------------------// -//template<> -//inline quint8 KoColorSpaceMaths::scaleToA(double a) -//{ -// double v = a * 255; -// return float2int(CLAMP(v, 0, 255)); -//} - -//template<> -//inline double KoColorSpaceMaths::scaleToA(quint8 a) -//{ -// return KoLuts::Uint8ToFloat(a); -//} - -//template<> -//inline quint16 KoColorSpaceMaths::scaleToA(double a) -//{ -// double v = a * 0xFFFF; -// return float2int(CLAMP(v, 0, 0xFFFF)); -//} - -//template<> -//inline double KoColorSpaceMaths::scaleToA(quint16 a) -//{ -// return KoLuts::Uint16ToFloat(a); -//} - -//template<> -//inline double KoColorSpaceMaths::clamp(double a) -//{ -// return a; -//} - -////------------------------------ float specialization ------------------------------// - -//template<> -//inline float KoColorSpaceMaths::scaleToA(double a) -//{ -// return (float)a; -//} - -//template<> -//inline double KoColorSpaceMaths::scaleToA(float a) -//{ -// return a; -//} - -//template<> -//inline quint16 KoColorSpaceMaths::scaleToA(float a) -//{ -// float v = a * 0xFFFF; -// return (quint16)float2int(CLAMP(v, 0, 0xFFFF)); -//} - -//template<> -//inline float KoColorSpaceMaths::scaleToA(quint16 a) -//{ -// return KoLuts::Uint16ToFloat(a); -//} - -//template<> -//inline quint8 KoColorSpaceMaths::scaleToA(float a) -//{ -// float v = a * 255; -// return (quint8)float2int(CLAMP(v, 0, 255)); -//} - -//template<> -//inline float KoColorSpaceMaths::scaleToA(quint8 a) -//{ -// return KoLuts::Uint8ToFloat(a); -//} - -//template<> -//inline float KoColorSpaceMaths::blend(float a, float b, float alpha) -//{ -// return (a - b) * alpha + b; -//} - -//template<> -//inline double KoColorSpaceMaths::clamp(double a) -//{ -// return a; -//} - -////------------------------------ half specialization ------------------------------// - -//#ifdef HAVE_OPENEXR - -//template<> -//inline half KoColorSpaceMaths::scaleToA(double a) -//{ -// return (half)a; -//} - -//template<> -//inline double KoColorSpaceMaths::scaleToA(half a) -//{ -// return a; -//} - -//template<> -//inline float KoColorSpaceMaths::scaleToA(half a) -//{ -// return a; -//} - -//template<> -//inline half KoColorSpaceMaths::scaleToA(float a) -//{ -// return (half) a; -//} - -//template<> -//inline quint8 KoColorSpaceMaths::scaleToA(half a) -//{ -// half v = a * 255; -// return (quint8)(CLAMP(v, 0, 255)); -//} - -//template<> -//inline half KoColorSpaceMaths::scaleToA(quint8 a) -//{ -// return a *(1.0 / 255.0); -//} -//template<> -//inline quint16 KoColorSpaceMaths::scaleToA(half a) -//{ -// double v = a * 0xFFFF; -// return (quint16)(CLAMP(v, 0, 0xFFFF)); -//} - -//template<> -//inline half KoColorSpaceMaths::scaleToA(quint16 a) -//{ -// return a *(1.0 / 0xFFFF); -//} - -//template<> -//inline half KoColorSpaceMaths::scaleToA(half a) -//{ -// return a; -//} - -//template<> -//inline half KoColorSpaceMaths::blend(half a, half b, half alpha) -//{ -// return (a - b) * alpha + b; -//} - -//template<> -//inline double KoColorSpaceMaths::clamp(double a) -//{ -// return a; -//} - - -//#endif - -////------------------------------ quint8 specialization ------------------------------// - -//template<> -//inline quint8 KoColorSpaceMaths::multiply(quint8 a, quint8 b) -//{ -// return (quint8)UINT8_MULT(a, b); -//} - - -//template<> -//inline quint8 KoColorSpaceMaths::multiply(quint8 a, quint8 b, quint8 c) -//{ -// return (quint8)UINT8_MULT3(a, b, c); -//} - -//template<> -//inline KoColorSpaceMathsTraits::compositetype -//KoColorSpaceMaths::divide(quint8 a, quint8 b) -//{ -// return UINT8_DIVIDE(a, b); -//} - -//template<> -//inline quint8 KoColorSpaceMaths::invert(quint8 a) -//{ -// return ~a; -//} - -//template<> -//inline quint8 KoColorSpaceMaths::blend(quint8 a, quint8 b, quint8 c) -//{ -// return UINT8_BLEND(a, b, c); -//} - -////------------------------------ quint16 specialization ------------------------------// - -//template<> -//inline quint16 KoColorSpaceMaths::multiply(quint16 a, quint16 b) -//{ -// return (quint16)UINT16_MULT(a, b); -//} - -//template<> -//inline KoColorSpaceMathsTraits::compositetype -//KoColorSpaceMaths::divide(quint16 a, quint16 b) -//{ -// return UINT16_DIVIDE(a, b); -//} - -//template<> -//inline quint16 KoColorSpaceMaths::invert(quint16 a) -//{ -// return ~a; -//} - -////------------------------------ various specialization ------------------------------// - - -//// TODO: use more functions from KoIntegersMaths to do the computation - -///// This specialization is needed because the default implementation won't work when scaling up -//template<> -//inline quint16 KoColorSpaceMaths::scaleToA(quint8 a) -//{ -// return UINT8_TO_UINT16(a); -//} - -//template<> -//inline quint8 KoColorSpaceMaths::scaleToA(quint16 a) -//{ -// return UINT16_TO_UINT8(a); -//} - - -//// Due to once again a bug in gcc, there is the need for those specialized functions: - -//template<> -//inline quint8 KoColorSpaceMaths::scaleToA(quint8 a) -//{ -// return a; -//} - -//template<> -//inline quint16 KoColorSpaceMaths::scaleToA(quint16 a) -//{ -// return a; -//} - -//template<> -//inline float KoColorSpaceMaths::scaleToA(float a) -//{ -// return a; -//} - -//namespace Arithmetic -//{ -// const static qreal pi = 3.14159265358979323846; - -// template -// inline T mul(T a, T b) { return KoColorSpaceMaths::multiply(a, b); } - -// template -// inline T mul(T a, T b, T c) { return KoColorSpaceMaths::multiply(a, b, c); } - -//// template -//// inline T mul(T a, T b) { -//// typedef typename KoColorSpaceMathsTraits::compositetype composite_type; -//// return T(composite_type(a) * b / KoColorSpaceMathsTraits::unitValue); -//// } -//// -//// template -//// inline T mul(T a, T b, T c) { -//// typedef typename KoColorSpaceMathsTraits::compositetype composite_type; -//// return T((composite_type(a) * b * c) / (composite_type(KoColorSpaceMathsTraits::unitValue) * KoColorSpaceMathsTraits::unitValue)); -//// } - -// template -// inline T inv(T a) { return KoColorSpaceMaths::invert(a); } - -// template -// inline T lerp(T a, T b, T alpha) { return KoColorSpaceMaths::blend(b, a, alpha); } - -// template -// inline TRet scale(T a) { return KoColorSpaceMaths::scaleToA(a); } - -// template -// inline typename KoColorSpaceMathsTraits::compositetype -// div(T a, T b) { return KoColorSpaceMaths::divide(a, b); } - -// template -// inline T clamp(typename KoColorSpaceMathsTraits::compositetype a) { -// return KoColorSpaceMaths::clamp(a); -// } - -// template -// inline T min(T a, T b, T c) { -// b = (a < b) ? a : b; -// return (b < c) ? b : c; -// } - -// template -// inline T max(T a, T b, T c) { -// b = (a > b) ? a : b; -// return (b > c) ? b : c; -// } - -// template -// inline T zeroValue() { return KoColorSpaceMathsTraits::zeroValue; } - -// template -// inline T halfValue() { return KoColorSpaceMathsTraits::halfValue; } - -// template -// inline T unitValue() { return KoColorSpaceMathsTraits::unitValue; } - -// template -// inline T unionShapeOpacity(T a, T b) { -// typedef typename KoColorSpaceMathsTraits::compositetype composite_type; -// return T(composite_type(a) + b - mul(a,b)); -// } - -// template -// inline T blend(T src, T srcAlpha, T dst, T dstAlpha, T cfValue) { -// return mul(inv(srcAlpha), dstAlpha, dst) + mul(inv(dstAlpha), srcAlpha, src) + mul(dstAlpha, srcAlpha, cfValue); -// } -//} - -//struct HSYType -//{ -// template -// inline static TReal getLightness(TReal r, TReal g, TReal b) { -// return TReal(0.299)*r + TReal(0.587)*g + TReal(0.114)*b; -// } - -// template -// inline static TReal getSaturation(TReal r, TReal g, TReal b) { -// return Arithmetic::max(r,g,b) - Arithmetic::min(r,g,b); -// } -//}; - -//struct HSIType -//{ -// template -// inline static TReal getLightness(TReal r, TReal g, TReal b) { -// return (r + g + b) * TReal(0.33333333333333333333); // (r + g + b) / 3.0 -// } - -// template -// inline static TReal getSaturation(TReal r, TReal g, TReal b) { -// TReal max = Arithmetic::max(r, g, b); -// TReal min = Arithmetic::min(r, g, b); -// TReal chroma = max - min; - -// return (chroma > std::numeric_limits::epsilon()) ? -// (TReal(1.0) - min / getLightness(r, g, b)) : TReal(0.0); -// } -//}; - -//struct HSLType -//{ -// template -// inline static TReal getLightness(TReal r, TReal g, TReal b) { -// TReal max = Arithmetic::max(r, g, b); -// TReal min = Arithmetic::min(r, g, b); -// return (max + min) * TReal(0.5); -// } - -// template -// inline static TReal getSaturation(TReal r, TReal g, TReal b) { -// TReal max = Arithmetic::max(r, g, b); -// TReal min = Arithmetic::min(r, g, b); -// TReal chroma = max - min; -// TReal light = (max + min) * TReal(0.5); -// TReal div = TReal(1.0) - std::abs(TReal(2.0)*light - TReal(1.0)); - -// if(div > std::numeric_limits::epsilon()) -// return chroma / div; - -// return TReal(1.0); -// } -//}; - -//struct HSVType -//{ -// template -// inline static TReal getLightness(TReal r, TReal g, TReal b) { -// return Arithmetic::max(r,g,b); -// } - -// template -// inline static TReal getSaturation(TReal r, TReal g, TReal b) { -// TReal max = Arithmetic::max(r, g, b); -// TReal min = Arithmetic::min(r, g, b); -// return (max == TReal(0.0)) ? TReal(0.0) : (max - min) / max; -// } -//}; - -//template -//TReal getHue(TReal r, TReal g, TReal b) { -// TReal min = Arithmetic::min(r, g, b); -// TReal max = Arithmetic::max(r, g, b); -// TReal chroma = max - min; - -// TReal hue = TReal(-1.0); - -// if(chroma > std::numeric_limits::epsilon()) { - -//// return atan2(TReal(2.0)*r - g - b, TReal(1.73205080756887729353)*(g - b)); - -// if(max == r) // between yellow and magenta -// hue = (g - b) / chroma; -// else if(max == g) // between cyan and yellow -// hue = TReal(2.0) + (b - r) / chroma; -// else if(max == b) // between magenta and cyan -// hue = TReal(4.0) + (r - g) / chroma; - -// if(hue < -std::numeric_limits::epsilon()) -// hue += TReal(6.0); - -// hue /= TReal(6.0); -// } - -//// hue = (r == max) ? (b-g) : (g == max) ? TReal(2.0)+(r-b) : TReal(4.0)+(g-r); - -// return hue; -//} - -//template -//void getRGB(TReal& r, TReal& g, TReal& b, TReal hue) { -// // 0 red -> (1,0,0) -// // 1 yellow -> (1,1,0) -// // 2 green -> (0,1,0) -// // 3 cyan -> (0,1,1) -// // 4 blue -> (0,0,1) -// // 5 maenta -> (1,0,1) -// // 6 red -> (1,0,0) - -// if(hue < -std::numeric_limits::epsilon()) { -// r = g = b = TReal(0.0); -// return; -// } - -// int i = int(hue * TReal(6.0)); -// TReal x = hue * TReal(6.0) - i; -// TReal y = TReal(1.0) - x; - -// switch(i % 6){ -// case 0: { r=TReal(1.0), g=x , b=TReal(0.0); } break; -// case 1: { r=y , g=TReal(1.0), b=TReal(0.0); } break; -// case 2: { r=TReal(0.0), g=TReal(1.0), b=x ; } break; -// case 3: { r=TReal(0.0), g=y , b=TReal(1.0); } break; -// case 4: { r=x , g=TReal(0.0), b=TReal(1.0); } break; -// case 5: { r=TReal(1.0), g=TReal(0.0), b=y ; } break; -// } -//} - -//template -//inline static TReal getLightness(TReal r, TReal g, TReal b) { -// return HSXType::getLightness(r, g, b); -//} - -//template -//inline void addLightness(TReal& r, TReal& g, TReal& b, TReal light) -//{ -// using namespace Arithmetic; - -// r += light; -// g += light; -// b += light; - -// TReal l = HSXType::getLightness(r, g, b); -// TReal n = min(r, g, b); -// TReal x = max(r, g, b); - -// if(n < TReal(0.0)) { -// TReal iln = TReal(1.0) / (l-n); -// r = l + ((r-l) * l) * iln; -// g = l + ((g-l) * l) * iln; -// b = l + ((b-l) * l) * iln; -// } - -// if(x > TReal(1.0) && (x-l) > std::numeric_limits::epsilon()) { -// TReal il = TReal(1.0) - l; -// TReal ixl = TReal(1.0) / (x - l); -// r = l + ((r-l) * il) * ixl; -// g = l + ((g-l) * il) * ixl; -// b = l + ((b-l) * il) * ixl; -// } -//} - -//template -//inline void setLightness(TReal& r, TReal& g, TReal& b, TReal light) -//{ -// addLightness(r,g,b, light - HSXType::getLightness(r,g,b)); -//} - -//template -//inline static TReal getSaturation(TReal r, TReal g, TReal b) { -// return HSXType::getSaturation(r, g, b); -//} - -//template -//inline void setSaturation(TReal& r, TReal& g, TReal& b, TReal sat) -//{ -// int min = 0; -// int mid = 1; -// int max = 2; -// TReal rgb[3] = {r, g, b}; - -// if(rgb[mid] < rgb[min]) { -// int tmp = min; -// min = mid; -// mid = tmp; -// } - -// if(rgb[max] < rgb[mid]) { -// int tmp = mid; -// mid = max; -// max = tmp; -// } - -// if(rgb[mid] < rgb[min]) { -// int tmp = min; -// min = mid; -// mid = tmp; -// } - -// if((rgb[max] - rgb[min]) > TReal(0.0)) { -// rgb[mid] = ((rgb[mid]-rgb[min]) * sat) / (rgb[max]-rgb[min]); -// rgb[max] = sat; -// rgb[min] = TReal(0.0); - -// r = rgb[0]; -// g = rgb[1]; -// b = rgb[2]; -// } -// else r = g = b = TReal(0.0); -//} - #endif diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp index 393ab69cdd..2fa5c84b85 100644 --- a/libs/ui/KisMainWindow.cpp +++ b/libs/ui/KisMainWindow.cpp @@ -1,2849 +1,2851 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2006 David Faure Copyright (C) 2007, 2009 Thomas zander Copyright (C) 2010 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" #include // qt includes #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 #include #include #include #include #include #include #include #include #include #include "kis_selection_manager.h" #include "kis_icon_utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoDockFactoryBase.h" #include "KoDocumentInfoDlg.h" #include "KoDocumentInfo.h" #include "KoFileDialog.h" #include #include #include #include #include #include "KoToolDocker.h" #include "KoToolBoxDocker_p.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_ANDROID #include #endif #include #include #include "dialogs/kis_about_application.h" #include "dialogs/kis_delayed_save_dialog.h" #include "dialogs/kis_dlg_preferences.h" #include "kis_action_manager.h" #include "KisApplication.h" #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_custom_image_widget.h" #include #include "kis_group_layer.h" #include "kis_image_from_clipboard_widget.h" #include "kis_image.h" #include #include "KisImportExportManager.h" #include "kis_mainwindow_observer.h" #include "kis_memory_statistics_server.h" #include "kis_node.h" #include "KisOpenPane.h" #include "kis_paintop_box.h" #include "KisPart.h" #include "KisPrintJob.h" #include "KisResourceServerProvider.h" #include "kis_signal_compressor_with_param.h" #include "kis_statusbar.h" #include "KisView.h" #include "KisViewManager.h" #include "thememanager.h" #include "kis_animation_importer.h" #include "dialogs/kis_dlg_import_image_sequence.h" #include #include "KisWindowLayoutManager.h" #include #include "KisWelcomePageWidget.h" #include #include #include "KisCanvasWindow.h" #include "kis_action.h" #include class ToolDockerFactory : public KoDockFactoryBase { public: ToolDockerFactory() : KoDockFactoryBase() { } QString id() const override { return "sharedtooldocker"; } QDockWidget* createDockWidget() override { KoToolDocker* dockWidget = new KoToolDocker(); return dockWidget; } DockPosition defaultDockPosition() const override { return DockRight; } }; class Q_DECL_HIDDEN KisMainWindow::Private { public: Private(KisMainWindow *parent, QUuid id) : q(parent) , id(id) , dockWidgetMenu(new KActionMenu(i18nc("@action:inmenu", "&Dockers"), parent)) , windowMenu(new KActionMenu(i18nc("@action:inmenu", "&Window"), parent)) , documentMenu(new KActionMenu(i18nc("@action:inmenu", "New &View"), parent)) , workspaceMenu(new KActionMenu(i18nc("@action:inmenu", "Wor&kspace"), parent)) , welcomePage(new KisWelcomePageWidget(parent)) , widgetStack(new QStackedWidget(parent)) , mdiArea(new QMdiArea(parent)) , windowMapper(new KisSignalMapper(parent)) , documentMapper(new KisSignalMapper(parent)) #ifdef Q_OS_ANDROID , fileManager(new KisAndroidFileManager(parent)) #endif { if (id.isNull()) this->id = QUuid::createUuid(); welcomeScroller = new QScrollArea(); welcomeScroller->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); welcomeScroller->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); welcomeScroller->setWidget(welcomePage); welcomeScroller->setWidgetResizable(true); widgetStack->addWidget(welcomeScroller); widgetStack->addWidget(mdiArea); mdiArea->setTabsMovable(true); mdiArea->setActivationOrder(QMdiArea::ActivationHistoryOrder); } ~Private() { qDeleteAll(toolbarList); } KisMainWindow *q {0}; QUuid id; KisViewManager *viewManager {0}; QPointer activeView; QList toolbarList; bool firstTime {true}; bool windowSizeDirty {false}; bool readOnly {false}; KisAction *showDocumentInfo {0}; KisAction *saveAction {0}; KisAction *saveActionAs {0}; // KisAction *printAction; // KisAction *printActionPreview; // KisAction *exportPdf {0}; KisAction *importAnimation {0}; KisAction *closeAll {0}; // KisAction *reloadFile; KisAction *importFile {0}; KisAction *exportFile {0}; KisAction *undo {0}; KisAction *redo {0}; KisAction *newWindow {0}; KisAction *close {0}; KisAction *mdiCascade {0}; KisAction *mdiTile {0}; KisAction *mdiNextWindow {0}; KisAction *mdiPreviousWindow {0}; KisAction *toggleDockers {0}; KisAction *toggleDockerTitleBars {0}; KisAction *toggleDetachCanvas {0}; KisAction *fullScreenMode {0}; KisAction *showSessionManager {0}; KisAction *expandingSpacers[2]; KActionMenu *dockWidgetMenu; KActionMenu *windowMenu; KActionMenu *documentMenu; KActionMenu *workspaceMenu; KHelpMenu *helpMenu {0}; KRecentFilesAction *recentFiles {0}; KisResourceModel *workspacemodel {0}; QScopedPointer undoActionsUpdateManager; QString lastExportLocation; QMap dockWidgetsMap; QByteArray dockerStateBeforeHiding; KoToolDocker *toolOptionsDocker {0}; QCloseEvent *deferredClosingEvent {0}; Digikam::ThemeManager *themeManager {0}; QScrollArea *welcomeScroller {0}; KisWelcomePageWidget *welcomePage {0}; QStackedWidget *widgetStack {0}; QMdiArea *mdiArea; QMdiSubWindow *activeSubWindow {0}; KisSignalMapper *windowMapper; KisSignalMapper *documentMapper; KisCanvasWindow *canvasWindow {0}; QByteArray lastExportedFormat; QScopedPointer > tabSwitchCompressor; QMutex savingEntryMutex; KConfigGroup windowStateConfig; QUuid workspaceBorrowedBy; KisSignalAutoConnectionsStore screenConnectionsStore; #ifdef Q_OS_ANDROID KisAndroidFileManager *fileManager; #endif KisActionManager * actionManager() { return viewManager->actionManager(); } QTabBar* findTabBarHACK() { QObjectList objects = mdiArea->children(); Q_FOREACH (QObject *object, objects) { QTabBar *bar = qobject_cast(object); if (bar) { return bar; } } return 0; } }; KisMainWindow::KisMainWindow(QUuid uuid) : KXmlGuiWindow() , d(new Private(this, uuid)) { d->workspacemodel = KisResourceModelProvider::resourceModel(ResourceType::Workspaces); connect(d->workspacemodel, SIGNAL(afterResourcesLayoutReset()), this, SLOT(updateWindowMenu())); d->viewManager = new KisViewManager(this, actionCollection()); KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this); d->windowStateConfig = KSharedConfig::openConfig()->group("MainWindow"); setStandardToolBarMenuEnabled(true); setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); setDockNestingEnabled(true); qApp->setStartDragDistance(25); // 25 px is a distance that works well for Tablet and Mouse events #ifdef Q_OS_MACOS setUnifiedTitleAndToolBarOnMac(true); #endif connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts())); connect(this, SIGNAL(themeChanged()), d->viewManager, SLOT(updateIcons())); connect(KisPart::instance(), SIGNAL(documentClosed(QString)), SLOT(updateWindowMenu())); connect(KisPart::instance(), SIGNAL(documentOpened(QString)), SLOT(updateWindowMenu())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged())); actionCollection()->addAssociatedWidget(this); KoPluginLoader::instance()->load("Krita/ViewPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), d->viewManager, false); // Load the per-application plugins (Right now, only Python) We do this only once, when the first mainwindow is being created. KoPluginLoader::instance()->load("Krita/ApplicationPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), qApp, true); KoToolBoxFactory toolBoxFactory; QDockWidget *toolbox = createDockWidget(&toolBoxFactory); toolbox->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); KisConfig cfg(true); if (cfg.toolOptionsInDocker()) { ToolDockerFactory toolDockerFactory; d->toolOptionsDocker = qobject_cast(createDockWidget(&toolDockerFactory)); d->toolOptionsDocker->toggleViewAction()->setEnabled(true); } QMap dockwidgetActions; dockwidgetActions[toolbox->toggleViewAction()->text()] = toolbox->toggleViewAction(); Q_FOREACH (const QString & docker, KoDockRegistry::instance()->keys()) { KoDockFactoryBase *factory = KoDockRegistry::instance()->value(docker); QDockWidget *dw = createDockWidget(factory); dockwidgetActions[dw->toggleViewAction()->text()] = dw->toggleViewAction(); } if (d->toolOptionsDocker) { dockwidgetActions[d->toolOptionsDocker->toggleViewAction()->text()] = d->toolOptionsDocker->toggleViewAction(); } connect(KoToolManager::instance(), SIGNAL(toolOptionWidgetsChanged(KoCanvasController*,QList >)), this, SLOT(newOptionWidgets(KoCanvasController*,QList >))); Q_FOREACH (QString title, dockwidgetActions.keys()) { d->dockWidgetMenu->addAction(dockwidgetActions[title]); } Q_FOREACH (QDockWidget *wdg, dockWidgets()) { if ((wdg->features() & QDockWidget::DockWidgetClosable) == 0) { wdg->setVisible(true); } } Q_FOREACH (KoCanvasObserverBase* observer, canvasObservers()) { observer->setObservedCanvas(0); KisMainwindowObserver* mainwindowObserver = dynamic_cast(observer); if (mainwindowObserver) { mainwindowObserver->setViewManager(d->viewManager); } } // Load all the actions from the tool plugins Q_FOREACH(KoToolFactoryBase *toolFactory, KoToolRegistry::instance()->values()) { toolFactory->createActions(actionCollection()); } d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setTabPosition(QTabWidget::North); d->mdiArea->setTabsClosable(true); // Tab close button override // Windows just has a black X, and Ubuntu has a dark x that is hard to read // just switch this icon out for all OSs so it is easier to see d->mdiArea->setStyleSheet("QTabBar::close-button { image: url(:/pics/broken-preset.png) }"); setCentralWidget(d->widgetStack); d->widgetStack->setCurrentIndex(0); connect(d->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(subWindowActivated())); connect(d->windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*))); connect(d->documentMapper, SIGNAL(mapped(QObject*)), this, SLOT(newView(QObject*))); d->canvasWindow = new KisCanvasWindow(this); actionCollection()->addAssociatedWidget(d->canvasWindow); createActions(); // the welcome screen needs to grab actions...so make sure this line goes after the createAction() so they exist d->welcomePage->setMainWindow(this); setAutoSaveSettings(d->windowStateConfig, false); subWindowActivated(); updateWindowMenu(); if (isHelpMenuEnabled() && !d->helpMenu) { // workaround for KHelpMenu (or rather KAboutData::applicationData()) internally // not using the Q*Application metadata ATM, which results e.g. in the bugreport wizard // not having the app version preset // fixed hopefully in KF5 5.22.0, patch pending QGuiApplication *app = qApp; KAboutData aboutData(app->applicationName(), app->applicationDisplayName(), app->applicationVersion()); aboutData.setOrganizationDomain(app->organizationDomain().toUtf8()); d->helpMenu = new KHelpMenu(this, aboutData, false); // workaround-less version: // d->helpMenu = new KHelpMenu(this, QString()/*unused*/, false); // The difference between using KActionCollection->addAction() is that // these actions do not get tied to the MainWindow. What does this all do? KActionCollection *actions = d->viewManager->actionCollection(); QAction *helpContentsAction = d->helpMenu->action(KHelpMenu::menuHelpContents); QAction *whatsThisAction = d->helpMenu->action(KHelpMenu::menuWhatsThis); QAction *reportBugAction = d->helpMenu->action(KHelpMenu::menuReportBug); QAction *switchLanguageAction = d->helpMenu->action(KHelpMenu::menuSwitchLanguage); QAction *aboutAppAction = d->helpMenu->action(KHelpMenu::menuAboutApp); QAction *aboutKdeAction = d->helpMenu->action(KHelpMenu::menuAboutKDE); if (helpContentsAction) { actions->addAction(helpContentsAction->objectName(), helpContentsAction); } if (whatsThisAction) { actions->addAction(whatsThisAction->objectName(), whatsThisAction); } if (reportBugAction) { actions->addAction(reportBugAction->objectName(), reportBugAction); } if (switchLanguageAction) { actions->addAction(switchLanguageAction->objectName(), switchLanguageAction); } if (aboutAppAction) { actions->addAction(aboutAppAction->objectName(), aboutAppAction); } if (aboutKdeAction) { actions->addAction(aboutKdeAction->objectName(), aboutKdeAction); } connect(d->helpMenu, SIGNAL(showAboutApplication()), SLOT(showAboutApplication())); } // KDE' libs 4''s help contents action is broken outside kde, for some reason... We can handle it just as easily ourselves QAction *helpAction = actionCollection()->action("help_contents"); helpAction->disconnect(); connect(helpAction, SIGNAL(triggered()), this, SLOT(showManual())); #if 0 //check for colliding shortcuts QSet existingShortcuts; Q_FOREACH (QAction* action, actionCollection()->actions()) { if(action->shortcut() == QKeySequence(0)) { continue; } dbgKrita << "shortcut " << action->text() << " " << action->shortcut(); Q_ASSERT(!existingShortcuts.contains(action->shortcut())); existingShortcuts.insert(action->shortcut()); } #endif configChanged(); // If we have customized the toolbars, load that first setLocalXMLFile(KoResourcePaths::locateLocal("data", "krita4.xmlgui")); setXMLFile(":/kxmlgui5/krita4.xmlgui"); guiFactory()->addClient(this); connect(guiFactory(), SIGNAL(makingChanges(bool)), SLOT(slotXmlGuiMakingChanges(bool))); // Create and plug toolbar list for Settings menu QList toolbarList; Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) { KToolBar * toolBar = ::qobject_cast(it); toolBar->setMovable(KisConfig(true).readEntry("LockAllDockerPanels", false)); if (toolBar) { if (toolBar->objectName() == "BrushesAndStuff") { toolBar->setEnabled(false); } KToggleAction* act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this); actionCollection()->addAction(toolBar->objectName().toUtf8(), act); act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle()))); connect(act, SIGNAL(toggled(bool)), this, SLOT(slotToolbarToggled(bool))); act->setChecked(!toolBar->isHidden()); toolbarList.append(act); } else { warnUI << "Toolbar list contains a " << it->metaObject()->className() << " which is not a toolbar!"; } } KToolBar::setToolBarsLocked(KisConfig(true).readEntry("LockAllDockerPanels", false)); plugActionList("toolbarlist", toolbarList); d->toolbarList = toolbarList; applyToolBarLayout(); d->viewManager->updateGUI(); d->viewManager->updateIcons(); QTimer::singleShot(1000, this, SLOT(checkSanity())); { using namespace std::placeholders; // For _1 placeholder std::function callback( std::bind(&KisMainWindow::switchTab, this, _1)); d->tabSwitchCompressor.reset( new KisSignalCompressorWithParam(500, callback, KisSignalCompressor::FIRST_INACTIVE)); } if (cfg.readEntry("CanvasOnlyActive", false)) { QString currentWorkspace = cfg.readEntry("CurrentWorkspace", "Default"); KoResourceServer * rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResourceSP workspace = rserver->resourceByName(currentWorkspace); if (workspace) { restoreWorkspace(workspace->resourceId()); } cfg.writeEntry("CanvasOnlyActive", false); menuBar()->setVisible(true); } this->winId(); // Ensures the native window has been created. QWindow *window = this->windowHandle(); connect(window, SIGNAL(screenChanged(QScreen *)), this, SLOT(windowScreenChanged(QScreen *))); } KisMainWindow::~KisMainWindow() { // Q_FOREACH (QAction *ac, actionCollection()->actions()) { // QAction *action = qobject_cast(ac); // if (action) { // qDebug() << "", "").replace("", "") // << "\n\ticonText=" << action->iconText().replace("&", "&") // << "\n\tshortcut=" << action->shortcut().toString() // << "\n\tisCheckable=" << QString((action->isChecked() ? "true" : "false")) // << "\n\tstatusTip=" << action->statusTip() // << "\n/>\n" ; // } // else { // dbgKrita << "Got a non-qaction:" << ac->objectName(); // } // } // The doc and view might still exist (this is the case when closing the window) KisPart::instance()->removeMainWindow(this); delete d->viewManager; delete d; } QUuid KisMainWindow::id() const { return d->id; } void KisMainWindow::addView(KisView *view, QMdiSubWindow *subWindow) { if (d->activeView == view && !subWindow) return; if (d->activeView) { d->activeView->disconnect(this); } // register the newly created view in the input manager viewManager()->inputManager()->addTrackedCanvas(view->canvasBase()); showView(view, subWindow); updateCaption(); emit restoringDone(); if (d->activeView) { connect(d->activeView, SIGNAL(titleModified(QString,bool)), SLOT(slotDocumentTitleModified())); connect(d->viewManager->statusBar(), SIGNAL(memoryStatusUpdated()), this, SLOT(updateCaption())); } } void KisMainWindow::notifyChildViewDestroyed(KisView *view) { /** * If we are the last view of the window, Qt will not activate another tab * before destroying tab/window. In this case we should clear all the dangling * pointers manually by setting the current view to null */ viewManager()->inputManager()->removeTrackedCanvas(view->canvasBase()); if (view->canvasBase() == viewManager()->canvasBase()) { viewManager()->setCurrentView(0); } } void KisMainWindow::showView(KisView *imageView, QMdiSubWindow *subwin) { if (imageView && activeView() != imageView) { // XXX: find a better way to initialize this! imageView->setViewManager(d->viewManager); imageView->canvasBase()->setFavoriteResourceManager(d->viewManager->paintOpBox()->favoriteResourcesManager()); imageView->slotLoadingFinished(); if (!subwin) { subwin = d->mdiArea->addSubWindow(imageView); } else { subwin->setWidget(imageView); } imageView->setSubWindow(subwin); subwin->setAttribute(Qt::WA_DeleteOnClose, true); connect(subwin, SIGNAL(destroyed()), SLOT(updateWindowMenu())); KisConfig cfg(true); subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setWindowIcon(qApp->windowIcon()); #ifdef Q_OS_MACOS connect(subwin, SIGNAL(destroyed()), SLOT(updateSubwindowFlags())); updateSubwindowFlags(); #endif if (d->mdiArea->subWindowList().size() == 1) { imageView->showMaximized(); } else { imageView->show(); } /** * Hack alert! * * Here we explicitly request KoToolManager to emit all the tool * activation signals, to reinitialize the tool options docker. * * That is needed due to a design flaw we have in the * initialization procedure. The tool in the KoToolManager is * initialized in KisView::setViewManager() calls, which * happens early enough. During this call the tool manager * requests KoCanvasControllerWidget to emit the signal to * update the widgets in the tool docker. *But* at that moment * of time the view is not yet connected to the main window, * because it happens in KisViewManager::setCurrentView a bit * later. This fact makes the widgets updating signals be lost * and never reach the tool docker. * * So here we just explicitly call the tool activation stub. */ KoToolManager::instance()->initializeCurrentToolForCanvas(); // No, no, no: do not try to call this _before_ the show() has // been called on the view; only when that has happened is the // opengl context active, and very bad things happen if we tell // the dockers to update themselves with a view if the opengl // context is not active. setActiveView(imageView); updateWindowMenu(); updateCaption(); } } void KisMainWindow::slotPreferences() { QScopedPointer dlgPreferences(new KisDlgPreferences(this)); if (dlgPreferences->editPreferences()) { KisConfigNotifier::instance()->notifyConfigChanged(); KisConfigNotifier::instance()->notifyPixelGridModeChanged(); KisImageConfigNotifier::instance()->notifyConfigChanged(); // XXX: should this be changed for the views in other windows as well? Q_FOREACH (QPointer koview, KisPart::instance()->views()) { KisViewManager *view = qobject_cast(koview); if (view) { // Update the settings for all nodes -- they don't query // KisConfig directly because they need the settings during // compositing, and they don't connect to the config notifier // because nodes are not QObjects (because only one base class // can be a QObject). KisNode* node = dynamic_cast(view->image()->rootLayer().data()); node->updateSettings(); } } updateWindowMenu(); d->viewManager->showHideScrollbars(); } } void KisMainWindow::slotThemeChanged() { // save theme changes instantly KConfigGroup group( KSharedConfig::openConfig(), "theme"); group.writeEntry("Theme", d->themeManager->currentThemeName()); // reload action icons! Q_FOREACH (QAction *action, actionCollection()->actions()) { KisIconUtils::updateIcon(action); } if (d->mdiArea) { d->mdiArea->setPalette(qApp->palette()); for (int i=0; imdiArea->subWindowList().size(); i++) { QMdiSubWindow *window = d->mdiArea->subWindowList().at(i); if (window) { window->setPalette(qApp->palette()); KisView *view = qobject_cast(window->widget()); if (view) { view->slotThemeChanged(qApp->palette()); } } } } emit themeChanged(); } bool KisMainWindow::canvasDetached() const { return centralWidget() != d->widgetStack; } void KisMainWindow::setCanvasDetached(bool detach) { if (detach == canvasDetached()) return; QWidget *outgoingWidget = centralWidget() ? takeCentralWidget() : nullptr; QWidget *incomingWidget = d->canvasWindow->swapMainWidget(outgoingWidget); if (incomingWidget) { setCentralWidget(incomingWidget); } if (detach) { d->canvasWindow->show(); } else { d->canvasWindow->hide(); } } void KisMainWindow::slotFileSelected(QString path) { QString url = path; if (!url.isEmpty()) { bool res = openDocument(QUrl::fromLocalFile(url), Import); if (!res) { warnKrita << "Loading" << url << "failed"; } } } void KisMainWindow::slotEmptyFilePath() { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The chosen file's location could not be found. Does it exist?")); } QWidget * KisMainWindow::canvasWindow() const { return d->canvasWindow; } void KisMainWindow::updateReloadFileAction(KisDocument *doc) { Q_UNUSED(doc); // d->reloadFile->setEnabled(doc && !doc->url().isEmpty()); } void KisMainWindow::setReadWrite(bool readwrite) { d->saveAction->setEnabled(readwrite); d->importFile->setEnabled(readwrite); d->readOnly = !readwrite; updateCaption(); } void KisMainWindow::addRecentURL(const QUrl &url) { // Add entry to recent documents list // (call coming from KisDocument because it must work with cmd line, template dlg, file/open, etc.) if (!url.isEmpty()) { bool ok = true; if (url.isLocalFile()) { QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile(); const QStringList tmpDirs = KoResourcePaths::resourceDirs("tmp"); for (QStringList::ConstIterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it) { if (path.contains(*it)) { ok = false; // it's in the tmp resource } } const QStringList templateDirs = KoResourcePaths::findDirs("templates"); for (QStringList::ConstIterator it = templateDirs.begin() ; ok && it != templateDirs.end() ; ++it) { if (path.contains(*it)) { ok = false; // it's in the templates directory. break; } } } if (ok) { d->recentFiles->addUrl(url); } saveRecentFiles(); } } void KisMainWindow::saveRecentFiles() { // Save list of recent files KSharedConfigPtr config = KSharedConfig::openConfig(); d->recentFiles->saveEntries(config->group("RecentFiles")); config->sync(); // Tell all windows to reload their list, after saving // Doesn't work multi-process, but it's a start Q_FOREACH (KisMainWindow *mw, KisPart::instance()->mainWindows()) { if (mw != this) { mw->reloadRecentFileList(); } } } QList KisMainWindow::recentFilesUrls() { return d->recentFiles->urls(); } void KisMainWindow::clearRecentFiles() { d->recentFiles->clear(); d->welcomePage->populateRecentDocuments(); } void KisMainWindow::removeRecentUrl(const QUrl &url) { d->recentFiles->removeUrl(url); KSharedConfigPtr config = KSharedConfig::openConfig(); d->recentFiles->saveEntries(config->group("RecentFiles")); config->sync(); } void KisMainWindow::reloadRecentFileList() { d->recentFiles->loadEntries(KSharedConfig::openConfig()->group("RecentFiles")); } void KisMainWindow::updateCaption() { if (!d->mdiArea->activeSubWindow()) { updateCaption(QString(), false); } else if (d->activeView && d->activeView->document() && d->activeView->image()){ KisDocument *doc = d->activeView->document(); QString caption(doc->caption()); caption = "RESOURCES REWRITE GOING ON " + caption; if (d->readOnly) { caption += " [" + i18n("Write Protected") + "] "; } if (doc->isRecovered()) { caption += " [" + i18n("Recovered") + "] "; } // show the file size for the document KisMemoryStatisticsServer::Statistics m_fileSizeStats = KisMemoryStatisticsServer::instance()->fetchMemoryStatistics(d->activeView ? d->activeView->image() : 0); if (m_fileSizeStats.imageSize) { caption += QString(" (").append( KFormat().formatByteSize(m_fileSizeStats.imageSize)).append( ")"); } updateCaption(caption, doc->isModified()); if (!doc->url().fileName().isEmpty()) { d->saveAction->setToolTip(i18n("Save as %1", doc->url().fileName())); } else { d->saveAction->setToolTip(i18n("Save")); } } } void KisMainWindow::updateCaption(const QString &caption, bool modified) { QString versionString = KritaVersionWrapper::versionString(true); QString title = caption; if (!title.contains(QStringLiteral("[*]"))) { // append the placeholder so that the modified mechanism works title.append(QStringLiteral(" [*]")); } if (d->mdiArea->activeSubWindow()) { #if defined(KRITA_ALPHA) || defined (KRITA_BETA) || defined (KRITA_RC) d->mdiArea->activeSubWindow()->setWindowTitle(QString("%1: %2").arg(versionString).arg(title)); #else d->mdiArea->activeSubWindow()->setWindowTitle(title); #endif d->mdiArea->activeSubWindow()->setWindowModified(modified); } else { #if defined(KRITA_ALPHA) || defined (KRITA_BETA) || defined (KRITA_RC) setWindowTitle(QString("%1: %2").arg(versionString).arg(title)); #else setWindowTitle(title); #endif } setWindowModified(modified); } KisView *KisMainWindow::activeView() const { if (d->activeView) { return d->activeView; } return 0; } bool KisMainWindow::openDocument(const QUrl &url, OpenFlags flags) { if (!QFile(url.toLocalFile()).exists()) { if (!(flags & BatchMode)) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The file %1 does not exist.", url.url())); } d->recentFiles->removeUrl(url); //remove the file from the recent-opened-file-list saveRecentFiles(); return false; } return openDocumentInternal(url, flags); } bool KisMainWindow::openDocumentInternal(const QUrl &url, OpenFlags flags) { if (!url.isLocalFile()) { qWarning() << "KisMainWindow::openDocumentInternal. Not a local file:" << url; return false; } KisDocument *newdoc = KisPart::instance()->createDocument(); if (flags & BatchMode) { newdoc->setFileBatchMode(true); } d->firstTime = true; connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); connect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); KisDocument::OpenFlags openFlags = KisDocument::None; // XXX: Why this duplication of of OpenFlags... if (flags & RecoveryFile) { openFlags |= KisDocument::RecoveryFile; } bool openRet = !(flags & Import) ? newdoc->openUrl(url, openFlags) : newdoc->importDocument(url); if (!openRet) { delete newdoc; return false; } KisPart::instance()->addDocument(newdoc); updateReloadFileAction(newdoc); if (!QFileInfo(url.toLocalFile()).isWritable()) { setReadWrite(false); } if (flags & RecoveryFile && ( url.toLocalFile().startsWith(QDir::tempPath()) || url.toLocalFile().startsWith(QDir::homePath())) ) { newdoc->setUrl(QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation) + "/" + QFileInfo(url.toLocalFile()).fileName())); newdoc->save(false, 0); } return true; } void KisMainWindow::showDocument(KisDocument *document) { Q_FOREACH(QMdiSubWindow *subwindow, d->mdiArea->subWindowList()) { KisView *view = qobject_cast(subwindow->widget()); KIS_SAFE_ASSERT_RECOVER_NOOP(view); if (view) { if (view->document() == document) { setActiveSubWindow(subwindow); return; } } } addViewAndNotifyLoadingCompleted(document); } KisView* KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document, QMdiSubWindow *subWindow) { showWelcomeScreen(false); // see workaround in function header KisView *view = KisPart::instance()->createView(document, d->viewManager, this); addView(view, subWindow); emit guiLoadingFinished(); return view; } QStringList KisMainWindow::showOpenFileDialog(bool isImporting) { KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument"); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); dialog.setCaption(isImporting ? i18n("Import Images") : i18n("Open Images")); return dialog.filenames(); } // Separate from openDocument to handle async loading (remote URLs) void KisMainWindow::slotLoadCompleted() { KisDocument *newdoc = qobject_cast(sender()); if (newdoc && newdoc->image()) { addViewAndNotifyLoadingCompleted(newdoc); disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); emit loadCompleted(); } } void KisMainWindow::slotLoadCanceled(const QString & errMsg) { KisUsageLogger::log(QString("Loading canceled. Error:").arg(errMsg)); if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); // ... can't delete the document, it's the one who emitted the signal... KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); } void KisMainWindow::slotSaveCanceled(const QString &errMsg) { KisUsageLogger::log(QString("Saving canceled. Error:").arg(errMsg)); if (!errMsg.isEmpty()) { // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); } slotSaveCompleted(); } void KisMainWindow::slotSaveCompleted() { KisUsageLogger::log(QString("Saving Completed")); KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); if (d->deferredClosingEvent) { KXmlGuiWindow::closeEvent(d->deferredClosingEvent); } } bool KisMainWindow::hackIsSaving() const { StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); return !l.owns_lock(); } bool KisMainWindow::installBundle(const QString &fileName) const { QFileInfo from(fileName); QFileInfo to(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName()); if (to.exists()) { QFile::remove(to.canonicalFilePath()); } return QFile::copy(fileName, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName()); } QImage KisMainWindow::layoutThumbnail() { int size = 256; qreal scale = qreal(size)/qreal(qMax(geometry().width(), geometry().height())); QImage layoutThumbnail = QImage(qRound(geometry().width()*scale), qRound(geometry().height()*scale), QImage::Format_ARGB32); QPainter gc(&layoutThumbnail); gc.fillRect(0, 0, layoutThumbnail.width(), layoutThumbnail.height(), this->palette().dark()); for (int childW = 0; childW< children().size(); childW++) { if (children().at(childW)->isWidgetType()) { QWidget *w = dynamic_cast(children().at(childW)); if (w->isVisible()) { QRect wRect = QRectF(w->geometry().x()*scale , w->geometry().y()*scale , w->geometry().width()*scale , w->geometry().height()*scale ).toRect(); wRect = wRect.intersected(layoutThumbnail.rect().adjusted(-1, -1, -1, -1)); gc.setBrush(this->palette().window()); if (w == d->widgetStack) { gc.setBrush(d->mdiArea->background()); } gc.setPen(this->palette().windowText().color()); gc.drawRect(wRect); } } } gc.end(); return layoutThumbnail; } bool KisMainWindow::saveDocument(KisDocument *document, bool saveas, bool isExporting) { if (!document) { return true; } /** * Make sure that we cannot enter this method twice! * * The lower level functions may call processEvents() so * double-entry is quite possible to achieve. Here we try to lock * the mutex, and if it is failed, just cancel saving. */ StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); if (!l.owns_lock()) return false; // no busy wait for saving because it is dangerous! KisDelayedSaveDialog dlg(document->image(), KisDelayedSaveDialog::SaveDialog, 0, this); dlg.blockIfImageIsBusy(); if (dlg.result() == KisDelayedSaveDialog::Rejected) { return false; } else if (dlg.result() == KisDelayedSaveDialog::Ignored) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("You are saving a file while the image is " "still rendering. The saved file may be " "incomplete or corrupted.\n\n" "Please select a location where the original " "file will not be overridden!")); saveas = true; } if (document->isRecovered()) { saveas = true; } if (document->url().isEmpty()) { saveas = true; } connect(document, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); connect(document, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); QByteArray nativeFormat = document->nativeFormatMimeType(); QByteArray oldMimeFormat = document->mimeType(); QUrl suggestedURL = document->url(); QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); if (!mimeFilter.contains(oldMimeFormat)) { dbgUI << "KisMainWindow::saveDocument no export filter for" << oldMimeFormat; // --- don't setOutputMimeType in case the user cancels the Save As // dialog and then tries to just plain Save --- // suggest a different filename extension (yes, we fortunately don't all live in a world of magic :)) QString suggestedFilename = QFileInfo(suggestedURL.toLocalFile()).completeBaseName(); if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name suggestedFilename = suggestedFilename + "." + KisMimeDatabase::suffixesForMimeType(KIS_MIME_TYPE).first(); suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename); suggestedURL.setPath(suggestedURL.path() + suggestedFilename); } // force the user to choose outputMimeType saveas = true; } bool ret = false; if (document->url().isEmpty() || isExporting || saveas) { // if you're just File/Save As'ing to change filter options you // don't want to be reminded about overwriting files etc. bool justChangingFilterOptions = false; KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveAs"); dialog.setCaption(isExporting ? i18n("Exporting") : i18n("Saving As")); //qDebug() << ">>>>>" << isExporting << d->lastExportLocation << d->lastExportedFormat << QString::fromLatin1(document->mimeType()); if (isExporting && !d->lastExportLocation.isEmpty() && !d->lastExportLocation.contains(QDir::tempPath())) { // Use the location where we last exported to, if it's set, as the opening location for the file dialog QString proposedPath = QFileInfo(d->lastExportLocation).absolutePath(); // If the document doesn't have a filename yet, use the title QString proposedFileName = suggestedURL.isEmpty() ? document->documentInfo()->aboutInfo("title") : QFileInfo(suggestedURL.toLocalFile()).completeBaseName(); // Use the last mimetype we exported to by default QString proposedMimeType = d->lastExportedFormat.isEmpty() ? "" : d->lastExportedFormat; QString proposedExtension = KisMimeDatabase::suffixesForMimeType(proposedMimeType).first().remove("*,"); // Set the default dir: this overrides the one loaded from the config file, since we're exporting and the lastExportLocation is not empty dialog.setDefaultDir(proposedPath + "/" + proposedFileName + "." + proposedExtension, true); dialog.setMimeTypeFilters(mimeFilter, proposedMimeType); } else { // Get the last used location for saving KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString proposedPath = group.readEntry("SaveAs", ""); // if that is empty, get the last used location for loading if (proposedPath.isEmpty()) { proposedPath = group.readEntry("OpenDocument", ""); } // If that is empty, too, use the Pictures location. if (proposedPath.isEmpty()) { proposedPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); } // But only use that if the suggestedUrl, that is, the document's own url is empty, otherwise // open the location where the document currently is. dialog.setDefaultDir(suggestedURL.isEmpty() ? proposedPath : suggestedURL.toLocalFile(), true); // If exporting, default to all supported file types if user is exporting QByteArray default_mime_type = ""; if (!isExporting) { // otherwise use the document's mimetype, or if that is empty, kra, which is the savest. default_mime_type = document->mimeType().isEmpty() ? nativeFormat : document->mimeType(); } dialog.setMimeTypeFilters(mimeFilter, QString::fromLatin1(default_mime_type)); } QUrl newURL = QUrl::fromUserInput(dialog.filename()); if (newURL.isLocalFile()) { QString fn = newURL.toLocalFile(); if (QFileInfo(fn).completeSuffix().isEmpty()) { fn.append(KisMimeDatabase::suffixesForMimeType(nativeFormat).first()); newURL = QUrl::fromLocalFile(fn); } } if (document->documentInfo()->aboutInfo("title") == i18n("Unnamed")) { QString fn = newURL.toLocalFile(); QFileInfo info(fn); document->documentInfo()->setAboutInfo("title", info.completeBaseName()); } QByteArray outputFormat = nativeFormat; QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newURL.toLocalFile(), false); outputFormat = outputFormatString.toLatin1(); if (!isExporting) { justChangingFilterOptions = (newURL == document->url()) && (outputFormat == document->mimeType()); } else { QString path = QFileInfo(d->lastExportLocation).absolutePath(); QString filename = QFileInfo(document->url().toLocalFile()).completeBaseName(); justChangingFilterOptions = (QFileInfo(newURL.toLocalFile()).absolutePath() == path) && (QFileInfo(newURL.toLocalFile()).completeBaseName() == filename) && (outputFormat == d->lastExportedFormat); } bool bOk = true; if (newURL.isEmpty()) { bOk = false; } if (bOk) { bool wantToSave = true; // don't change this line unless you know what you're doing :) if (!justChangingFilterOptions) { if (!document->isNativeFormat(outputFormat)) wantToSave = true; } if (wantToSave) { if (!isExporting) { // Save As ret = document->saveAs(newURL, outputFormat, true); if (ret) { dbgUI << "Successful Save As!"; KisPart::instance()->addRecentURLToAllMainWindows(newURL); setReadWrite(true); } else { dbgUI << "Failed Save As!"; } } else { // Export ret = document->exportDocument(newURL, outputFormat); if (ret) { d->lastExportLocation = newURL.toLocalFile(); d->lastExportedFormat = outputFormat; } } } // if (wantToSave) { else ret = false; } // if (bOk) { else ret = false; } else { // saving // We cannot "export" into the currently // opened document. We are not Gimp. KIS_ASSERT_RECOVER_NOOP(!isExporting); // be sure document has the correct outputMimeType! if (document->isModified()) { ret = document->save(true, 0); } if (!ret) { dbgUI << "Failed Save!"; } } updateReloadFileAction(document); updateCaption(); return ret; } void KisMainWindow::undo() { if (activeView()) { activeView()->document()->undoStack()->undo(); } } void KisMainWindow::redo() { if (activeView()) { activeView()->document()->undoStack()->redo(); } } void KisMainWindow::closeEvent(QCloseEvent *e) { if (hackIsSaving()) { e->setAccepted(false); return; } if (!KisPart::instance()->closingSession()) { QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only"); if ((action) && (action->isChecked())) { action->setChecked(false); } // Save session when last window is closed if (KisPart::instance()->mainwindowCount() == 1) { bool closeAllowed = KisPart::instance()->closeSession(); if (!closeAllowed) { e->setAccepted(false); return; } } } d->mdiArea->closeAllSubWindows(); QList childrenList = d->mdiArea->subWindowList(); if (childrenList.isEmpty()) { d->deferredClosingEvent = e; saveWindowState(true); d->canvasWindow->close(); } else { e->setAccepted(false); } } void KisMainWindow::saveWindowSettings() { KSharedConfigPtr config = KSharedConfig::openConfig(); if (d->windowSizeDirty ) { dbgUI << "KisMainWindow::saveWindowSettings"; KConfigGroup group = d->windowStateConfig; KWindowConfig::saveWindowSize(windowHandle(), group); config->sync(); d->windowSizeDirty = false; } if (!d->activeView || d->activeView->document()) { // Save toolbar position into the config file of the app, under the doc's component name KConfigGroup group = d->windowStateConfig; saveMainWindowSettings(group); // Save collapsible state of dock widgets for (QMap::const_iterator i = d->dockWidgetsMap.constBegin(); i != d->dockWidgetsMap.constEnd(); ++i) { if (i.value()->widget()) { KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key()); dockGroup.writeEntry("Collapsed", i.value()->widget()->isHidden()); dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool()); dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value())); dockGroup.writeEntry("xPosition", (int) i.value()->widget()->x()); dockGroup.writeEntry("yPosition", (int) i.value()->widget()->y()); dockGroup.writeEntry("width", (int) i.value()->widget()->width()); dockGroup.writeEntry("height", (int) i.value()->widget()->height()); } } } KSharedConfig::openConfig()->sync(); resetAutoSaveSettings(); // Don't let KMainWindow override the good stuff we wrote down } void KisMainWindow::resizeEvent(QResizeEvent * e) { d->windowSizeDirty = true; KXmlGuiWindow::resizeEvent(e); } void KisMainWindow::setActiveView(KisView* view) { d->activeView = view; updateCaption(); if (d->undoActionsUpdateManager) { d->undoActionsUpdateManager->setCurrentDocument(view ? view->document() : 0); } d->viewManager->setCurrentView(view); KisWindowLayoutManager::instance()->activeDocumentChanged(view->document()); } void KisMainWindow::dragMove(QDragMoveEvent * event) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar && d->mdiArea->viewMode() == QMdiArea::TabbedView) { qWarning() << "WARNING!!! Cannot find QTabBar in the main window! Looks like Qt has changed behavior. Drag & Drop between multiple tabs might not work properly (tabs will not switch automatically)!"; } if (tabBar && tabBar->isVisible()) { QPoint pos = tabBar->mapFromGlobal(mapToGlobal(event->pos())); if (tabBar->rect().contains(pos)) { const int tabIndex = tabBar->tabAt(pos); if (tabIndex >= 0 && tabBar->currentIndex() != tabIndex) { d->tabSwitchCompressor->start(tabIndex); } } else if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } } void KisMainWindow::dragLeave() { if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } void KisMainWindow::switchTab(int index) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar) return; tabBar->setCurrentIndex(index); } void KisMainWindow::showWelcomeScreen(bool show) { d->widgetStack->setCurrentIndex(!show); } void KisMainWindow::slotFileNew() { const QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import); KisOpenPane *startupWidget = new KisOpenPane(this, mimeFilter, QStringLiteral("templates/")); startupWidget->setWindowModality(Qt::WindowModal); startupWidget->setWindowTitle(i18n("Create new document")); KisConfig cfg(true); int w = cfg.defImageWidth(); int h = cfg.defImageHeight(); const double resolution = cfg.defImageResolution(); const QString colorModel = cfg.defColorModel(); const QString colorDepth = cfg.defaultColorDepth(); const QString colorProfile = cfg.defColorProfile(); CustomDocumentWidgetItem item; item.widget = new KisCustomImageWidget(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.icon = "document-new"; item.title = i18n("Custom Document"); startupWidget->addCustomDocumentWidget(item.widget, item.title, "Custom Document", item.icon); QSize sz = KisClipboard::instance()->clipSize(); if (sz.isValid() && sz.width() != 0 && sz.height() != 0) { w = sz.width(); h = sz.height(); } item.widget = new KisImageFromClipboard(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.title = i18n("Create from Clipboard"); item.icon = "tab-new"; startupWidget->addCustomDocumentWidget(item.widget, item.title, "Create from ClipBoard", item.icon); // calls deleteLater connect(startupWidget, SIGNAL(documentSelected(KisDocument*)), KisPart::instance(), SLOT(startCustomDocument(KisDocument*))); // calls deleteLater connect(startupWidget, SIGNAL(openTemplate(QUrl)), KisPart::instance(), SLOT(openTemplate(QUrl))); startupWidget->exec(); // Cancel calls deleteLater... } void KisMainWindow::slotImportFile() { dbgUI << "slotImportFile()"; slotFileOpen(true); } void KisMainWindow::slotFileOpen(bool isImporting) { #ifndef Q_OS_ANDROID QStringList urls = showOpenFileDialog(isImporting); if (urls.isEmpty()) return; Q_FOREACH (const QString& url, urls) { if (!url.isEmpty()) { OpenFlags flags = isImporting ? Import : None; bool res = openDocument(QUrl::fromLocalFile(url), flags); if (!res) { warnKrita << "Loading" << url << "failed"; } } } #else Q_UNUSED(isImporting) d->fileManager->openImportFile(); connect(d->fileManager, SIGNAL(sigFileSelected(QString)), this, SLOT(slotFileSelected(QString))); connect(d->fileManager, SIGNAL(sigEmptyFilePath()), this, SLOT(slotEmptyFilePath())); #endif } void KisMainWindow::slotFileOpenRecent(const QUrl &url) { (void) openDocument(QUrl::fromLocalFile(url.toLocalFile()), None); } void KisMainWindow::slotFileSave() { if (saveDocument(d->activeView->document(), false, false)) { emit documentSaved(); } } void KisMainWindow::slotFileSaveAs() { if (saveDocument(d->activeView->document(), true, false)) { emit documentSaved(); } } void KisMainWindow::slotExportFile() { if (saveDocument(d->activeView->document(), true, true)) { emit documentSaved(); } } void KisMainWindow::slotShowSessionManager() { KisPart::instance()->showSessionManager(); } KoCanvasResourceProvider *KisMainWindow::resourceManager() const { return d->viewManager->canvasResourceProvider()->resourceManager(); } int KisMainWindow::viewCount() const { return d->mdiArea->subWindowList().size(); } const KConfigGroup &KisMainWindow::windowStateConfig() const { return d->windowStateConfig; } void KisMainWindow::saveWindowState(bool restoreNormalState) { if (restoreNormalState) { QAction *showCanvasOnly = d->viewManager->actionCollection()->action("view_show_canvas_only"); if (showCanvasOnly && showCanvasOnly->isChecked()) { showCanvasOnly->setChecked(false); } d->windowStateConfig.writeEntry("ko_geometry", saveGeometry().toBase64()); d->windowStateConfig.writeEntry("State", saveState().toBase64()); if (!d->dockerStateBeforeHiding.isEmpty()) { restoreState(d->dockerStateBeforeHiding); } statusBar()->setVisible(true); menuBar()->setVisible(true); saveWindowSettings(); } else { saveMainWindowSettings(d->windowStateConfig); } } bool KisMainWindow::restoreWorkspaceState(const QByteArray &state) { QByteArray oldState = saveState(); // needed because otherwise the layout isn't correctly restored in some situations Q_FOREACH (QDockWidget *dock, dockWidgets()) { dock->toggleViewAction()->setEnabled(true); dock->hide(); } bool success = KXmlGuiWindow::restoreState(state); if (!success) { KXmlGuiWindow::restoreState(oldState); return false; } return success; } bool KisMainWindow::restoreWorkspace(int workspaceId) { KisWorkspaceResourceSP workspace = KisResourceModelProvider::resourceModel(ResourceType::Workspaces) ->resourceForId(workspaceId).dynamicCast(); bool success = restoreWorkspaceState(workspace->dockerState()); if (activeKisView()) { activeKisView()->resourceProvider()->notifyLoadingWorkspace(workspace); } return success; } QByteArray KisMainWindow::borrowWorkspace(KisMainWindow *other) { QByteArray currentWorkspace = saveState(); if (!d->workspaceBorrowedBy.isNull()) { if (other->id() == d->workspaceBorrowedBy) { // We're swapping our original workspace back d->workspaceBorrowedBy = QUuid(); return currentWorkspace; } else { // Get our original workspace back before swapping with a third window KisMainWindow *borrower = KisPart::instance()->windowById(d->workspaceBorrowedBy); if (borrower) { QByteArray originalLayout = borrower->borrowWorkspace(this); borrower->restoreWorkspaceState(currentWorkspace); d->workspaceBorrowedBy = other->id(); return originalLayout; } } } d->workspaceBorrowedBy = other->id(); return currentWorkspace; } void KisMainWindow::swapWorkspaces(KisMainWindow *a, KisMainWindow *b) { QByteArray workspaceA = a->borrowWorkspace(b); QByteArray workspaceB = b->borrowWorkspace(a); a->restoreWorkspaceState(workspaceB); b->restoreWorkspaceState(workspaceA); } KisViewManager *KisMainWindow::viewManager() const { return d->viewManager; } void KisMainWindow::slotDocumentInfo() { if (!d->activeView->document()) return; KoDocumentInfo *docInfo = d->activeView->document()->documentInfo(); if (!docInfo) return; KoDocumentInfoDlg *dlg = d->activeView->document()->createDocumentInfoDialog(this, docInfo); if (dlg->exec()) { if (dlg->isDocumentSaved()) { d->activeView->document()->setModified(false); } else { d->activeView->document()->setModified(true); } d->activeView->document()->setTitleModified(); } delete dlg; } bool KisMainWindow::slotFileCloseAll() { Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { if (subwin) { if(!subwin->close()) return false; } } updateCaption(); return true; } void KisMainWindow::slotFileQuit() { // Do not close while KisMainWindow has the savingEntryMutex locked, bug409395. // After the background saving job is initiated, KisDocument blocks closing // while it saves itself. if (hackIsSaving()) { return; } KisPart::instance()->closeSession(); } void KisMainWindow::slotFilePrint() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; applyDefaultSettings(printJob->printer()); QPrintDialog *printDialog = activeView()->createPrintDialog( printJob, this ); if (printDialog && printDialog->exec() == QDialog::Accepted) { printJob->printer().setPageMargins(0.0, 0.0, 0.0, 0.0, QPrinter::Point); printJob->printer().setPaperSize(QSizeF(activeView()->image()->width() / (72.0 * activeView()->image()->xRes()), activeView()->image()->height()/ (72.0 * activeView()->image()->yRes())), QPrinter::Inch); printJob->startPrinting(KisPrintJob::DeleteWhenDone); } else { delete printJob; } delete printDialog; } void KisMainWindow::slotFilePrintPreview() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; /* Sets the startPrinting() slot to be blocking. The Qt print-preview dialog requires the printing to be completely blocking and only return when the full document has been printed. By default the KisPrintingDialog is non-blocking and multithreading, setting blocking to true will allow it to be used in the preview dialog */ printJob->setProperty("blocking", true); QPrintPreviewDialog *preview = new QPrintPreviewDialog(&printJob->printer(), this); printJob->setParent(preview); // will take care of deleting the job connect(preview, SIGNAL(paintRequested(QPrinter*)), printJob, SLOT(startPrinting())); preview->exec(); delete preview; } KisPrintJob* KisMainWindow::exportToPdf(QString pdfFileName) { if (!activeView()) return 0; if (!activeView()->document()) return 0; KoPageLayout pageLayout; pageLayout.width = 0; pageLayout.height = 0; pageLayout.topMargin = 0; pageLayout.bottomMargin = 0; pageLayout.leftMargin = 0; pageLayout.rightMargin = 0; if (pdfFileName.isEmpty()) { KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString defaultDir = group.readEntry("SavePdfDialog"); if (defaultDir.isEmpty()) defaultDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); QUrl startUrl = QUrl::fromLocalFile(defaultDir); KisDocument* pDoc = d->activeView->document(); /** if document has a file name, take file name and replace extension with .pdf */ if (pDoc && pDoc->url().isValid()) { startUrl = pDoc->url(); QString fileName = startUrl.toLocalFile(); fileName = fileName.replace( QRegExp( "\\.\\w{2,5}$", Qt::CaseInsensitive ), ".pdf" ); startUrl = startUrl.adjusted(QUrl::RemoveFilename); startUrl.setPath(startUrl.path() + fileName ); } QPointer layoutDlg(new KoPageLayoutDialog(this, pageLayout)); layoutDlg->setWindowModality(Qt::WindowModal); if (layoutDlg->exec() != QDialog::Accepted || !layoutDlg) { delete layoutDlg; return 0; } pageLayout = layoutDlg->pageLayout(); delete layoutDlg; KoFileDialog dialog(this, KoFileDialog::SaveFile, "OpenDocument"); dialog.setCaption(i18n("Export as PDF")); dialog.setDefaultDir(startUrl.toLocalFile()); dialog.setMimeTypeFilters(QStringList() << "application/pdf"); QUrl url = QUrl::fromUserInput(dialog.filename()); pdfFileName = url.toLocalFile(); if (pdfFileName.isEmpty()) return 0; } KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return 0; if (isHidden()) { printJob->setProperty("noprogressdialog", true); } applyDefaultSettings(printJob->printer()); // TODO for remote files we have to first save locally and then upload. printJob->printer().setOutputFileName(pdfFileName); printJob->printer().setDocName(pdfFileName); printJob->printer().setColorMode(QPrinter::Color); if (pageLayout.format == KoPageFormat::CustomSize) { printJob->printer().setPaperSize(QSizeF(pageLayout.width, pageLayout.height), QPrinter::Millimeter); } else { printJob->printer().setPaperSize(KoPageFormat::printerPageSize(pageLayout.format)); } printJob->printer().setPageMargins(pageLayout.leftMargin, pageLayout.topMargin, pageLayout.rightMargin, pageLayout.bottomMargin, QPrinter::Millimeter); switch (pageLayout.orientation) { case KoPageFormat::Portrait: printJob->printer().setOrientation(QPrinter::Portrait); break; case KoPageFormat::Landscape: printJob->printer().setOrientation(QPrinter::Landscape); break; } //before printing check if the printer can handle printing if (!printJob->canPrint()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Cannot export to the specified file")); } printJob->startPrinting(KisPrintJob::DeleteWhenDone); return printJob; } void KisMainWindow::importAnimation() { if (!activeView()) return; KisDocument *document = activeView()->document(); if (!document) return; KisDlgImportImageSequence dlg(this, document); if (dlg.exec() == QDialog::Accepted) { QStringList files = dlg.files(); int firstFrame = dlg.firstFrame(); int step = dlg.step(); KoUpdaterPtr updater = !document->fileBatchMode() ? viewManager()->createUnthreadedUpdater(i18n("Import frames")) : 0; KisAnimationImporter importer(document->image(), updater); KisImportExportErrorCode status = importer.import(files, firstFrame, step); if (!status.isOk() && !status.isInternalError()) { QString msg = status.errorMessage(); if (!msg.isEmpty()) QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg)); } activeView()->canvasBase()->refetchDataFromImage(); } } void KisMainWindow::slotConfigureToolbars() { saveWindowState(); KEditToolBar edit(factory(), this); connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig())); (void) edit.exec(); applyToolBarLayout(); } void KisMainWindow::slotNewToolbarConfig() { applyMainWindowSettings(d->windowStateConfig); KXMLGUIFactory *factory = guiFactory(); Q_UNUSED(factory); // Check if there's an active view if (!d->activeView) return; plugActionList("toolbarlist", d->toolbarList); applyToolBarLayout(); } void KisMainWindow::slotToolbarToggled(bool toggle) { //dbgUI <<"KisMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true; // The action (sender) and the toolbar have the same name KToolBar * bar = toolBar(sender()->objectName()); if (bar) { if (toggle) { bar->show(); } else { bar->hide(); } if (d->activeView && d->activeView->document()) { saveWindowState(); } } else warnUI << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!"; } void KisMainWindow::viewFullscreen(bool fullScreen) { KisConfig cfg(false); cfg.setFullscreenMode(fullScreen); if (fullScreen) { setWindowState(windowState() | Qt::WindowFullScreen); // set } else { setWindowState(windowState() & ~Qt::WindowFullScreen); // reset } d->fullScreenMode->setChecked(isFullScreen()); } void KisMainWindow::setMaxRecentItems(uint _number) { d->recentFiles->setMaxItems(_number); } void KisMainWindow::slotReloadFile() { KisDocument* document = d->activeView->document(); if (!document || document->url().isEmpty()) return; if (document->isModified()) { bool ok = QMessageBox::question(this, i18nc("@title:window", "Krita"), i18n("You will lose all changes made since your last save\n" "Do you want to continue?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes; if (!ok) return; } QUrl url = document->url(); saveWindowSettings(); if (!document->reload()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Error: Could not reload this document")); } return; } QDockWidget* KisMainWindow::createDockWidget(KoDockFactoryBase* factory) { QDockWidget* dockWidget = 0; bool lockAllDockers = KisConfig(true).readEntry("LockAllDockerPanels", false); if (!d->dockWidgetsMap.contains(factory->id())) { dockWidget = factory->createDockWidget(); // It is quite possible that a dock factory cannot create the dock; don't // do anything in that case. if (!dockWidget) { warnKrita << "Could not create docker for" << factory->id(); return 0; } dockWidget->setFont(KoDockRegistry::dockFont()); dockWidget->setObjectName(factory->id()); dockWidget->setParent(this); if (lockAllDockers) { if (dockWidget->titleBarWidget()) { dockWidget->titleBarWidget()->setVisible(false); } dockWidget->setFeatures(QDockWidget::NoDockWidgetFeatures); } if (dockWidget->widget() && dockWidget->widget()->layout()) dockWidget->widget()->layout()->setContentsMargins(1, 1, 1, 1); Qt::DockWidgetArea side = Qt::RightDockWidgetArea; bool visible = true; switch (factory->defaultDockPosition()) { case KoDockFactoryBase::DockTornOff: dockWidget->setFloating(true); // position nicely? break; case KoDockFactoryBase::DockTop: side = Qt::TopDockWidgetArea; break; case KoDockFactoryBase::DockLeft: side = Qt::LeftDockWidgetArea; break; case KoDockFactoryBase::DockBottom: side = Qt::BottomDockWidgetArea; break; case KoDockFactoryBase::DockRight: side = Qt::RightDockWidgetArea; break; case KoDockFactoryBase::DockMinimized: default: side = Qt::RightDockWidgetArea; visible = false; } KConfigGroup group = d->windowStateConfig.group("DockWidget " + factory->id()); side = static_cast(group.readEntry("DockArea", static_cast(side))); if (side == Qt::NoDockWidgetArea) side = Qt::RightDockWidgetArea; addDockWidget(side, dockWidget); if (!visible) { dockWidget->hide(); } d->dockWidgetsMap.insert(factory->id(), dockWidget); } else { dockWidget = d->dockWidgetsMap[factory->id()]; } #ifdef Q_OS_MACOS dockWidget->setAttribute(Qt::WA_MacSmallSize, true); #endif dockWidget->setFont(KoDockRegistry::dockFont()); connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(forceDockTabFonts())); return dockWidget; } void KisMainWindow::forceDockTabFonts() { Q_FOREACH (QObject *child, children()) { if (child->inherits("QTabBar")) { ((QTabBar *)child)->setFont(KoDockRegistry::dockFont()); } } } QList KisMainWindow::dockWidgets() const { return d->dockWidgetsMap.values(); } QDockWidget* KisMainWindow::dockWidget(const QString &id) { if (!d->dockWidgetsMap.contains(id)) return 0; return d->dockWidgetsMap[id]; } QList KisMainWindow::canvasObservers() const { QList observers; Q_FOREACH (QDockWidget *docker, dockWidgets()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { observers << observer; } else { warnKrita << docker << "is not a canvas observer"; } } return observers; } void KisMainWindow::toggleDockersVisibility(bool visible) { if (!visible) { d->dockerStateBeforeHiding = saveState(); Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if (dw->isVisible()) { dw->hide(); } } } } else { restoreState(d->dockerStateBeforeHiding); } } void KisMainWindow::slotDocumentTitleModified() { updateCaption(); updateReloadFileAction(d->activeView ? d->activeView->document() : 0); } void KisMainWindow::subWindowActivated() { bool enabled = (activeKisView() != 0); d->mdiCascade->setEnabled(enabled); d->mdiNextWindow->setEnabled(enabled); d->mdiPreviousWindow->setEnabled(enabled); d->mdiTile->setEnabled(enabled); d->close->setEnabled(enabled); d->closeAll->setEnabled(enabled); setActiveSubWindow(d->mdiArea->activeSubWindow()); Q_FOREACH (QToolBar *tb, toolBars()) { if (tb->objectName() == "BrushesAndStuff") { tb->setEnabled(enabled); } } /** * Qt has a weirdness, it has hardcoded shortcuts added to an action * in the window menu. We need to reset the shortcuts for that menu * to nothing, otherwise the shortcuts cannot be made configurable. * * See: https://bugs.kde.org/show_bug.cgi?id=352205 * https://bugs.kde.org/show_bug.cgi?id=375524 * https://bugs.kde.org/show_bug.cgi?id=398729 */ QMdiSubWindow *subWindow = d->mdiArea->currentSubWindow(); if (subWindow) { QMenu *menu = subWindow->systemMenu(); if (menu && menu->actions().size() == 8) { Q_FOREACH (QAction *action, menu->actions()) { action->setShortcut(QKeySequence()); } menu->actions().last()->deleteLater(); } } updateCaption(); d->actionManager()->updateGUI(); } void KisMainWindow::windowFocused() { /** * Notify selection manager so that it could update selection mask overlay */ if (viewManager() && viewManager()->selectionManager()) { viewManager()->selectionManager()->selectionChanged(); } KisPart *kisPart = KisPart::instance(); KisWindowLayoutManager *layoutManager = KisWindowLayoutManager::instance(); if (!layoutManager->primaryWorkspaceFollowsFocus()) return; QUuid primary = layoutManager->primaryWindowId(); if (primary.isNull()) return; if (d->id == primary) { if (!d->workspaceBorrowedBy.isNull()) { KisMainWindow *borrower = kisPart->windowById(d->workspaceBorrowedBy); if (!borrower) return; swapWorkspaces(this, borrower); } } else { if (d->workspaceBorrowedBy == primary) return; KisMainWindow *primaryWindow = kisPart->windowById(primary); if (!primaryWindow) return; swapWorkspaces(this, primaryWindow); } } void KisMainWindow::updateWindowMenu() { QMenu *menu = d->windowMenu->menu(); menu->clear(); menu->addAction(d->newWindow); menu->addAction(d->documentMenu); QMenu *docMenu = d->documentMenu->menu(); docMenu->clear(); QFontMetrics fontMetrics = docMenu->fontMetrics(); - +#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) QScreen *screen = qApp->screenAt(this->geometry().topLeft()); + int fileStringWidth = int(QApplication::desktop()->screenGeometry(this).width() * .40f); +#else int fileStringWidth = int(screen->availableGeometry().width() * .40f); - +#endif Q_FOREACH (QPointer doc, KisPart::instance()->documents()) { if (doc) { QString title = fontMetrics.elidedText(doc->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth); if (title.isEmpty() && doc->image()) { title = doc->image()->objectName(); } QAction *action = docMenu->addAction(title); action->setIcon(qApp->windowIcon()); connect(action, SIGNAL(triggered()), d->documentMapper, SLOT(map())); d->documentMapper->setMapping(action, doc); } } menu->addAction(d->workspaceMenu); QMenu *workspaceMenu = d->workspaceMenu->menu(); workspaceMenu->clear(); KisResourceIterator resourceIterator(KisResourceModelProvider::resourceModel(ResourceType::Workspaces)); KisMainWindow *m_this = this; while (resourceIterator.hasNext()) { KisResourceItemSP resource = resourceIterator.next(); QAction *action = workspaceMenu->addAction(resource->name()); connect(action, &QAction::triggered, this, [=]() { m_this->restoreWorkspace(resource->id()); }); } workspaceMenu->addSeparator(); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&Import Workspace...")), &QAction::triggered, this, [&]() { QStringList mimeTypes = KisResourceLoaderRegistry::instance()->mimeTypes(ResourceType::Workspaces); KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(mimeTypes); dialog.setCaption(i18nc("@title:window", "Choose File to Add")); QString filename = dialog.filename(); d->workspacemodel->importResourceFile(filename); }); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&New Workspace...")), &QAction::triggered, [=]() { QString name; auto rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResourceSP workspace(new KisWorkspaceResource("")); workspace->setDockerState(m_this->saveState()); d->viewManager->canvasResourceProvider()->notifySavingWorkspace(workspace); workspace->setValid(true); QString saveLocation = rserver->saveLocation(); QFileInfo fileInfo(saveLocation + name + workspace->defaultFileExtension()); bool fileOverWriteAccepted = false; while(!fileOverWriteAccepted) { name = QInputDialog::getText(this, i18nc("@title:window", "New Workspace..."), i18nc("@label:textbox", "Name:")); if (name.isNull() || name.isEmpty()) { return; } else { fileInfo = QFileInfo(saveLocation + name.split(" ").join("_") + workspace->defaultFileExtension()); if (fileInfo.exists()) { int res = QMessageBox::warning(this, i18nc("@title:window", "Name Already Exists") , i18n("The name '%1' already exists, do you wish to overwrite it?", name) , QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (res == QMessageBox::Yes) fileOverWriteAccepted = true; } else { fileOverWriteAccepted = true; } } } workspace->setFilename(fileInfo.fileName()); workspace->setName(name); rserver->addResource(workspace); }); // TODO: What to do about delete? // workspaceMenu->addAction(i18nc("@action:inmenu", "&Delete Workspace...")); menu->addSeparator(); menu->addAction(d->close); menu->addAction(d->closeAll); if (d->mdiArea->viewMode() == QMdiArea::SubWindowView) { menu->addSeparator(); menu->addAction(d->mdiTile); menu->addAction(d->mdiCascade); } menu->addSeparator(); menu->addAction(d->mdiNextWindow); menu->addAction(d->mdiPreviousWindow); menu->addSeparator(); QList windows = d->mdiArea->subWindowList(); for (int i = 0; i < windows.size(); ++i) { QPointerchild = qobject_cast(windows.at(i)->widget()); if (child && child->document()) { QString text; if (i < 9) { text = i18n("&%1 %2", i + 1, fontMetrics.elidedText(child->document()->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth)); } else { text = i18n("%1 %2", i + 1, fontMetrics.elidedText(child->document()->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth)); } QAction *action = menu->addAction(text); action->setIcon(qApp->windowIcon()); action->setCheckable(true); action->setChecked(child == activeKisView()); connect(action, SIGNAL(triggered()), d->windowMapper, SLOT(map())); d->windowMapper->setMapping(action, windows.at(i)); } } bool showMdiArea = windows.count( ) > 0; if (!showMdiArea) { showWelcomeScreen(true); // see workaround in function in header // keep the recent file list updated when going back to welcome screen reloadRecentFileList(); d->welcomePage->populateRecentDocuments(); } // enable/disable the toolbox docker if there are no documents open Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if ( dw->objectName() == "ToolBox") { dw->setEnabled(showMdiArea); } } } updateCaption(); } void KisMainWindow::updateSubwindowFlags() { bool onlyOne = false; if (d->mdiArea->subWindowList().size() == 1 && d->mdiArea->viewMode() == QMdiArea::SubWindowView) { onlyOne = true; } Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { if (onlyOne) { subwin->setWindowFlags(subwin->windowFlags() | Qt::FramelessWindowHint); subwin->showMaximized(); } else { subwin->setWindowFlags((subwin->windowFlags() | Qt::FramelessWindowHint) ^ Qt::FramelessWindowHint); } } } void KisMainWindow::setActiveSubWindow(QWidget *window) { if (!window) return; QMdiSubWindow *subwin = qobject_cast(window); //dbgKrita << "setActiveSubWindow();" << subwin << d->activeSubWindow; if (subwin && subwin != d->activeSubWindow) { KisView *view = qobject_cast(subwin->widget()); //dbgKrita << "\t" << view << activeView(); if (view && view != activeView()) { d->mdiArea->setActiveSubWindow(subwin); setActiveView(view); } d->activeSubWindow = subwin; } updateWindowMenu(); d->actionManager()->updateGUI(); } void KisMainWindow::configChanged() { KisConfig cfg(true); QMdiArea::ViewMode viewMode = (QMdiArea::ViewMode)cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView); d->mdiArea->setViewMode(viewMode); Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); /** * Dirty workaround for a bug in Qt (checked on Qt 5.6.1): * * If you make a window "Show on top" and then switch to the tabbed mode * the window will continue to be painted in its initial "mid-screen" * position. It will persist here until you explicitly switch to its tab. */ if (viewMode == QMdiArea::TabbedView) { Qt::WindowFlags oldFlags = subwin->windowFlags(); Qt::WindowFlags flags = oldFlags; flags &= ~Qt::WindowStaysOnTopHint; flags &= ~Qt::WindowStaysOnBottomHint; if (flags != oldFlags) { subwin->setWindowFlags(flags); subwin->showMaximized(); } } } #ifdef Q_OS_MACOS updateSubwindowFlags(); #endif KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager->setCurrentTheme(group.readEntry("Theme", "Krita dark")); d->actionManager()->updateGUI(); QString s = cfg.getMDIBackgroundColor(); KoColor c = KoColor::fromXML(s); QBrush brush(c.toQColor()); d->mdiArea->setBackground(brush); QString backgroundImage = cfg.getMDIBackgroundImage(); if (backgroundImage != "") { QImage image(backgroundImage); QBrush brush(image); d->mdiArea->setBackground(brush); } d->mdiArea->update(); } KisView* KisMainWindow::newView(QObject *document, QMdiSubWindow *subWindow) { KisDocument *doc = qobject_cast(document); KisView *view = addViewAndNotifyLoadingCompleted(doc, subWindow); d->actionManager()->updateGUI(); return view; } void KisMainWindow::newWindow() { KisMainWindow *mainWindow = KisPart::instance()->createMainWindow(); mainWindow->initializeGeometry(); mainWindow->show(); } void KisMainWindow::closeCurrentWindow() { if (d->mdiArea->currentSubWindow()) { d->mdiArea->currentSubWindow()->close(); d->actionManager()->updateGUI(); } } void KisMainWindow::checkSanity() { // print error if the lcms engine is not available if (!KoColorSpaceEngineRegistry::instance()->contains("icc")) { // need to wait 1 event since exiting here would not work. m_errorMessage = i18n("The Krita LittleCMS color management plugin is not installed. Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); if (rserver->resourceCount() == 0) { m_errorMessage = i18n("Krita cannot find any brush presets! Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } } void KisMainWindow::showErrorAndDie() { QMessageBox::critical(0, i18nc("@title:window", "Installation error"), m_errorMessage); if (m_dieOnError) { exit(10); } } void KisMainWindow::showAboutApplication() { KisAboutApplication dlg(this); dlg.exec(); } QPointer KisMainWindow::activeKisView() { if (!d->mdiArea) return 0; QMdiSubWindow *activeSubWindow = d->mdiArea->activeSubWindow(); //dbgKrita << "activeKisView" << activeSubWindow; if (!activeSubWindow) return 0; return qobject_cast(activeSubWindow->widget()); } void KisMainWindow::newOptionWidgets(KoCanvasController *controller, const QList > &optionWidgetList) { KIS_ASSERT_RECOVER_NOOP(controller == KoToolManager::instance()->activeCanvasController()); bool isOurOwnView = false; Q_FOREACH (QPointer view, KisPart::instance()->views()) { if (view && view->canvasController() == controller) { isOurOwnView = view->mainWindow() == this; } } if (!isOurOwnView) return; Q_FOREACH (QWidget *w, optionWidgetList) { #ifdef Q_OS_MACOS w->setAttribute(Qt::WA_MacSmallSize, true); #endif w->setFont(KoDockRegistry::dockFont()); } if (d->toolOptionsDocker) { d->toolOptionsDocker->setOptionWidgets(optionWidgetList); } else { d->viewManager->paintOpBox()->newOptionWidgets(optionWidgetList); } } void KisMainWindow::applyDefaultSettings(QPrinter &printer) { if (!d->activeView) return; QString title = d->activeView->document()->documentInfo()->aboutInfo("title"); if (title.isEmpty()) { QFileInfo info(d->activeView->document()->url().fileName()); title = info.completeBaseName(); } if (title.isEmpty()) { // #139905 title = i18n("%1 unsaved document (%2)", qApp->applicationDisplayName(), QLocale().toString(QDate::currentDate(), QLocale::ShortFormat)); } printer.setDocName(title); } void KisMainWindow::createActions() { KisActionManager *actionManager = d->actionManager(); actionManager->createStandardAction(KStandardAction::New, this, SLOT(slotFileNew())); actionManager->createStandardAction(KStandardAction::Open, this, SLOT(slotFileOpen())); actionManager->createStandardAction(KStandardAction::Quit, this, SLOT(slotFileQuit())); actionManager->createStandardAction(KStandardAction::ConfigureToolbars, this, SLOT(slotConfigureToolbars())); d->fullScreenMode = actionManager->createStandardAction(KStandardAction::FullScreen, this, SLOT(viewFullscreen(bool))); d->recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection()); connect(d->recentFiles, SIGNAL(recentListCleared()), this, SLOT(saveRecentFiles())); KSharedConfigPtr configPtr = KSharedConfig::openConfig(); d->recentFiles->loadEntries(configPtr->group("RecentFiles")); d->saveAction = actionManager->createStandardAction(KStandardAction::Save, this, SLOT(slotFileSave())); d->saveAction->setActivationFlags(KisAction::ACTIVE_IMAGE); d->saveActionAs = actionManager->createStandardAction(KStandardAction::SaveAs, this, SLOT(slotFileSaveAs())); d->saveActionAs->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printAction = actionManager->createStandardAction(KStandardAction::Print, this, SLOT(slotFilePrint())); // d->printAction->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printActionPreview = actionManager->createStandardAction(KStandardAction::PrintPreview, this, SLOT(slotFilePrintPreview())); // d->printActionPreview->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undo = actionManager->createStandardAction(KStandardAction::Undo, this, SLOT(undo())); d->undo->setActivationFlags(KisAction::ACTIVE_IMAGE); d->redo = actionManager->createStandardAction(KStandardAction::Redo, this, SLOT(redo())); d->redo->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undoActionsUpdateManager.reset(new KisUndoActionsUpdateManager(d->undo, d->redo)); d->undoActionsUpdateManager->setCurrentDocument(d->activeView ? d->activeView->document() : 0); // d->exportPdf = actionManager->createAction("file_export_pdf"); // connect(d->exportPdf, SIGNAL(triggered()), this, SLOT(exportToPdf())); d->importAnimation = actionManager->createAction("file_import_animation"); connect(d->importAnimation, SIGNAL(triggered()), this, SLOT(importAnimation())); d->closeAll = actionManager->createAction("file_close_all"); connect(d->closeAll, SIGNAL(triggered()), this, SLOT(slotFileCloseAll())); // d->reloadFile = actionManager->createAction("file_reload_file"); // d->reloadFile->setActivationFlags(KisAction::CURRENT_IMAGE_MODIFIED); // connect(d->reloadFile, SIGNAL(triggered(bool)), this, SLOT(slotReloadFile())); d->importFile = actionManager->createAction("file_import_file"); connect(d->importFile, SIGNAL(triggered(bool)), this, SLOT(slotImportFile())); d->exportFile = actionManager->createAction("file_export_file"); connect(d->exportFile, SIGNAL(triggered(bool)), this, SLOT(slotExportFile())); /* The following entry opens the document information dialog. Since the action is named so it intends to show data this entry should not have a trailing ellipses (...). */ d->showDocumentInfo = actionManager->createAction("file_documentinfo"); connect(d->showDocumentInfo, SIGNAL(triggered(bool)), this, SLOT(slotDocumentInfo())); d->themeManager->setThemeMenuAction(new KActionMenu(i18nc("@action:inmenu", "&Themes"), this)); d->themeManager->registerThemeActions(actionCollection()); connect(d->themeManager, SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged())); connect(d->themeManager, SIGNAL(signalThemeChanged()), d->welcomePage, SLOT(slotUpdateThemeColors())); d->toggleDockers = actionManager->createAction("view_toggledockers"); KisConfig(true).showDockers(true); d->toggleDockers->setChecked(true); connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(bool))); d->toggleDetachCanvas = actionManager->createAction("view_detached_canvas"); d->toggleDetachCanvas->setChecked(false); connect(d->toggleDetachCanvas, SIGNAL(toggled(bool)), SLOT(setCanvasDetached(bool))); setCanvasDetached(false); actionCollection()->addAction("settings_dockers_menu", d->dockWidgetMenu); actionCollection()->addAction("window", d->windowMenu); d->mdiCascade = actionManager->createAction("windows_cascade"); connect(d->mdiCascade, SIGNAL(triggered()), d->mdiArea, SLOT(cascadeSubWindows())); d->mdiTile = actionManager->createAction("windows_tile"); connect(d->mdiTile, SIGNAL(triggered()), d->mdiArea, SLOT(tileSubWindows())); d->mdiNextWindow = actionManager->createAction("windows_next"); connect(d->mdiNextWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activateNextSubWindow())); d->mdiPreviousWindow = actionManager->createAction("windows_previous"); connect(d->mdiPreviousWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activatePreviousSubWindow())); d->newWindow = actionManager->createAction("view_newwindow"); connect(d->newWindow, SIGNAL(triggered(bool)), this, SLOT(newWindow())); d->close = actionManager->createStandardAction(KStandardAction::Close, this, SLOT(closeCurrentWindow())); d->showSessionManager = actionManager->createAction("file_sessions"); connect(d->showSessionManager, SIGNAL(triggered(bool)), this, SLOT(slotShowSessionManager())); actionManager->createStandardAction(KStandardAction::Preferences, this, SLOT(slotPreferences())); for (int i = 0; i < 2; i++) { d->expandingSpacers[i] = new KisAction(i18n("Expanding Spacer")); d->expandingSpacers[i]->setDefaultWidget(new QWidget(this)); d->expandingSpacers[i]->defaultWidget()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); actionManager->addAction(QString("expanding_spacer_%1").arg(i), d->expandingSpacers[i]); } } void KisMainWindow::applyToolBarLayout() { const bool isPlastiqueStyle = style()->objectName() == "plastique"; Q_FOREACH (KToolBar *toolBar, toolBars()) { toolBar->layout()->setSpacing(4); if (isPlastiqueStyle) { toolBar->setContentsMargins(0, 0, 0, 2); } //Hide text for buttons with an icon in the toolbar Q_FOREACH (QAction *ac, toolBar->actions()){ if (ac->icon().pixmap(QSize(1,1)).isNull() == false){ ac->setPriority(QAction::LowPriority); }else { ac->setIcon(QIcon()); } } } } void KisMainWindow::initializeGeometry() { // if the user didn's specify the geometry on the command line (does anyone do that still?), // we first figure out some good default size and restore the x,y position. See bug 285804Z. KConfigGroup cfg = d->windowStateConfig; QByteArray geom = QByteArray::fromBase64(cfg.readEntry("ko_geometry", QByteArray())); if (!restoreGeometry(geom)) { const int scnum = QApplication::desktop()->screenNumber(parentWidget()); QRect desk = QGuiApplication::screens().at(scnum)->availableVirtualGeometry(); quint32 x = desk.x(); quint32 y = desk.y(); quint32 w = 0; quint32 h = 0; // Default size -- maximize on small screens, something useful on big screens const int deskWidth = desk.width(); if (deskWidth > 1024) { // a nice width, and slightly less than total available // height to compensate for the window decs w = (deskWidth / 3) * 2; h = (desk.height() / 3) * 2; } else { w = desk.width(); h = desk.height(); } x += (desk.width() - w) / 2; y += (desk.height() - h) / 2; move(x,y); setGeometry(geometry().x(), geometry().y(), w, h); } d->fullScreenMode->setChecked(isFullScreen()); } void KisMainWindow::showManual() { QDesktopServices::openUrl(QUrl("https://docs.krita.org")); } void KisMainWindow::windowScreenChanged(QScreen *screen) { emit screenChanged(); d->screenConnectionsStore.clear(); d->screenConnectionsStore.addConnection(screen, SIGNAL(physicalDotsPerInchChanged(qreal)), this, SIGNAL(screenChanged())); } void KisMainWindow::slotXmlGuiMakingChanges(bool finished) { if (finished) { subWindowActivated(); } } #include diff --git a/libs/ui/KisPart.cpp b/libs/ui/KisPart.cpp index fc26a5a8a3..418aeedcae 100644 --- a/libs/ui/KisPart.cpp +++ b/libs/ui/KisPart.cpp @@ -1,568 +1,579 @@ /* This file is part of the KDE project * Copyright (C) 1998-1999 Torben Weis * Copyright (C) 2000-2005 David Faure * Copyright (C) 2007-2008 Thorsten Zachmann * Copyright (C) 2010-2012 Boudewijn Rempt * Copyright (C) 2011 Inge Wallin * Copyright (C) 2015 Michael Abrahams * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisPart.h" #include "KoProgressProxy.h" #include #include #include #include #include #include #include #include #include "KisApplication.h" #include "KisMainWindow.h" #include "KisDocument.h" #include "KisView.h" #include "KisViewManager.h" #include "KisImportExportManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "kis_shape_controller.h" #include "KisResourceServerProvider.h" #include "kis_animation_cache_populator.h" #include "kis_idle_watcher.h" #include "kis_image.h" #include "KisOpenPane.h" #include "kis_color_manager.h" #include "kis_action.h" #include "kis_action_registry.h" #include "KisSessionResource.h" +#include "KisBusyWaitBroker.h" +#include "dialogs/kis_delayed_save_dialog.h" Q_GLOBAL_STATIC(KisPart, s_instance) class Q_DECL_HIDDEN KisPart::Private { public: Private(KisPart *_part) : part(_part) , idleWatcher(2500) , animationCachePopulator(_part) { } ~Private() { } KisPart *part; QList > views; QList > mainWindows; QList > documents; KActionCollection *actionCollection{0}; KisIdleWatcher idleWatcher; KisAnimationCachePopulator animationCachePopulator; KisSessionResourceSP currentSession; bool closingSession{false}; QScopedPointer sessionManager; bool queryCloseDocument(KisDocument *document) { Q_FOREACH(auto view, views) { if (view && view->isVisible() && view->document() == document) { return view->queryClose(); } } return true; } }; KisPart* KisPart::instance() { return s_instance; } +namespace { +void busyWaitWithFeedback(KisImageSP image) +{ + const int busyWaitDelay = 1000; + KisDelayedSaveDialog dialog(image, KisDelayedSaveDialog::ForcedDialog, busyWaitDelay, KisPart::instance()->currentMainwindow()); + dialog.blockIfImageIsBusy(); +} +} KisPart::KisPart() : d(new Private(this)) { // Preload all the resources in the background Q_UNUSED(KoResourceServerProvider::instance()); Q_UNUSED(KisResourceServerProvider::instance()); Q_UNUSED(KisColorManager::instance()); connect(this, SIGNAL(documentOpened(QString)), this, SLOT(updateIdleWatcherConnections())); connect(this, SIGNAL(documentClosed(QString)), this, SLOT(updateIdleWatcherConnections())); connect(KisActionRegistry::instance(), SIGNAL(shortcutsUpdated()), this, SLOT(updateShortcuts())); connect(&d->idleWatcher, SIGNAL(startedIdleMode()), &d->animationCachePopulator, SLOT(slotRequestRegeneration())); d->animationCachePopulator.slotRequestRegeneration(); + KisBusyWaitBroker::instance()->setFeedbackCallback(&busyWaitWithFeedback); } KisPart::~KisPart() { while (!d->documents.isEmpty()) { delete d->documents.takeFirst(); } while (!d->views.isEmpty()) { delete d->views.takeFirst(); } while (!d->mainWindows.isEmpty()) { delete d->mainWindows.takeFirst(); } delete d; } void KisPart::updateIdleWatcherConnections() { QVector images; Q_FOREACH (QPointer document, documents()) { if (document->image()) { images << document->image(); } } d->idleWatcher.setTrackedImages(images); } void KisPart::addDocument(KisDocument *document) { //dbgUI << "Adding document to part list" << document; Q_ASSERT(document); if (!d->documents.contains(document)) { d->documents.append(document); emit documentOpened('/'+objectName()); emit sigDocumentAdded(document); connect(document, SIGNAL(sigSavingFinished()), SLOT(slotDocumentSaved())); } } QList > KisPart::documents() const { return d->documents; } KisDocument *KisPart::createDocument() const { KisDocument *doc = new KisDocument(); return doc; } int KisPart::documentCount() const { return d->documents.size(); } void KisPart::removeDocument(KisDocument *document) { d->documents.removeAll(document); emit documentClosed('/'+objectName()); emit sigDocumentRemoved(document->url().toLocalFile()); document->deleteLater(); } KisMainWindow *KisPart::createMainWindow(QUuid id) { KisMainWindow *mw = new KisMainWindow(id); dbgUI <<"mainWindow" << (void*)mw << "added to view" << this; d->mainWindows.append(mw); emit sigWindowAdded(mw); return mw; } KisView *KisPart::createView(KisDocument *document, KisViewManager *viewManager, QWidget *parent) { // If creating the canvas fails, record this and disable OpenGL next time KisConfig cfg(false); KConfigGroup grp( KSharedConfig::openConfig(), "crashprevention"); if (grp.readEntry("CreatingCanvas", false)) { cfg.disableOpenGL(); } if (cfg.canvasState() == "OPENGL_FAILED") { cfg.disableOpenGL(); } grp.writeEntry("CreatingCanvas", true); grp.sync(); QApplication::setOverrideCursor(Qt::WaitCursor); KisView *view = new KisView(document, viewManager, parent); QApplication::restoreOverrideCursor(); // Record successful canvas creation grp.writeEntry("CreatingCanvas", false); grp.sync(); addView(view); return view; } void KisPart::addView(KisView *view) { if (!view) return; if (!d->views.contains(view)) { d->views.append(view); } emit sigViewAdded(view); } void KisPart::removeView(KisView *view) { if (!view) return; /** * HACK ALERT: we check here explicitly if the document (or main * window), is saving the stuff. If we close the * document *before* the saving is completed, a crash * will happen. */ KIS_ASSERT_RECOVER_RETURN(!view->mainWindow()->hackIsSaving()); emit sigViewRemoved(view); QPointer doc = view->document(); d->views.removeAll(view); if (doc) { bool found = false; Q_FOREACH (QPointer view, d->views) { if (view && view->document() == doc) { found = true; break; } } if (!found) { removeDocument(doc); } } } QList > KisPart::views() const { return d->views; } int KisPart::viewCount(KisDocument *doc) const { if (!doc) { return d->views.count(); } else { int count = 0; Q_FOREACH (QPointer view, d->views) { if (view && view->isVisible() && view->document() == doc) { count++; } } return count; } } bool KisPart::closingSession() const { return d->closingSession; } bool KisPart::exists() { return s_instance.exists(); } bool KisPart::closeSession(bool keepWindows) { d->closingSession = true; Q_FOREACH(auto document, d->documents) { if (!d->queryCloseDocument(document.data())) { d->closingSession = false; return false; } } if (d->currentSession) { KisConfig kisCfg(false); if (kisCfg.saveSessionOnQuit(false)) { d->currentSession->storeCurrentWindows(); d->currentSession->save(); KConfigGroup cfg = KSharedConfig::openConfig()->group("session"); cfg.writeEntry("previousSession", d->currentSession->name()); } d->currentSession = nullptr; } if (!keepWindows) { Q_FOREACH (auto window, d->mainWindows) { window->close(); } if (d->sessionManager) { d->sessionManager->close(); } } d->closingSession = false; return true; } void KisPart::slotDocumentSaved() { KisDocument *doc = qobject_cast(sender()); emit sigDocumentSaved(doc->url().toLocalFile()); } void KisPart::removeMainWindow(KisMainWindow *mainWindow) { dbgUI <<"mainWindow" << (void*)mainWindow <<"removed from doc" << this; if (mainWindow) { d->mainWindows.removeAll(mainWindow); } } const QList > &KisPart::mainWindows() const { return d->mainWindows; } int KisPart::mainwindowCount() const { return d->mainWindows.count(); } KisMainWindow *KisPart::currentMainwindow() const { QWidget *widget = qApp->activeWindow(); KisMainWindow *mainWindow = qobject_cast(widget); while (!mainWindow && widget) { widget = widget->parentWidget(); mainWindow = qobject_cast(widget); } if (!mainWindow && mainWindows().size() > 0) { mainWindow = mainWindows().first(); } return mainWindow; } KisMainWindow * KisPart::windowById(QUuid id) const { Q_FOREACH(QPointer mainWindow, d->mainWindows) { if (mainWindow->id() == id) { return mainWindow; } } return nullptr; } KisIdleWatcher* KisPart::idleWatcher() const { return &d->idleWatcher; } KisAnimationCachePopulator* KisPart::cachePopulator() const { return &d->animationCachePopulator; } void KisPart::openExistingFile(const QUrl &url) { // TODO: refactor out this method! KisMainWindow *mw = currentMainwindow(); KIS_SAFE_ASSERT_RECOVER_RETURN(mw); mw->openDocument(url, KisMainWindow::None); } void KisPart::updateShortcuts() { // Update any non-UI actionCollections. That includes: // Now update the UI actions. Q_FOREACH (KisMainWindow *mainWindow, d->mainWindows) { KActionCollection *ac = mainWindow->actionCollection(); ac->updateShortcuts(); // Loop through mainWindow->actionCollections() to modify tooltips // so that they list shortcuts at the end in parentheses Q_FOREACH ( QAction* action, ac->actions()) { // Remove any existing suffixes from the tooltips. // Note this regexp starts with a space, e.g. " (Ctrl-a)" QString strippedTooltip = action->toolTip().remove(QRegExp("\\s\\(.*\\)")); // Now update the tooltips with the new shortcut info. if (action->shortcut() == QKeySequence(0)) action->setToolTip(strippedTooltip); else action->setToolTip( strippedTooltip + " (" + action->shortcut().toString() + ")"); } } } void KisPart::openTemplate(const QUrl &url) { qApp->setOverrideCursor(Qt::BusyCursor); KisDocument *document = createDocument(); bool ok = document->loadNativeFormat(url.toLocalFile()); document->setModified(false); document->undoStack()->clear(); if (ok) { QString mimeType = KisMimeDatabase::mimeTypeForFile(url.toLocalFile()); // in case this is a open document template remove the -template from the end mimeType.remove( QRegExp( "-template$" ) ); document->setMimeTypeAfterLoading(mimeType); document->resetURL(); document->setReadWrite(true); } else { if (document->errorMessage().isEmpty()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not create document from template\n%1", document->localFilePath())); } else { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not create document from template\n%1\nReason: %2", document->localFilePath(), document->errorMessage())); } delete document; return; } addDocument(document); KisMainWindow *mw = currentMainwindow(); mw->addViewAndNotifyLoadingCompleted(document); KisOpenPane *pane = qobject_cast(sender()); if (pane) { pane->hide(); pane->deleteLater(); } qApp->restoreOverrideCursor(); } void KisPart::addRecentURLToAllMainWindows(QUrl url) { // Add to recent actions list in our mainWindows Q_FOREACH (KisMainWindow *mainWindow, d->mainWindows) { mainWindow->addRecentURL(url); } } void KisPart::startCustomDocument(KisDocument* doc) { addDocument(doc); KisMainWindow *mw = currentMainwindow(); KisOpenPane *pane = qobject_cast(sender()); if (pane) { pane->hide(); pane->deleteLater(); } mw->addViewAndNotifyLoadingCompleted(doc); } KisInputManager* KisPart::currentInputManager() { KisMainWindow *mw = currentMainwindow(); KisViewManager *manager = mw ? mw->viewManager() : 0; return manager ? manager->inputManager() : 0; } void KisPart::showSessionManager() { if (d->sessionManager.isNull()) { d->sessionManager.reset(new KisSessionManagerDialog()); } d->sessionManager->show(); d->sessionManager->activateWindow(); } void KisPart::startBlankSession() { KisMainWindow *window = createMainWindow(); window->initializeGeometry(); window->show(); } bool KisPart::restoreSession(const QString &sessionName) { if (sessionName.isNull()) return false; KoResourceServer *rserver = KisResourceServerProvider::instance()->sessionServer(); KisSessionResourceSP session = rserver->resourceByName(sessionName); if (!session || !session->valid()) return false; session->restore(); return true; } void KisPart::setCurrentSession(KisSessionResourceSP session) { d->currentSession = session; } diff --git a/libs/ui/flake/kis_shape_layer.cc b/libs/ui/flake/kis_shape_layer.cc index be978c1a18..96eecb96cd 100644 --- a/libs/ui/flake/kis_shape_layer.cc +++ b/libs/ui/flake/kis_shape_layer.cc @@ -1,762 +1,768 @@ /* * Copyright (c) 2006-2008 Boudewijn Rempt * Copyright (c) 2007 Thomas Zander * Copyright (c) 2009 Cyrille Berger * Copyright (c) 2011 Jan Hambrecht * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_shape_layer.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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "SvgWriter.h" #include "SvgParser.h" #include #include #include "kis_default_bounds.h" #include #include "kis_shape_layer_canvas.h" #include "kis_image_view_converter.h" #include #include "kis_node_visitor.h" #include "kis_processing_visitor.h" #include "kis_effect_mask.h" #include "commands/KoShapeReorderCommand.h" #include "kis_do_something_command.h" +#include +#include +#include #include class ShapeLayerContainerModel : public SimpleShapeContainerModel { public: ShapeLayerContainerModel(KisShapeLayer *parent) : q(parent) {} void add(KoShape *child) override { SimpleShapeContainerModel::add(child); /** * The shape is always added with the absolute transformation set appropriately. * Here we should just squeeze it into the layer's transformation. */ KIS_SAFE_ASSERT_RECOVER_NOOP(inheritsTransform(child)); if (inheritsTransform(child)) { QTransform parentTransform = q->absoluteTransformation(); child->applyAbsoluteTransformation(parentTransform.inverted()); } } void remove(KoShape *child) override { KIS_SAFE_ASSERT_RECOVER_NOOP(inheritsTransform(child)); if (inheritsTransform(child)) { QTransform parentTransform = q->absoluteTransformation(); child->applyAbsoluteTransformation(parentTransform); } SimpleShapeContainerModel::remove(child); } void shapeHasBeenAddedToHierarchy(KoShape *shape, KoShapeContainer *addedToSubtree) override { q->shapeManager()->addShape(shape); SimpleShapeContainerModel::shapeHasBeenAddedToHierarchy(shape, addedToSubtree); } void shapeToBeRemovedFromHierarchy(KoShape *shape, KoShapeContainer *removedFromSubtree) override { q->shapeManager()->remove(shape); SimpleShapeContainerModel::shapeToBeRemovedFromHierarchy(shape, removedFromSubtree); } private: KisShapeLayer *q; }; struct KisShapeLayer::Private { public: Private() : canvas(0) , controller(0) , x(0) , y(0) {} KisPaintDeviceSP paintDevice; KisShapeLayerCanvasBase * canvas; KoShapeControllerBase* controller; int x; int y; }; KisShapeLayer::KisShapeLayer(KoShapeControllerBase* controller, KisImageWSP image, const QString &name, quint8 opacity) : KisExternalLayer(image, name, opacity), KoShapeLayer(new ShapeLayerContainerModel(this)), m_d(new Private()) { initShapeLayer(controller); } KisShapeLayer::KisShapeLayer(const KisShapeLayer& rhs) : KisShapeLayer(rhs, rhs.m_d->controller) { } KisShapeLayer::KisShapeLayer(const KisShapeLayer& _rhs, KoShapeControllerBase* controller, KisShapeLayerCanvasBase *canvas) : KisExternalLayer(_rhs) , KoShapeLayer(new ShapeLayerContainerModel(this)) //no _rhs here otherwise both layer have the same KoShapeContainerModel , m_d(new Private()) { // copy the projection to avoid extra round of updates! initShapeLayer(controller, _rhs.m_d->paintDevice, canvas); /** * The transformaitons of the added shapes are automatically merged into the transformation * of the layer, so we should apply this extra transform separately */ const QTransform thisInvertedTransform = this->absoluteTransformation().inverted(); m_d->canvas->shapeManager()->setUpdatesBlocked(true); Q_FOREACH (KoShape *shape, _rhs.shapes()) { KoShape *clonedShape = shape->cloneShape(); KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; } clonedShape->setTransformation(shape->absoluteTransformation() * thisInvertedTransform); addShape(clonedShape); } m_d->canvas->shapeManager()->setUpdatesBlocked(false); } KisShapeLayer::KisShapeLayer(const KisShapeLayer& _rhs, const KisShapeLayer &_addShapes) : KisExternalLayer(_rhs) , KoShapeLayer(new ShapeLayerContainerModel(this)) //no _merge here otherwise both layer have the same KoShapeContainerModel , m_d(new Private()) { // Make sure our new layer is visible otherwise the shapes cannot be painted. setVisible(true); initShapeLayer(_rhs.m_d->controller); /** * With current implementation this matrix will always be an identity, because * we do not copy the transformation from any of the source layers. But we should * handle this anyway, to not be caught by this in the future. */ const QTransform thisInvertedTransform = this->absoluteTransformation().inverted(); QList shapesAbove; QList shapesBelow; // copy in _rhs's shapes Q_FOREACH (KoShape *shape, _rhs.shapes()) { KoShape *clonedShape = shape->cloneShape(); KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; } clonedShape->setTransformation(shape->absoluteTransformation() * thisInvertedTransform); shapesBelow.append(clonedShape); } // copy in _addShapes's shapes Q_FOREACH (KoShape *shape, _addShapes.shapes()) { KoShape *clonedShape = shape->cloneShape(); KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; } clonedShape->setTransformation(shape->absoluteTransformation() * thisInvertedTransform); shapesAbove.append(clonedShape); } QList shapes = KoShapeReorderCommand::mergeDownShapes(shapesBelow, shapesAbove); KoShapeReorderCommand cmd(shapes); cmd.redo(); Q_FOREACH (KoShape *shape, shapesBelow + shapesAbove) { addShape(shape); } } KisShapeLayer::KisShapeLayer(KoShapeControllerBase* controller, KisImageWSP image, const QString &name, quint8 opacity, KisShapeLayerCanvasBase *canvas) : KisExternalLayer(image, name, opacity) , KoShapeLayer(new ShapeLayerContainerModel(this)) , m_d(new Private()) { initShapeLayer(controller, nullptr, canvas); } KisShapeLayer::~KisShapeLayer() { /** * Small hack alert: we should avoid updates on shape deletion */ m_d->canvas->prepareForDestroying(); Q_FOREACH (KoShape *shape, shapes()) { shape->setParent(0); delete shape; } delete m_d->canvas; delete m_d; } void KisShapeLayer::initShapeLayer(KoShapeControllerBase* controller, KisPaintDeviceSP copyFromProjection, KisShapeLayerCanvasBase *canvas) { setSupportsLodMoves(false); setShapeId(KIS_SHAPE_LAYER_ID); KIS_ASSERT_RECOVER_NOOP(this->image()); if (!copyFromProjection) { m_d->paintDevice = new KisPaintDevice(image()->colorSpace()); m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(this->image())); m_d->paintDevice->setParentNode(this); } else { m_d->paintDevice = new KisPaintDevice(*copyFromProjection); } if (!canvas) { auto *slCanvas = new KisShapeLayerCanvas(this, image()); slCanvas->setProjection(m_d->paintDevice); canvas = slCanvas; } m_d->canvas = canvas; m_d->canvas->moveToThread(this->thread()); m_d->controller = controller; m_d->canvas->shapeManager()->selection()->disconnect(this); connect(m_d->canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged())); connect(m_d->canvas->selectedShapesProxy(), SIGNAL(currentLayerChanged(const KoShapeLayer*)), this, SIGNAL(currentLayerChanged(const KoShapeLayer*))); connect(this, SIGNAL(sigMoveShapes(QPointF)), SLOT(slotMoveShapes(QPointF))); - connect(this, SIGNAL(sigTransformShapes(QTransform)), SLOT(slotTransformShapes(QTransform)), Qt::BlockingQueuedConnection); - } bool KisShapeLayer::allowAsChild(KisNodeSP node) const { return node->inherits("KisMask"); } void KisShapeLayer::setImage(KisImageWSP _image) { KisLayer::setImage(_image); m_d->canvas->setImage(_image); m_d->paintDevice->convertTo(_image->colorSpace()); m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(_image)); } KisLayerSP KisShapeLayer::createMergedLayerTemplate(KisLayerSP prevLayer) { KisShapeLayer *prevShape = dynamic_cast(prevLayer.data()); if (prevShape) return new KisShapeLayer(*prevShape, *this); else return KisExternalLayer::createMergedLayerTemplate(prevLayer); } void KisShapeLayer::fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer) { if (!dynamic_cast(dstLayer.data())) { KisLayer::fillMergedLayerTemplate(dstLayer, prevLayer); } } void KisShapeLayer::setParent(KoShapeContainer *parent) { Q_UNUSED(parent) KIS_ASSERT_RECOVER_RETURN(0); } QIcon KisShapeLayer::icon() const { return KisIconUtils::loadIcon("vectorLayer"); } KisPaintDeviceSP KisShapeLayer::original() const { return m_d->paintDevice; } KisPaintDeviceSP KisShapeLayer::paintDevice() const { return 0; } qint32 KisShapeLayer::x() const { return m_d->x; } qint32 KisShapeLayer::y() const { return m_d->y; } void KisShapeLayer::setX(qint32 x) { qint32 delta = x - this->x(); QPointF diff = QPointF(m_d->canvas->viewConverter()->viewToDocumentX(delta), 0); emit sigMoveShapes(diff); // Save new value to satisfy LSP m_d->x = x; } void KisShapeLayer::setY(qint32 y) { qint32 delta = y - this->y(); QPointF diff = QPointF(0, m_d->canvas->viewConverter()->viewToDocumentY(delta)); emit sigMoveShapes(diff); // Save new value to satisfy LSP m_d->y = y; } namespace { void filterTransformableShapes(QList &shapes) { auto it = shapes.begin(); while (it != shapes.end()) { if (shapes.size() == 1) break; if ((*it)->inheritsTransformFromAny(shapes)) { it = shapes.erase(it); } else { ++it; } } } } QList KisShapeLayer::shapesToBeTransformed() { QList shapes = shapeManager()->shapes(); // We expect that **all** the shapes inherit the transform from its parent // SANITY_CHECK: we expect all the shapes inside the // shape layer to inherit transform! Q_FOREACH (KoShape *shape, shapes) { if (shape->parent()) { KIS_SAFE_ASSERT_RECOVER(shape->parent()->inheritsTransform(shape)) { break; } } } shapes << this; filterTransformableShapes(shapes); return shapes; } void KisShapeLayer::slotMoveShapes(const QPointF &diff) { QList shapes = shapesToBeTransformed(); if (shapes.isEmpty()) return; KoShapeMoveCommand cmd(shapes, diff); cmd.redo(); } void KisShapeLayer::slotTransformShapes(const QTransform &newTransform) { KoShapeTransformCommand cmd({this}, {transformation()}, {newTransform}); cmd.redo(); } bool KisShapeLayer::accept(KisNodeVisitor& visitor) { return visitor.visit(this); } void KisShapeLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { return visitor.visit(this, undoAdapter); } KoShapeManager* KisShapeLayer::shapeManager() const { return m_d->canvas->shapeManager(); } KoViewConverter* KisShapeLayer::converter() const { return m_d->canvas->viewConverter(); } bool KisShapeLayer::visible(bool recursive) const { return KisExternalLayer::visible(recursive); } void KisShapeLayer::setVisible(bool visible, bool isLoading) { const bool oldVisible = this->visible(false); KoShapeLayer::setVisible(visible); KisExternalLayer::setVisible(visible, isLoading); if (visible && !oldVisible && m_d->canvas->hasChangedWhileBeingInvisible()) { m_d->canvas->rerenderAfterBeingInvisible(); } } void KisShapeLayer::setUserLocked(bool value) { KoShapeLayer::setGeometryProtected(value); KisExternalLayer::setUserLocked(value); } bool KisShapeLayer::isShapeEditable(bool recursive) const { return KoShapeLayer::isShapeEditable(recursive) && isEditable(true); } // we do not override KoShape::setGeometryProtected() as we consider // the user not being able to access the layer shape from Krita UI! void KisShapeLayer::forceUpdateTimedNode() { m_d->canvas->forceRepaint(); } bool KisShapeLayer::hasPendingTimedUpdates() const { return m_d->canvas->hasPendingUpdates(); } void KisShapeLayer::forceUpdateHiddenAreaOnOriginal() { m_d->canvas->forceRepaintWithHiddenAreas(); } bool KisShapeLayer::saveShapesToStore(KoStore *store, QList shapes, const QSizeF &sizeInPt) { if (!store->open("content.svg")) { return false; } KoStoreDevice storeDev(store); storeDev.open(QIODevice::WriteOnly); std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); SvgWriter writer(shapes); writer.save(storeDev, sizeInPt); if (!store->close()) { return false; } return true; } QList KisShapeLayer::createShapesFromSvg(QIODevice *device, const QString &baseXmlDir, const QRectF &rectInPixels, qreal resolutionPPI, KoDocumentResourceManager *resourceManager, QSizeF *fragmentSize) { QString errorMsg; int errorLine = 0; int errorColumn; KoXmlDocument doc = SvgParser::createDocumentFromSvg(device, &errorMsg, &errorLine, &errorColumn); if (doc.isNull()) { errKrita << "Parsing error in " << "contents.svg" << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; errKrita << i18n("Parsing error in the main document at line %1, column %2\nError message: %3" , errorLine , errorColumn , errorMsg); } SvgParser parser(resourceManager); parser.setXmlBaseDir(baseXmlDir); parser.setResolution(rectInPixels /* px */, resolutionPPI /* ppi */); return parser.parseSvg(doc.documentElement(), fragmentSize); } bool KisShapeLayer::saveLayer(KoStore * store) const { // FIXME: we handle xRes() only! const QSizeF sizeInPx = image()->bounds().size(); const QSizeF sizeInPt(sizeInPx.width() / image()->xRes(), sizeInPx.height() / image()->yRes()); return saveShapesToStore(store, this->shapes(), sizeInPt); } bool KisShapeLayer::loadSvg(QIODevice *device, const QString &baseXmlDir) { QSizeF fragmentSize; // unused! KisImageSP image = this->image(); // FIXME: we handle xRes() only! KIS_SAFE_ASSERT_RECOVER_NOOP(qFuzzyCompare(image->xRes(), image->yRes())); const qreal resolutionPPI = 72.0 * image->xRes(); QList shapes = createShapesFromSvg(device, baseXmlDir, image->bounds(), resolutionPPI, m_d->controller->resourceManager(), &fragmentSize); Q_FOREACH (KoShape *shape, shapes) { addShape(shape); } return true; } bool KisShapeLayer::loadLayer(KoStore* store) { if (!store) { warnKrita << i18n("No store backend"); return false; } if (store->open("content.svg")) { KoStoreDevice storeDev(store); storeDev.open(QIODevice::ReadOnly); loadSvg(&storeDev, ""); store->close(); return true; } KoOdfReadStore odfStore(store); QString errorMessage; odfStore.loadAndParse(errorMessage); if (!errorMessage.isEmpty()) { warnKrita << errorMessage; return false; } KoXmlElement contents = odfStore.contentDoc().documentElement(); // dbgKrita <<"Start loading OASIS document..." << contents.text(); // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().localName(); // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().namespaceURI(); // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().isElement(); KoXmlElement body(KoXml::namedItemNS(contents, KoXmlNS::office, "body")); if (body.isNull()) { //setErrorMessage( i18n( "Invalid OASIS document. No office:body tag found." ) ); return false; } body = KoXml::namedItemNS(body, KoXmlNS::office, "drawing"); if (body.isNull()) { //setErrorMessage( i18n( "Invalid OASIS document. No office:drawing tag found." ) ); return false; } KoXmlElement page(KoXml::namedItemNS(body, KoXmlNS::draw, "page")); if (page.isNull()) { //setErrorMessage( i18n( "Invalid OASIS document. No draw:page tag found." ) ); return false; } KoXmlElement * master = 0; if (odfStore.styles().masterPages().contains("Standard")) master = odfStore.styles().masterPages().value("Standard"); else if (odfStore.styles().masterPages().contains("Default")) master = odfStore.styles().masterPages().value("Default"); else if (! odfStore.styles().masterPages().empty()) master = odfStore.styles().masterPages().begin().value(); if (master) { const KoXmlElement *style = odfStore.styles().findStyle( master->attributeNS(KoXmlNS::style, "page-layout-name", QString())); KoPageLayout pageLayout; pageLayout.loadOdf(*style); setSize(QSizeF(pageLayout.width, pageLayout.height)); } // We work fine without a master page KoOdfLoadingContext context(odfStore.styles(), odfStore.store()); context.setManifestFile(QString("tar:/") + odfStore.store()->currentPath() + "META-INF/manifest.xml"); KoShapeLoadingContext shapeContext(context, m_d->controller->resourceManager()); KoXmlElement layerElement; forEachElement(layerElement, context.stylesReader().layerSet()) { // FIXME: investigate what is this // KoShapeLayer * l = new KoShapeLayer(); if (!loadOdf(layerElement, shapeContext)) { dbgKrita << "Could not load vector layer!"; return false; } } KoXmlElement child; forEachElement(child, page) { KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf(child, shapeContext); if (shape) { addShape(shape); } } return true; } void KisShapeLayer::resetCache() { m_d->canvas->resetCache(); } KUndo2Command* KisShapeLayer::crop(const QRect & rect) { QPoint oldPos(x(), y()); QPoint newPos = oldPos - rect.topLeft(); return new KisNodeMoveCommand2(this, oldPos, newPos); } -struct TransformShapeLayerDeferred : public KUndo2Command +class TransformShapeLayerDeferred : public KUndo2Command { +public: TransformShapeLayerDeferred(KisShapeLayer *shapeLayer, const QTransform &globalDocTransform) : m_shapeLayer(shapeLayer), - m_globalDocTransform(globalDocTransform) + m_globalDocTransform(globalDocTransform), + m_blockingConnection(std::bind(&KisShapeLayer::slotTransformShapes, shapeLayer, std::placeholders::_1)) { } void undo() { - emit m_shapeLayer->sigTransformShapes(m_savedTransform); + KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() != qApp->thread()); + m_blockingConnection.start(m_savedTransform); } void redo() { m_savedTransform = m_shapeLayer->transformation(); const QTransform globalTransform = m_shapeLayer->absoluteTransformation(); const QTransform localTransform = globalTransform * m_globalDocTransform * globalTransform.inverted(); - emit m_shapeLayer->sigTransformShapes(localTransform * m_savedTransform); + KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() != qApp->thread()); + m_blockingConnection.start(localTransform * m_savedTransform); } private: KisShapeLayer *m_shapeLayer; QTransform m_globalDocTransform; QTransform m_savedTransform; + KisSafeBlockingQueueConnectionProxy m_blockingConnection; }; KUndo2Command* KisShapeLayer::transform(const QTransform &transform) { QList shapes = shapesToBeTransformed(); if (shapes.isEmpty()) return 0; KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shapes.size() == 1 && shapes.first() == this, 0); /** * We cannot transform shapes in the worker thread. Therefor we emit blocking-queued * signal to transform them in the GUI thread and then return. */ KisImageViewConverter *converter = dynamic_cast(this->converter()); QTransform docSpaceTransform = converter->documentToView() * transform * converter->viewToDocument(); return new TransformShapeLayerDeferred(this, docSpaceTransform); } KUndo2Command *KisShapeLayer::setProfile(const KoColorProfile *profile) { using namespace KisDoSomethingCommandOps; KUndo2Command *cmd = new KUndo2Command(); new KisDoSomethingCommand(this, false, cmd); m_d->paintDevice->setProfile(profile, cmd); new KisDoSomethingCommand(this, true, cmd); return cmd; } KUndo2Command *KisShapeLayer::convertTo(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { using namespace KisDoSomethingCommandOps; KUndo2Command *cmd = new KUndo2Command(); new KisDoSomethingCommand(this, false, cmd); m_d->paintDevice->convertTo(dstColorSpace, renderingIntent, conversionFlags, cmd); new KisDoSomethingCommand(this, true, cmd); return cmd; } KoShapeControllerBase *KisShapeLayer::shapeController() const { return m_d->controller; } diff --git a/libs/ui/flake/kis_shape_layer.h b/libs/ui/flake/kis_shape_layer.h index 5292859e60..bbade1e811 100644 --- a/libs/ui/flake/kis_shape_layer.h +++ b/libs/ui/flake/kis_shape_layer.h @@ -1,216 +1,210 @@ /* * Copyright (c) 2006 Boudewijn Rempt * Copyright (c) 2007 Thomas Zander * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_SHAPE_LAYER_H_ #define KIS_SHAPE_LAYER_H_ #include #include #include #include #include #include class QRect; class QIcon; class QRect; class QString; class KoShapeManager; class KoStore; class KoViewConverter; class KoShapeControllerBase; class KoDocumentResourceManager; class KisShapeLayerCanvasBase; const QString KIS_SHAPE_LAYER_ID = "KisShapeLayer"; /** A KisShapeLayer contains any number of non-krita flakes, such as path shapes, text shapes and anything else people come up with. The KisShapeLayer has a shapemanager and a canvas of its own. The canvas paints onto the projection, and the projection is what we render in Krita. This means that no matter how many views you have, you cannot have a different view on your shapes per view. XXX: what about removing shapes? */ class KRITAUI_EXPORT KisShapeLayer : public KisExternalLayer, public KoShapeLayer, public KisDelayedUpdateNodeInterface, public KisCroppedOriginalLayerInterface { Q_OBJECT public: KisShapeLayer(KoShapeControllerBase* shapeController, KisImageWSP image, const QString &name, quint8 opacity); KisShapeLayer(const KisShapeLayer& _rhs); KisShapeLayer(const KisShapeLayer& _rhs, KoShapeControllerBase* controller, KisShapeLayerCanvasBase *canvas = 0); /** * Merge constructor. * * Creates a new layer as a merge of two existing layers. * * This is used by createMergedLayer() */ KisShapeLayer(const KisShapeLayer& _merge, const KisShapeLayer &_addShapes); ~KisShapeLayer() override; protected: KisShapeLayer(KoShapeControllerBase* shapeController, KisImageWSP image, const QString &name, quint8 opacity, KisShapeLayerCanvasBase *canvas); private: void initShapeLayer(KoShapeControllerBase* controller, KisPaintDeviceSP copyFromProjection = 0, KisShapeLayerCanvasBase *canvas = 0); public: KisNodeSP clone() const override { return new KisShapeLayer(*this); } bool allowAsChild(KisNodeSP) const override; void setImage(KisImageWSP image) override; KisLayerSP createMergedLayerTemplate(KisLayerSP prevLayer) override; void fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer) override; public: // KoShape overrides bool isSelectable() const { return false; } void setParent(KoShapeContainer *parent); // KisExternalLayer implementation QIcon icon() const override; void resetCache() override; KisPaintDeviceSP original() const override; KisPaintDeviceSP paintDevice() const override; qint32 x() const override; qint32 y() const override; void setX(qint32) override; void setY(qint32) override; bool accept(KisNodeVisitor&) override; void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) override; KoShapeManager *shapeManager() const; static bool saveShapesToStore(KoStore *store, QList shapes, const QSizeF &sizeInPt); static QList createShapesFromSvg(QIODevice *device, const QString &baseXmlDir, const QRectF &rectInPixels, qreal resolutionPPI, KoDocumentResourceManager *resourceManager, QSizeF *fragmentSize); bool saveLayer(KoStore * store) const; bool loadLayer(KoStore* store); KUndo2Command* crop(const QRect & rect) override; KUndo2Command* transform(const QTransform &transform) override; KUndo2Command* setProfile(const KoColorProfile *profile) override; KUndo2Command* convertTo(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) override; bool visible(bool recursive = false) const override; void setVisible(bool visible, bool isLoading = false) override; void setUserLocked(bool value) override; bool isShapeEditable(bool recursive) const override; /** * Forces a repaint of a shape layer without waiting for an event loop * calling a delayed timer update. If you want to see the result of the shape * layer right here and right now, you should do: * * shapeLayer->setDirty(); * shapeLayer->image()->waitForDone(); * shapeLayer->forceUpdateTimedNode(); * shapeLayer->image()->waitForDone(); * */ void forceUpdateTimedNode() override; /** * \return true if there are any pending updates in the delayed queue */ bool hasPendingTimedUpdates() const override; void forceUpdateHiddenAreaOnOriginal() override; protected: using KoShape::isVisible; bool loadSvg(QIODevice *device, const QString &baseXmlDir); friend class ShapeLayerContainerModel; KoViewConverter* converter() const; KoShapeControllerBase *shapeController() const; + friend class TransformShapeLayerDeferred; + Q_SIGNALS: /** * These signals are forwarded from the local shape manager * This is done because we switch KoShapeManager and therefore * KoSelection in KisCanvas2, so we need to connect local managers * to the UI as well. * * \see comment in the constructor of KisCanvas2 */ void selectionChanged(); void currentLayerChanged(const KoShapeLayer *layer); Q_SIGNALS: /** * A signal + slot to synchronize UI and image * threads. Image thread emits the signal, UI * thread performs the action */ void sigMoveShapes(const QPointF &diff); - /** - * A signal + slot to synchronize UI and image - * threads. Image thread emits the signal, UI - * thread performs the action - */ - void sigTransformShapes(const QTransform &transform); - - private Q_SLOTS: void slotMoveShapes(const QPointF &diff); void slotTransformShapes(const QTransform &transform); private: QList shapesToBeTransformed(); private: struct Private; Private * const m_d; }; #endif diff --git a/libs/ui/flake/kis_shape_layer_canvas.cpp b/libs/ui/flake/kis_shape_layer_canvas.cpp index 2d05d0b13d..6d10988eb8 100644 --- a/libs/ui/flake/kis_shape_layer_canvas.cpp +++ b/libs/ui/flake/kis_shape_layer_canvas.cpp @@ -1,441 +1,441 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_shape_layer_canvas.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_image_view_converter.h" #include #include #include #include #include "kis_global.h" #include "krita_utils.h" KisShapeLayerCanvasBase::KisShapeLayerCanvasBase(KisShapeLayer *parent, KisImageWSP image) : KoCanvasBase(0) , m_viewConverter(new KisImageViewConverter(image)) , m_shapeManager(new KoShapeManager(this)) , m_selectedShapesProxy(new KoSelectedShapesProxySimple(m_shapeManager.data())) { m_shapeManager->selection()->setActiveLayer(parent); } KoShapeManager *KisShapeLayerCanvasBase::shapeManager() const { return m_shapeManager.data(); } KoSelectedShapesProxy *KisShapeLayerCanvasBase::selectedShapesProxy() const { return m_selectedShapesProxy.data(); } KoViewConverter* KisShapeLayerCanvasBase::viewConverter() const { return m_viewConverter.data(); } void KisShapeLayerCanvasBase::gridSize(QPointF *offset, QSizeF *spacing) const { KIS_SAFE_ASSERT_RECOVER_NOOP(false); // This should never be called as this canvas should have no tools. Q_UNUSED(offset); Q_UNUSED(spacing); } bool KisShapeLayerCanvasBase::snapToGrid() const { KIS_SAFE_ASSERT_RECOVER_NOOP(false); // This should never be called as this canvas should have no tools. return false; } void KisShapeLayerCanvasBase::addCommand(KUndo2Command *) { KIS_SAFE_ASSERT_RECOVER_NOOP(false); // This should never be called as this canvas should have no tools. } KoToolProxy * KisShapeLayerCanvasBase::toolProxy() const { // KIS_SAFE_ASSERT_RECOVER_NOOP(false); // This should never be called as this canvas should have no tools. return 0; } QWidget* KisShapeLayerCanvasBase::canvasWidget() { return 0; } const QWidget* KisShapeLayerCanvasBase::canvasWidget() const { return 0; } KoUnit KisShapeLayerCanvasBase::unit() const { KIS_SAFE_ASSERT_RECOVER_NOOP(false); // This should never be called as this canvas should have no tools. return KoUnit(KoUnit::Point); } void KisShapeLayerCanvasBase::prepareForDestroying() { m_isDestroying = true; } bool KisShapeLayerCanvasBase::hasChangedWhileBeingInvisible() { return m_hasChangedWhileBeingInvisible; } KisShapeLayerCanvas::KisShapeLayerCanvas(KisShapeLayer *parent, KisImageWSP image) : KisShapeLayerCanvasBase(parent, image) , m_projection(0) , m_parentLayer(parent) , m_asyncUpdateSignalCompressor(100, KisSignalCompressor::FIRST_INACTIVE) + , m_safeForcedConnection(std::bind(&KisShapeLayerCanvas::slotStartAsyncRepaint, this)) { /** * The layour should also add itself to its own shape manager, so that the canvas * would track its changes/transformations */ m_shapeManager->addShape(parent, KoShapeManager::AddWithoutRepaint); m_shapeManager->selection()->setActiveLayer(parent); connect(&m_asyncUpdateSignalCompressor, SIGNAL(timeout()), SLOT(slotStartAsyncRepaint())); - connect(this, SIGNAL(sigBlockingForceAsyncRepaintStart()), SLOT(slotStartAsyncRepaint()), Qt::BlockingQueuedConnection); setImage(image); } KisShapeLayerCanvas::~KisShapeLayerCanvas() { m_shapeManager->remove(m_parentLayer); } void KisShapeLayerCanvas::setImage(KisImageWSP image) { if (m_image) { disconnect(m_image, 0, this, 0); } m_viewConverter->setImage(image); m_image = image; if (image) { connect(m_image, SIGNAL(sigSizeChanged(QPointF,QPointF)), SLOT(slotImageSizeChanged())); m_cachedImageRect = m_image->bounds(); } } class KisRepaintShapeLayerLayerJob : public KisSpontaneousJob { public: KisRepaintShapeLayerLayerJob(KisShapeLayerSP layer, KisShapeLayerCanvas *canvas) : m_layer(layer), m_canvas(canvas) { } bool overrides(const KisSpontaneousJob *_otherJob) override { const KisRepaintShapeLayerLayerJob *otherJob = dynamic_cast(_otherJob); return otherJob && otherJob->m_canvas == m_canvas; } void run() override { m_canvas->repaint(); } int levelOfDetail() const override { return 0; } QString debugName() const override { QString result; QDebug dbg(&result); dbg << "KisRepaintShapeLayerLayerJob" << m_layer; return result; } private: // we store a pointer to the layer just // to keep the lifetime of the canvas! KisShapeLayerSP m_layer; KisShapeLayerCanvas *m_canvas; }; void KisShapeLayerCanvas::updateCanvas(const QVector ®ion) { if (!m_parentLayer->image() || m_isDestroying) { return; } { QMutexLocker locker(&m_dirtyRegionMutex); Q_FOREACH (const QRectF &rc, region) { // grow for antialiasing const QRect imageRect = kisGrowRect(m_viewConverter->documentToView(rc).toAlignedRect(), 2); m_dirtyRegion += imageRect; } } m_asyncUpdateSignalCompressor.start(); m_hasUpdateInCompressor = true; } void KisShapeLayerCanvas::updateCanvas(const QRectF& rc) { updateCanvas(QVector({rc})); } void KisShapeLayerCanvas::slotStartAsyncRepaint() { QRect repaintRect; bool forceUpdateHiddenAreasOnly = false; const qint32 MASK_IMAGE_WIDTH = 256; const qint32 MASK_IMAGE_HEIGHT = 256; { QMutexLocker locker(&m_dirtyRegionMutex); repaintRect = m_dirtyRegion.boundingRect(); forceUpdateHiddenAreasOnly = m_forceUpdateHiddenAreasOnly; /// Since we are going to override the previous jobs, we should fetch /// all the area covered by it. Otherwise we'll get dirty leftovers of /// the layer on the projection Q_FOREACH (const KoShapeManager::PaintJob &job, m_paintJobs) { repaintRect |= m_viewConverter->documentToView().mapRect(job.docUpdateRect).toAlignedRect(); } m_paintJobs.clear(); m_dirtyRegion = QRegion(); m_forceUpdateHiddenAreasOnly = false; } if (!forceUpdateHiddenAreasOnly) { if (repaintRect.isEmpty()) { return; } // Crop the update rect by the image bounds. We keep the cache consistent // by tracking the size of the image in slotImageSizeChanged() repaintRect = repaintRect.intersected(m_parentLayer->image()->bounds()); } else { const QRectF shapesBounds = KoShape::boundingRect(m_shapeManager->shapes()); repaintRect |= kisGrowRect(m_viewConverter->documentToView(shapesBounds).toAlignedRect(), 2); } /** * Vector shapes are not thread-safe against concurrent read-writes, so we * need to utilize rather complicated policy on accessing them: * * 1) All shape writes happen in GUI thread (right in the tools) * 2) No concurrent reads from the shapes may happen in other threads * while the user is modifying them. * * That is why our shape rendering code is split into two parts: * * 1) First we just fetch a shallow copy of the shapes of the layer (it * takes about 1ms for complicated vector layers) and pack them into * KoShapeManager::PaintJobsList jobs. It happens here, in * slotStartAsyncRepaint(), which runs in the GUI thread. It guarantees * that noone is accessing the shapes during the copy operation. * * 2) The rendering itself happens in the worker thread in repaint(). But * repaint() doesn't access original shapes anymore. It accesses only they * shallow copies, which means that there is no concurrent * access to anything (*). * * (*) "no concurrent access to anything" is a rather fragile term :) There * will still be concurrent access to it, on detaching... But(!), when detaching, * the original data is kept unchanged, so "it should be safe enough"(c). Especially * if we guarantee that rendering thread may not cause a detach (?), and the detach * can happen only from a single GUI thread. */ const QVector updateRects = KritaUtils::splitRectIntoPatchesTight(repaintRect, QSize(MASK_IMAGE_WIDTH, MASK_IMAGE_HEIGHT)); KoShapeManager::PaintJobsList jobs; Q_FOREACH (const QRect &viewUpdateRect, updateRects) { jobs << KoShapeManager::PaintJob(m_viewConverter->viewToDocument().mapRect(QRectF(viewUpdateRect)), viewUpdateRect); } m_shapeManager->preparePaintJobs(jobs, m_parentLayer); { QMutexLocker locker(&m_dirtyRegionMutex); m_paintJobs = jobs; } m_hasUpdateInCompressor = false; m_image->addSpontaneousJob(new KisRepaintShapeLayerLayerJob(m_parentLayer, this)); } void KisShapeLayerCanvas::slotImageSizeChanged() { QRegion dirtyCacheRegion; dirtyCacheRegion += m_image->bounds(); dirtyCacheRegion += m_cachedImageRect; dirtyCacheRegion -= m_image->bounds() & m_cachedImageRect; QVector dirtyRects; auto rc = dirtyCacheRegion.begin(); while (rc != dirtyCacheRegion.end()) { dirtyRects.append(m_viewConverter->viewToDocument(*rc)); rc++; } updateCanvas(dirtyRects); m_cachedImageRect = m_image->bounds(); } void KisShapeLayerCanvas::repaint() { KoShapeManager::PaintJobsList paintJobs; { QMutexLocker locker(&m_dirtyRegionMutex); std::swap(paintJobs, m_paintJobs); } const qint32 MASK_IMAGE_WIDTH = 256; const qint32 MASK_IMAGE_HEIGHT = 256; QImage image(MASK_IMAGE_WIDTH, MASK_IMAGE_HEIGHT, QImage::Format_ARGB32); QPainter tempPainter(&image); tempPainter.setRenderHint(QPainter::Antialiasing); tempPainter.setRenderHint(QPainter::TextAntialiasing); quint8 * dstData = new quint8[MASK_IMAGE_WIDTH * MASK_IMAGE_HEIGHT * m_projection->pixelSize()]; QRect repaintRect; Q_FOREACH (const KoShapeManager::PaintJob &job, paintJobs) { image.fill(0); tempPainter.setTransform(QTransform()); tempPainter.setClipRect(QRect(0,0,MASK_IMAGE_WIDTH,MASK_IMAGE_HEIGHT)); tempPainter.setTransform(m_viewConverter->documentToView() * QTransform::fromTranslate(-job.viewUpdateRect.x(), -job.viewUpdateRect.y())); m_shapeManager->paintJob(tempPainter, job, false); KoColorSpaceRegistry::instance()->rgb8() ->convertPixelsTo(image.constBits(), dstData, m_projection->colorSpace(), MASK_IMAGE_WIDTH * MASK_IMAGE_HEIGHT, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); // TODO: use job.viewUpdateRect instead of MASK_IMAGE_WIDTH/HEIGHT m_projection->writeBytes(dstData, job.viewUpdateRect.x(), job.viewUpdateRect.y(), MASK_IMAGE_WIDTH, MASK_IMAGE_HEIGHT); repaintRect |= job.viewUpdateRect; } delete[] dstData; m_projection->purgeDefaultPixels(); m_parentLayer->setDirty(repaintRect); m_hasChangedWhileBeingInvisible |= !m_parentLayer->visible(true); } void KisShapeLayerCanvas::forceRepaint() { /** * WARNING! Although forceRepaint() may be called from different threads, it is * not entirely safe. If the user plays with shapes at the same time (vector tools are * not ported to strokes yet), the shapes my be accessed from two different places at * the same time, which will cause a crash. * * The only real solution to this is to port vector tools to strokes framework. */ if (hasPendingUpdates()) { m_asyncUpdateSignalCompressor.stop(); - emit sigBlockingForceAsyncRepaintStart(); + m_safeForcedConnection.start(); } } bool KisShapeLayerCanvas::hasPendingUpdates() const { return m_hasUpdateInCompressor; } void KisShapeLayerCanvas::forceRepaintWithHiddenAreas() { KIS_SAFE_ASSERT_RECOVER_RETURN(m_parentLayer->image()); KIS_SAFE_ASSERT_RECOVER_RETURN(!m_isDestroying); { QMutexLocker locker(&m_dirtyRegionMutex); m_forceUpdateHiddenAreasOnly = true; } m_asyncUpdateSignalCompressor.stop(); - emit sigBlockingForceAsyncRepaintStart(); + m_safeForcedConnection.start(); } void KisShapeLayerCanvas::resetCache() { m_projection->clear(); QList shapes = m_shapeManager->shapes(); Q_FOREACH (const KoShape* shape, shapes) { shape->update(); } } void KisShapeLayerCanvas::rerenderAfterBeingInvisible() { KIS_SAFE_ASSERT_RECOVER_RETURN(m_parentLayer->visible(true)); m_hasChangedWhileBeingInvisible = false; resetCache(); } diff --git a/libs/ui/flake/kis_shape_layer_canvas.h b/libs/ui/flake/kis_shape_layer_canvas.h index 33d61e3286..2dceb5d483 100644 --- a/libs/ui/flake/kis_shape_layer_canvas.h +++ b/libs/ui/flake/kis_shape_layer_canvas.h @@ -1,136 +1,135 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_SHAPE_LAYER_CANVAS_H #define KIS_SHAPE_LAYER_CANVAS_H #include #include #include #include #include "kis_thread_safe_signal_compressor.h" #include #include #include +#include class KoShapeManager; class KoToolProxy; class KoViewConverter; class KUndo2Command; class QWidget; class KoUnit; class KisImageViewConverter; class KisShapeLayerCanvasBase : public KoCanvasBase { public: KisShapeLayerCanvasBase(KisShapeLayer *parent, KisImageWSP image); virtual void setImage(KisImageWSP image) = 0; void prepareForDestroying(); virtual void forceRepaint() = 0; virtual bool hasPendingUpdates() const = 0; virtual void forceRepaintWithHiddenAreas() { forceRepaint(); } bool hasChangedWhileBeingInvisible(); virtual void rerenderAfterBeingInvisible() = 0; virtual void resetCache() = 0; KoShapeManager *shapeManager() const override; KoViewConverter *viewConverter() const override; void gridSize(QPointF *offset, QSizeF *spacing) const override; bool snapToGrid() const override; void addCommand(KUndo2Command *command) override; KoSelectedShapesProxy *selectedShapesProxy() const override; KoToolProxy * toolProxy() const override; QWidget* canvasWidget() override; const QWidget* canvasWidget() const override; KoUnit unit() const override; void updateInputMethodInfo() override {} void setCursor(const QCursor &) override {} protected: QScopedPointer m_viewConverter; QScopedPointer m_shapeManager; QScopedPointer m_selectedShapesProxy; bool m_hasChangedWhileBeingInvisible {false}; bool m_isDestroying {false}; }; /** * KisShapeLayerCanvas is a special canvas implementation that Krita * uses for non-krita shapes to request updates on. * * Do NOT give this canvas to tools or to the KoCanvasController, it's * not made for that. */ class KisShapeLayerCanvas : public KisShapeLayerCanvasBase { Q_OBJECT public: KisShapeLayerCanvas(KisShapeLayer *parent, KisImageWSP image); ~KisShapeLayerCanvas() override; /// This canvas won't render onto a widget, but a projection void setProjection(KisPaintDeviceSP projection) { m_projection = projection; } void setImage(KisImageWSP image) override; void updateCanvas(const QRectF& rc) override; void updateCanvas(const QVector ®ion); void forceRepaint() override; bool hasPendingUpdates() const override; void forceRepaintWithHiddenAreas() override; void resetCache() override; void rerenderAfterBeingInvisible() override; private Q_SLOTS: friend class KisRepaintShapeLayerLayerJob; void repaint(); void slotStartAsyncRepaint(); void slotImageSizeChanged(); -Q_SIGNALS: - void sigBlockingForceAsyncRepaintStart(); - private: KisPaintDeviceSP m_projection; KisShapeLayer *m_parentLayer {0}; KisThreadSafeSignalCompressor m_asyncUpdateSignalCompressor; volatile bool m_hasUpdateInCompressor = false; + KisSafeBlockingQueueConnectionProxy m_safeForcedConnection; bool m_forceUpdateHiddenAreasOnly = false; QRegion m_dirtyRegion; QMutex m_dirtyRegionMutex; KoShapeManager::PaintJobsList m_paintJobs; QRect m_cachedImageRect; KisImageWSP m_image; }; #endif diff --git a/libs/ui/kis_animation_importer.cpp b/libs/ui/kis_animation_importer.cpp index a4c69aab8a..7e51c3153d 100644 --- a/libs/ui/kis_animation_importer.cpp +++ b/libs/ui/kis_animation_importer.cpp @@ -1,130 +1,128 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_animation_importer.h" #include #include "KoColorSpace.h" #include #include #include "KisPart.h" #include "KisDocument.h" #include "kis_image.h" #include "kis_undo_adapter.h" #include "kis_paint_layer.h" #include "kis_group_layer.h" #include "kis_raster_keyframe_channel.h" #include "commands/kis_image_layer_add_command.h" struct KisAnimationImporter::Private { KisImageSP image; KisDocument *document; bool stop; KoUpdaterPtr updater; }; KisAnimationImporter::KisAnimationImporter(KisImageSP image, KoUpdaterPtr updater) : m_d(new Private()) { m_d->document = 0; m_d->image = image; m_d->stop = false; m_d->updater = updater; } KisAnimationImporter::KisAnimationImporter(KisDocument* document) : m_d(new Private()) { m_d->document= document; m_d->image = document->image(); m_d->stop = false; } KisAnimationImporter::~KisAnimationImporter() {} KisImportExportErrorCode KisAnimationImporter::import(QStringList files, int firstFrame, int step) { Q_ASSERT(step > 0); - m_d->image->lock(); KisUndoAdapter *undo = m_d->image->undoAdapter(); undo->beginMacro(kundo2_i18n("Import animation")); QScopedPointer importDoc(KisPart::instance()->createDocument()); importDoc->setFileBatchMode(true); KisImportExportErrorCode status = ImportExportCodes::OK; int frame = firstFrame; int filesProcessed = 0; if (m_d->updater) { m_d->updater->setRange(0, files.size()); } KisRasterKeyframeChannel *contentChannel = 0; Q_FOREACH(QString file, files) { bool successfullyLoaded = importDoc->openUrl(QUrl::fromLocalFile(file), KisDocument::DontAddToRecent); if (!successfullyLoaded) { status = ImportExportCodes::InternalError; break; } if (frame == firstFrame) { const KoColorSpace *cs = importDoc->image()->colorSpace(); KisPaintLayerSP paintLayer = new KisPaintLayer(m_d->image, m_d->image->nextLayerName(), OPACITY_OPAQUE_U8, cs); undo->addCommand(new KisImageLayerAddCommand(m_d->image, paintLayer, m_d->image->rootLayer(), m_d->image->rootLayer()->childCount())); paintLayer->enableAnimation(); contentChannel = qobject_cast(paintLayer->getKeyframeChannel(KisKeyframeChannel::Content.id(), true)); } if (m_d->updater) { if (m_d->updater->interrupted()) { m_d->stop = true; } else { m_d->updater->setValue(filesProcessed + 1); // the updater doesn't call that automatically, // it is "threaded" by default qApp->processEvents(); } } if (m_d->stop) { status = ImportExportCodes::Cancelled; break; } contentChannel->importFrame(frame, importDoc->image()->projection(), NULL); frame += step; filesProcessed++; } undo->endMacro(); - m_d->image->unlock(); return status; } void KisAnimationImporter::cancel() { m_d->stop = true; } diff --git a/libs/ui/kis_custom_pattern.cc b/libs/ui/kis_custom_pattern.cc index b1279f251b..72a07c3af1 100644 --- a/libs/ui/kis_custom_pattern.cc +++ b/libs/ui/kis_custom_pattern.cc @@ -1,190 +1,190 @@ /* * Copyright (c) 2006 Bart Coppens * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_custom_pattern.h" #include #include #include #include #include #include #include #include #include #include #include "KisDocument.h" #include "KisViewManager.h" #include "kis_image.h" #include "kis_layer.h" #include "kis_paint_device.h" #include "kis_selection.h" #include "kis_painter.h" #include #include "KisResourceServerProvider.h" #include "kis_paint_layer.h" KisCustomPattern::KisCustomPattern(QWidget *parent, const char* name, const QString& caption, KisViewManager* view) : KisWdgCustomPattern(parent, name) , m_view(view) { Q_ASSERT(m_view); setWindowTitle(caption); m_pattern = 0; preview->setScaledContents(true); m_rServer = KoResourceServerProvider::instance()->patternServer(); connect(addButton, SIGNAL(pressed()), this, SLOT(slotAddPredefined())); connect(patternButton, SIGNAL(pressed()), this, SLOT(slotUsePattern())); connect(updateButton, SIGNAL(pressed()), this, SLOT(slotUpdateCurrentPattern())); connect(cmbSource, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateCurrentPattern())); } KisCustomPattern::~KisCustomPattern() { m_pattern.clear(); } void KisCustomPattern::slotUpdateCurrentPattern() { m_pattern.clear(); if (m_view && m_view->image()) { createPattern(); if (m_pattern) { const qint32 maxSize = 150; if ((m_pattern->width() > maxSize) || (m_pattern->height() > maxSize)) { float aspectRatio = (float)m_pattern->width() / m_pattern->height(); qint32 scaledWidth, scaledHeight; if (m_pattern->width() > m_pattern->height()) { scaledWidth = maxSize; scaledHeight = maxSize / aspectRatio; } else { scaledWidth = maxSize * aspectRatio; scaledHeight = maxSize; } if (scaledWidth == 0) scaledWidth++; if (scaledHeight == 0) scaledHeight++; QPixmap scaledPixmap = QPixmap::fromImage(m_pattern->pattern()); preview->setPixmap(scaledPixmap.scaled(scaledWidth, scaledHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation)); } else { preview->setPixmap(QPixmap::fromImage(m_pattern->pattern())); } } } } void KisCustomPattern::slotAddPredefined() { if (!m_pattern) return; // Save in the directory that is likely to be: ~/.kde/share/apps/krita/patterns // a unique file with this pattern name QString dir = KoResourceServerProvider::instance()->patternServer()->saveLocation(); QString tempFileName; { QTemporaryFile file(dir + QLatin1String("/krita_XXXXXX") + m_pattern->defaultFileExtension() ); file.setAutoRemove(false); file.open(); tempFileName = file.fileName().split("/").last(); } // Save it to that file m_pattern->setFilename(tempFileName); // Add it to the pattern server, so that it automatically gets to the mediators, and // so to the other pattern choosers can pick it up, if they want to m_rServer->addResource(m_pattern->clone().dynamicCast()); } void KisCustomPattern::slotUsePattern() { if (!m_pattern) return; KoPatternSP copy = m_pattern->clone().dynamicCast(); emit(activatedResource(copy)); } void KisCustomPattern::createPattern() { if (!m_view) return; KisPaintDeviceSP dev; KisPaintDeviceSP cache; QString name; KisImageWSP image = m_view->image(); if (!image) return; QRect rc = image->bounds(); if (cmbSource->currentIndex() == 0) { dev = m_view->activeNode()->projection(); name = m_view->activeNode()->name(); QRect rc2 = dev->exactBounds(); rc = rc.intersected(rc2); } else { - image->lock(); + image->barrierLock(); dev = image->projection(); image->unlock(); name = image->objectName(); } if (!dev) return; if(m_view->selection()) { KisSelectionSP selection = m_view->selection(); QRect selectionRect = selection->selectedExactRect(); cache = dev->createCompositionSourceDevice(); KisPainter gc(cache); gc.setSelection(selection); gc.bitBlt(selectionRect.topLeft(), dev, selectionRect); rc = selectionRect; } else { cache = dev; } if (!cache) return; // warn when creating large patterns QSize size = rc.size(); if (size.width() > 1000 || size.height() > 1000) { lblWarning->setText(i18n("The current image is too big to create a pattern. " "The pattern will be scaled down.")); size.scale(1000, 1000, Qt::KeepAspectRatio); } QString dir = KoResourceServerProvider::instance()->patternServer()->saveLocation(); m_pattern = KoPatternSP(new KoPattern(cache->createThumbnail(size.width(), size.height(), rc, /*oversample*/ 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()), name, dir)); } diff --git a/libs/ui/widgets/kis_floating_message.cpp b/libs/ui/widgets/kis_floating_message.cpp index 6330e90cd9..26050ecd9c 100644 --- a/libs/ui/widgets/kis_floating_message.cpp +++ b/libs/ui/widgets/kis_floating_message.cpp @@ -1,345 +1,349 @@ /* * This file is part of KimageShop^WKrayon^WKrita * * Copyright (c) 2012 Boudewijn Rempt * Copyright (c) 2004 Christian Muehlhaeuser * Copyright (c) 2004-2006 Seb Ruiz * Copyright (c) 2004,2005 Max Howell * Copyright (c) 2005 Gabor Lehel * Copyright (c) 2008,2009 Mark Kretschmann * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_floating_message.h" #include #include #include #include #include #include #include #include #include "kis_global.h" /* Code copied from kshadowengine.cpp * * Copyright (C) 2003 Laur Ivan * * Many thanks to: * - Bernardo Hung for the enhanced shadow * algorithm (currently used) * - Tim Jansen for the API updates and fixes. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License version 2 as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ namespace ShadowEngine { // Not sure, doesn't work above 10 static const int MULTIPLICATION_FACTOR = 3; // Multiplication factor for pixels directly above, under, or next to the text static const double AXIS_FACTOR = 2.0; // Multiplication factor for pixels diagonal to the text static const double DIAGONAL_FACTOR = 0.1; // Self explanatory static const int MAX_OPACITY = 200; double decay( QImage&, int, int ); QImage makeShadow( const QPixmap& textPixmap, const QColor &bgColor ) { const int w = textPixmap.width(); const int h = textPixmap.height(); const int bgr = bgColor.red(); const int bgg = bgColor.green(); const int bgb = bgColor.blue(); int alphaShadow; // This is the source pixmap QImage img = textPixmap.toImage(); QImage result( w, h, QImage::Format_ARGB32 ); result.fill( 0 ); // fill with black static const int M = 5; for( int i = M; i < w - M; i++) { for( int j = M; j < h - M; j++ ) { alphaShadow = (int) decay( img, i, j ); result.setPixel( i,j, qRgba( bgr, bgg , bgb, qMin( MAX_OPACITY, alphaShadow ) ) ); } } return result; } double decay( QImage& source, int i, int j ) { double alphaShadow; alphaShadow =(qGray(source.pixel(i-1,j-1)) * DIAGONAL_FACTOR + qGray(source.pixel(i-1,j )) * AXIS_FACTOR + qGray(source.pixel(i-1,j+1)) * DIAGONAL_FACTOR + qGray(source.pixel(i ,j-1)) * AXIS_FACTOR + 0 + qGray(source.pixel(i ,j+1)) * AXIS_FACTOR + qGray(source.pixel(i+1,j-1)) * DIAGONAL_FACTOR + qGray(source.pixel(i+1,j )) * AXIS_FACTOR + qGray(source.pixel(i+1,j+1)) * DIAGONAL_FACTOR) / MULTIPLICATION_FACTOR; return alphaShadow; } } #define OSD_WINDOW_OPACITY 0.74 KisFloatingMessage::KisFloatingMessage(const QString &message, QWidget *parent, bool showOverParent, int timeout, Priority priority, int alignment) : QWidget(parent) , m_message(message) , m_showOverParent(showOverParent) , m_timeout(timeout) , m_priority(priority) , m_alignment(alignment) , widgetQueuedForDeletion(false) { m_icon = KisIconUtils::loadIcon("krita").pixmap(256, 256).toImage(); setWindowFlags(Qt::FramelessWindowHint | Qt::ToolTip); setFocusPolicy(Qt::NoFocus); setAttribute(Qt::WA_ShowWithoutActivating); setFont(QFont("sans-serif")); m_timer.setSingleShot( true ); connect(&m_timer, SIGNAL(timeout()), SLOT(startFade())); connect(this, SIGNAL(destroyed()), SLOT(widgetDeleted())); } void KisFloatingMessage::tryOverrideMessage(const QString message, const QIcon& icon, int timeout, KisFloatingMessage::Priority priority, int alignment) { if ((int)priority > (int)m_priority) return; m_message = message; setIcon(icon); m_timeout = timeout; m_priority = priority; m_alignment = alignment; showMessage(); update(); } void KisFloatingMessage::showMessage() { if (widgetQueuedForDeletion) return; #if QT_VERSION >= QT_VERSION_CHECK(5,13,0) setGeometry(determineMetrics(fontMetrics().horizontalAdvance('x'))); #else setGeometry(determineMetrics(fontMetrics().width('x'))); #endif setWindowOpacity(OSD_WINDOW_OPACITY); QWidget::setVisible(true); m_timer.start(m_timeout); } void KisFloatingMessage::setShowOverParent(bool show) { m_showOverParent = show; } void KisFloatingMessage::setIcon(const QIcon& icon) { m_icon = icon.pixmap(256, 256).toImage(); } const int MARGIN = 20; QRect KisFloatingMessage::determineMetrics( const int M ) { m_m = M; const QSize minImageSize = m_icon.size().boundedTo(QSize(100, 100)); // determine a sensible maximum size, don't cover the whole desktop or cross the screen const QSize margin( (M + MARGIN) * 2, (M + MARGIN) * 2); //margins const QSize image = m_icon.isNull() ? QSize(0, 0) : minImageSize; - +#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) QScreen *s = qApp->screenAt(parentWidget()->geometry().topLeft()); const QSize max = s->availableGeometry().size() - margin; +#else + const QSize max = QApplication::desktop()->availableGeometry(parentWidget()).size() - margin; +#endif + // If we don't do that, the boundingRect() might not be suitable for drawText() (Qt issue N67674) m_message.replace(QRegExp( " +\n"), "\n"); // remove consecutive line breaks m_message.replace(QRegExp( "\n+"), "\n"); // The osd cannot be larger than the screen QRect rect = fontMetrics().boundingRect(0, 0, max.width() - image.width(), max.height(), m_alignment, m_message); if (!m_icon.isNull()) { const int availableWidth = max.width() - rect.width() - M; //WILL be >= (minImageSize.width() - M) m_scaledIcon = QPixmap::fromImage(m_icon.scaled(qMin(availableWidth, m_icon.width()), qMin( rect.height(), m_icon.height()), Qt::KeepAspectRatio, Qt::SmoothTransformation)); const int widthIncludingImage = rect.width() + m_scaledIcon.width() + M; //margin between text + image rect.setWidth( widthIncludingImage ); } // expand in all directions by 2*M // // take care with this rect, because it must be *bigger* // than the rect we paint the message in rect = kisGrowRect(rect, 2 * M); const QSize newSize = rect.size(); QRect screen = s->availableGeometry(); QPoint newPos(MARGIN, MARGIN); if (parentWidget() && m_showOverParent) { screen = parentWidget()->geometry(); screen.setTopLeft(parentWidget()->mapToGlobal(QPoint(MARGIN, MARGIN + 50))); newPos = screen.topLeft(); } else { // move to the right newPos.rx() = screen.width() - MARGIN - newSize.width(); //ensure we don't dip below the screen if (newPos.y() + newSize.height() > screen.height() - MARGIN) { newPos.ry() = screen.height() - MARGIN - newSize.height(); } // correct for screen position newPos += screen.topLeft(); if (parentWidget()) { // Move a bit to the left as there could be a scrollbar newPos.setX(newPos.x() - MARGIN); } } QRect rc(newPos, rect.size()); return rc; } void KisFloatingMessage::paintEvent( QPaintEvent *e ) { const int& M = m_m; QPoint point; QRect rect(point, size()); rect.adjust(0, 0, -1, -1); QPainter p(this); p.setRenderHints( QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform | QPainter::HighQualityAntialiasing ); p.setClipRect(e->rect()); //QPixmap background = The::svgHandler()->renderSvgWithDividers( "service_list_item", width(), height(), "service_list_item" ); //p.drawPixmap( 0, 0, background ); p.setPen(Qt::white); rect.adjust(M, M, -M, -M); if (!m_icon.isNull()) { QRect r(rect); r.setTop((size().height() - m_scaledIcon.height() ) / 2); r.setSize(m_scaledIcon.size()); p.drawPixmap(r.topLeft(), m_scaledIcon); rect.setLeft(rect.left() + m_scaledIcon.width() + M); } int graphicsHeight = 0; rect.setBottom(rect.bottom() - graphicsHeight); // Draw "shadow" text effect (black outline) QPixmap pixmap( rect.size() + QSize( 10, 10 ) ); pixmap.fill( Qt::black ); QPainter p2(&pixmap); p2.setFont(font()); p2.setPen(Qt::white); p2.setBrush(Qt::white); p2.drawText(QRect( QPoint( 5, 5 ), rect.size() ), m_alignment, m_message); p2.end(); QColor shadowColor; { int h, s, v; palette().color( QPalette::Normal, QPalette::Foreground ).getHsv( &h, &s, &v ); shadowColor = v > 128 ? Qt::black : Qt::white; } p.drawImage(rect.topLeft() - QPoint(5, 5), ShadowEngine::makeShadow(pixmap, shadowColor)); p.setPen( palette().color(QPalette::Active, QPalette::WindowText )); p.drawText(rect, m_alignment, m_message); } void KisFloatingMessage::startFade() { m_fadeTimeLine.setDuration(250); m_fadeTimeLine.setCurveShape(QTimeLine::EaseInCurve); m_fadeTimeLine.setLoopCount(1); m_fadeTimeLine.setFrameRange(0, 0); m_fadeTimeLine.setFrameRange(0, 10); connect(&m_fadeTimeLine, SIGNAL(finished()), SLOT(removeMessage())); connect(&m_fadeTimeLine, SIGNAL(frameChanged(int)), SLOT(updateOpacity(int))); m_fadeTimeLine.start(); } void KisFloatingMessage::removeMessage() { m_timer.stop(); m_fadeTimeLine.stop(); widgetQueuedForDeletion = true; hide(); deleteLater(); } void KisFloatingMessage::updateOpacity(int /*value*/) { setWindowOpacity(OSD_WINDOW_OPACITY - 0.1); } void KisFloatingMessage::widgetDeleted() { widgetQueuedForDeletion = false; } diff --git a/libs/widgets/kis_spinbox_color_selector.cpp b/libs/widgets/kis_spinbox_color_selector.cpp index 250c5cd591..637a505618 100644 --- a/libs/widgets/kis_spinbox_color_selector.cpp +++ b/libs/widgets/kis_spinbox_color_selector.cpp @@ -1,268 +1,287 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * Copyright (C) 2020 L. E. Segovia * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_spinbox_color_selector.h" #include #include #include "kis_double_parse_spin_box.h" #include "kis_int_parse_spin_box.h" #include "kis_signal_compressor.h" #include #ifdef HAVE_OPENEXR #include #endif #include +#include #include #include #include struct KisSpinboxColorSelector::Private { QList labels; QList spinBoxList; QList doubleSpinBoxList; KoColor color; const KoColorSpace *cs {0}; bool chooseAlpha {false}; QFormLayout *layout {0}; }; KisSpinboxColorSelector::KisSpinboxColorSelector(QWidget *parent) : QWidget(parent) , m_d(new Private) { this->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); m_d->layout = new QFormLayout(this); } KisSpinboxColorSelector::~KisSpinboxColorSelector() { } void KisSpinboxColorSelector::slotSetColor(KoColor color) { m_d->color = color; slotSetColorSpace(m_d->color.colorSpace()); updateSpinboxesWithNewValues(); } void KisSpinboxColorSelector::slotSetColorSpace(const KoColorSpace *cs) { if (cs == m_d->cs) { return; } m_d->cs = cs; //remake spinboxes delete m_d->layout; m_d->layout = new QFormLayout(this); Q_FOREACH(QObject *o, m_d->labels) { o->deleteLater(); } Q_FOREACH(QObject *o, m_d->spinBoxList) { o->deleteLater(); } Q_FOREACH(QObject *o, m_d->doubleSpinBoxList) { o->deleteLater(); } m_d->labels.clear(); m_d->spinBoxList.clear(); m_d->doubleSpinBoxList.clear(); QList channels = KoChannelInfo::displayOrderSorted(m_d->cs->channels()); Q_FOREACH (KoChannelInfo* channel, channels) { QString inputLabel = channel->name(); QLabel *inlb = new QLabel(this); m_d->labels << inlb; inlb->setText(inputLabel); switch (channel->channelValueType()) { case KoChannelInfo::UINT8: { KisIntParseSpinBox *input = new KisIntParseSpinBox(this); input->setMinimum(0); input->setMaximum(0xFF); m_d->spinBoxList.append(input); m_d->layout->addRow(inlb, input); connect(input, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateFromSpinBoxes())); if (channel->channelType() == KoChannelInfo::ALPHA && m_d->chooseAlpha == false) { inlb->setVisible(false); input->setVisible(false); input->blockSignals(true); } } break; case KoChannelInfo::UINT16: { KisIntParseSpinBox *input = new KisIntParseSpinBox(this); input->setMinimum(0); input->setMaximum(0xFFFF); m_d->spinBoxList.append(input); m_d->layout->addRow(inlb,input); connect(input, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateFromSpinBoxes())); if (channel->channelType() == KoChannelInfo::ALPHA && m_d->chooseAlpha == false) { inlb->setVisible(false); input->setVisible(false); input->blockSignals(true); } } break; case KoChannelInfo::UINT32: { KisIntParseSpinBox *input = new KisIntParseSpinBox(this); input->setMinimum(0); input->setMaximum(0xFFFFFFFF); m_d->spinBoxList.append(input); m_d->layout->addRow(inlb,input); connect(input, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateFromSpinBoxes())); if (channel->channelType() == KoChannelInfo::ALPHA && m_d->chooseAlpha == false) { inlb->setVisible(false); input->setVisible(false); input->blockSignals(true); } } break; #ifdef HAVE_OPENEXR case KoChannelInfo::FLOAT16: { + half m_uiMin, m_uiMax; + if (cs->colorModelId() == LABAColorModelID || cs->colorModelId() == CMYKAColorModelID) { + m_uiMin = channel->getUIMin(); + m_uiMax = channel->getUIMax(); + } else { + m_uiMin = 0; + m_uiMax = KoColorSpaceMathsTraits::max; + } + KisDoubleParseSpinBox *input = new KisDoubleParseSpinBox(this); - input->setMinimum(channel->getUIMin()); - input->setMaximum(channel->getUIMax()); + input->setMinimum(m_uiMin); + input->setMaximum(m_uiMax); input->setSingleStep(0.1); m_d->doubleSpinBoxList.append(input); m_d->layout->addRow(inlb,input); connect(input, SIGNAL(valueChanged(double)), this, SLOT(slotUpdateFromSpinBoxes())); if (channel->channelType() == KoChannelInfo::ALPHA && m_d->chooseAlpha == false) { inlb->setVisible(false); input->setVisible(false); input->blockSignals(true); } } break; #endif case KoChannelInfo::FLOAT32: { + float m_uiMin, m_uiMax; + if (cs->colorModelId() == LABAColorModelID || cs->colorModelId() == CMYKAColorModelID) { + m_uiMin = channel->getUIMin(); + m_uiMax = channel->getUIMax(); + } else { + m_uiMin = 0; + m_uiMax = KoColorSpaceMathsTraits::max; + } + KisDoubleParseSpinBox *input = new KisDoubleParseSpinBox(this); - input->setMinimum(channel->getUIMin()); - input->setMaximum(channel->getUIMax()); + input->setMinimum(m_uiMin); + input->setMaximum(m_uiMax); input->setSingleStep(0.1); m_d->doubleSpinBoxList.append(input); m_d->layout->addRow(inlb,input); connect(input, SIGNAL(valueChanged(double)), this, SLOT(slotUpdateFromSpinBoxes())); if (channel->channelType() == KoChannelInfo::ALPHA && m_d->chooseAlpha == false) { inlb->setVisible(false); input->setVisible(false); input->blockSignals(true); } } break; default: Q_ASSERT(false); } } this->setLayout(m_d->layout); } void KisSpinboxColorSelector::createColorFromSpinboxValues() { KoColor newColor(m_d->cs); int channelcount = m_d->cs->channelCount(); QVector channelValues(channelcount); channelValues.fill(1.0); QList channels = KoChannelInfo::displayOrderSorted(m_d->cs->channels()); for (int i = 0; i < (int)qAbs(m_d->cs->colorChannelCount()); i++) { int channelposition = KoChannelInfo::displayPositionToChannelIndex(i, m_d->cs->channels()); if (channels.at(i)->channelValueType()==KoChannelInfo::UINT8 && m_d->spinBoxList.at(i)){ int value = m_d->spinBoxList.at(i)->value(); channelValues[channelposition] = KoColorSpaceMaths::scaleToA(value); } else if (channels.at(i)->channelValueType()==KoChannelInfo::UINT16 && m_d->spinBoxList.at(i)){ channelValues[channelposition] = KoColorSpaceMaths::scaleToA(m_d->spinBoxList.at(i)->value()); } else if ((channels.at(i)->channelValueType()==KoChannelInfo::FLOAT16 || channels.at(i)->channelValueType()==KoChannelInfo::FLOAT32 || channels.at(i)->channelValueType()==KoChannelInfo::FLOAT64) && m_d->doubleSpinBoxList.at(i)) { channelValues[channelposition] = m_d->doubleSpinBoxList.at(i)->value(); } } m_d->cs->fromNormalisedChannelsValue(newColor.data(), channelValues); newColor.setOpacity(m_d->color.opacityU8()); m_d->color = newColor; } void KisSpinboxColorSelector::slotUpdateFromSpinBoxes() { createColorFromSpinboxValues(); emit sigNewColor(m_d->color); } void KisSpinboxColorSelector::updateSpinboxesWithNewValues() { int channelcount = m_d->cs->channelCount(); QVector channelValues(channelcount); channelValues.fill(1.0); m_d->cs->normalisedChannelsValue(m_d->color.data(), channelValues); QList channels = KoChannelInfo::displayOrderSorted(m_d->cs->channels()); int i; /*while (QLayoutItem *item = this->layout()->takeAt(0)) { item->widget()->blockSignals(true); }*/ for (i=0; ispinBoxList.size(); i++) { m_d->spinBoxList.at(i)->blockSignals(true); } for (i=0; idoubleSpinBoxList.size(); i++) { m_d->doubleSpinBoxList.at(i)->blockSignals(true); } for (i = 0; i < (int)qAbs(m_d->cs->colorChannelCount()); i++) { int channelposition = KoChannelInfo::displayPositionToChannelIndex(i, m_d->cs->channels()); if (channels.at(i)->channelValueType() == KoChannelInfo::UINT8 && m_d->spinBoxList.at(i)) { int value = KoColorSpaceMaths::scaleToA(channelValues[channelposition]); m_d->spinBoxList.at(i)->setValue(value); } else if (channels.at(i)->channelValueType() == KoChannelInfo::UINT16 && m_d->spinBoxList.at(i)) { m_d->spinBoxList.at(i)->setValue(KoColorSpaceMaths::scaleToA(channelValues[channelposition])); } else if ((channels.at(i)->channelValueType()==KoChannelInfo::FLOAT16 || channels.at(i)->channelValueType()==KoChannelInfo::FLOAT32 || channels.at(i)->channelValueType()==KoChannelInfo::FLOAT64) && m_d->doubleSpinBoxList.at(i)) { float value = channels.at(i)->getUIMin() + channelValues[channelposition] * channels.at(i)->getUIUnitValue(); m_d->doubleSpinBoxList.at(i)->setValue(value); } } for (i=0; ispinBoxList.size(); i++) { m_d->spinBoxList.at(i)->blockSignals(false); } for (i=0; idoubleSpinBoxList.size(); i++) { m_d->doubleSpinBoxList.at(i)->blockSignals(false); } /*while (QLayoutItem *item = this->layout()->takeAt(0)) { item->widget()->blockSignals(false); }*/ } diff --git a/packaging/linux/appimage/build-deps.sh b/packaging/linux/appimage/build-deps.sh index 04d15556a6..4e7ff53125 100755 --- a/packaging/linux/appimage/build-deps.sh +++ b/packaging/linux/appimage/build-deps.sh @@ -1,84 +1,85 @@ #!/bin/bash # # Build all Krita's dependencies on Ubuntu 14.04. # # Prerequisites: cmake git build-essential libxcb-keysyms1-dev plus all deps for Qt5 # # Halt on errors and be verbose about what we are doing set -e set -x # Read in our parameters export BUILD_PREFIX=$1 export KRITA_SOURCES=$2 # qjsonparser, used to add metadata to the plugins needs to work in a en_US.UTF-8 environment. # That's not always the case, so make sure it is export LC_ALL=en_US.UTF-8 export LANG=en_us.UTF-8 # We want to use $prefix/deps/usr/ for all our dependencies export DEPS_INSTALL_PREFIX=$BUILD_PREFIX/deps/usr/ export DOWNLOADS_DIR=$BUILD_PREFIX/downloads/ # Setup variables needed to help everything find what we build export LD_LIBRARY_PATH=$DEPS_INSTALL_PREFIX/lib:$LD_LIBRARY_PATH export PATH=$DEPS_INSTALL_PREFIX/bin:$PATH export PKG_CONFIG_PATH=$DEPS_INSTALL_PREFIX/share/pkgconfig:$DEPS_INSTALL_PREFIX/lib/pkgconfig:/usr/lib/pkgconfig:$PKG_CONFIG_PATH export CMAKE_PREFIX_PATH=$DEPS_INSTALL_PREFIX:$CMAKE_PREFIX_PATH # A krita build layout looks like this: # krita/ -- the source directory # downloads/ -- downloads of the dependencies from files.kde.org # deps-build/ -- build directory for the dependencies # deps/ -- the location for the built dependencies # build/ -- build directory for krita itself # krita.appdir/ -- install directory for krita and the dependencies # Make sure our downloads directory exists if [ ! -d $DOWNLOADS_DIR ] ; then mkdir -p $DOWNLOADS_DIR fi # Make sure our build directory exists if [ ! -d $BUILD_PREFIX/deps-build/ ] ; then mkdir -p $BUILD_PREFIX/deps-build/ fi # The 3rdparty dependency handling in Krita also requires the install directory to be pre-created if [ ! -d $DEPS_INSTALL_PREFIX ] ; then mkdir -p $DEPS_INSTALL_PREFIX fi # Switch to our build directory as we're basically ready to start building... cd $BUILD_PREFIX/deps-build/ # Configure the dependencies for building cmake $KRITA_SOURCES/3rdparty -DCMAKE_INSTALL_PREFIX=$DEPS_INSTALL_PREFIX -DINSTALL_ROOT=$DEPS_INSTALL_PREFIX -DEXTERNALS_DOWNLOAD_DIR=$DOWNLOADS_DIR # Now start building everything we need, in the appropriate order #cmake --build . --config RelWithDebInfo --target ext_png #cmake --build . --config RelWithDebInfo --target ext_tiff #cmake --build . --config RelWithDebInfo --target ext_jpeg cmake --build . --config RelWithDebInfo --target ext_boost cmake --build . --config RelWithDebInfo --target ext_fftw3 cmake --build . --config RelWithDebInfo --target ext_eigen3 +cmake --build . --config RelWithDebInfo --target ext_expat cmake --build . --config RelWithDebInfo --target ext_exiv2 cmake --build . --config RelWithDebInfo --target ext_lcms2 cmake --build . --config RelWithDebInfo --target ext_ocio cmake --build . --config RelWithDebInfo --target ext_openexr cmake --build . --config RelWithDebInfo --target ext_vc cmake --build . --config RelWithDebInfo --target ext_libraw cmake --build . --config RelWithDebInfo --target ext_giflib #cmake --build . --config RelWithDebInfo --target ext_gsl cmake --build . --config RelWithDebInfo --target ext_python #cmake --build . --config RelWithDebInfo --target ext_freetype #cmake --build . --config RelWithDebInfo --target ext_fontconfig cmake --build . --config RelWithDebInfo --target ext_qt cmake --build . --config RelWithDebInfo --target ext_poppler cmake --build . --config RelWithDebInfo --target ext_kcrash cmake --build . --config RelWithDebInfo --target ext_gmic cmake --build . --config RelWithDebInfo --target ext_sip cmake --build . --config RelWithDebInfo --target ext_pyqt cmake --build . --config RelWithDebInfo --target ext_quazip diff --git a/plugins/color/lcms2engine/colorspaces/cmyk_f32/CmykF32ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/cmyk_f32/CmykF32ColorSpace.cpp index 6a55982afe..8f51bf9c02 100644 --- a/plugins/color/lcms2engine/colorspaces/cmyk_f32/CmykF32ColorSpace.cpp +++ b/plugins/color/lcms2engine/colorspaces/cmyk_f32/CmykF32ColorSpace.cpp @@ -1,144 +1,144 @@ /* * Copyright (c) 2006 Cyrille Berger * Copyright (c) 2020 L. E. Segovia * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "CmykF32ColorSpace.h" #include #include #include #include "compositeops/KoCompositeOps.h" #include #include CmykF32ColorSpace::CmykF32ColorSpace(const QString &name, KoColorProfile *p) : LcmsColorSpace(colorSpaceId(), name, TYPE_CMYKA_FLT, cmsSigCmykData, p) { const IccColorProfile *icc_p = dynamic_cast(p); Q_ASSERT(icc_p); QVector uiRanges(icc_p->getFloatUIMinMax()); Q_ASSERT(uiRanges.size() == 4); addChannel(new KoChannelInfo(i18n("Cyan"), 0 * sizeof(float), 0, KoChannelInfo::COLOR, KoChannelInfo::FLOAT32, sizeof(float), Qt::cyan, uiRanges[0])); addChannel(new KoChannelInfo(i18n("Magenta"), 1 * sizeof(float), 1, KoChannelInfo::COLOR, KoChannelInfo::FLOAT32, sizeof(float), Qt::magenta, uiRanges[1])); addChannel(new KoChannelInfo(i18n("Yellow"), 2 * sizeof(float), 2, KoChannelInfo::COLOR, KoChannelInfo::FLOAT32, sizeof(float), Qt::yellow, uiRanges[2])); addChannel(new KoChannelInfo(i18n("Black"), 3 * sizeof(float), 3, KoChannelInfo::COLOR, KoChannelInfo::FLOAT32, sizeof(float), Qt::black, uiRanges[3])); addChannel(new KoChannelInfo(i18n("Alpha"), 4 * sizeof(float), 4, KoChannelInfo::ALPHA, KoChannelInfo::FLOAT32, sizeof(float))); init(); dbgPlugins << "CMYK (float) profile bounds for: " << icc_p->name(); dbgPlugins << "C: " << uiRanges[0].minVal << uiRanges[0].maxVal; dbgPlugins << "M: " << uiRanges[1].minVal << uiRanges[1].maxVal; dbgPlugins << "Y: " << uiRanges[2].minVal << uiRanges[2].maxVal; dbgPlugins << "K: " << uiRanges[3].minVal << uiRanges[3].maxVal; addStandardCompositeOps(this); } bool CmykF32ColorSpace::willDegrade(ColorSpaceIndependence independence) const { if (independence == TO_RGBA16) { return true; } else { return false; } } KoColorSpace *CmykF32ColorSpace::clone() const { return new CmykF32ColorSpace(name(), profile()->clone()); } void CmykF32ColorSpace::colorToXML(const quint8 *pixel, QDomDocument &doc, QDomElement &colorElt) const { const KoCmykF32Traits::Pixel *p = reinterpret_cast(pixel); QDomElement labElt = doc.createElement("CMYK"); // XML expects 0-1, we need 0-100 // Get the bounds from the channels and adjust the calculations labElt.setAttribute("c", KisDomUtils::toString(KoColorSpaceMaths< KoCmykF32Traits::channels_type, qreal>::scaleToA(1.f / this->channels()[0]->getUIUnitValue() * (p->cyan - this->channels()[0]->getUIMin())))); labElt.setAttribute("m", KisDomUtils::toString(KoColorSpaceMaths< KoCmykF32Traits::channels_type, qreal>::scaleToA(1.f / this->channels()[1]->getUIUnitValue() * (p->magenta - this->channels()[1]->getUIMin())))); labElt.setAttribute("y", KisDomUtils::toString(KoColorSpaceMaths< KoCmykF32Traits::channels_type, qreal>::scaleToA(1.f / this->channels()[2]->getUIUnitValue() * (p->yellow - this->channels()[2]->getUIMin())))); labElt.setAttribute("k", KisDomUtils::toString(KoColorSpaceMaths< KoCmykF32Traits::channels_type, qreal>::scaleToA(1.f / this->channels()[3]->getUIUnitValue() * (p->black - this->channels()[3]->getUIMin())))); labElt.setAttribute("space", profile()->name()); colorElt.appendChild(labElt); } void CmykF32ColorSpace::colorFromXML(quint8 *pixel, const QDomElement &elt) const { KoCmykF32Traits::Pixel *p = reinterpret_cast(pixel); p->cyan = this->channels()[0]->getUIMin() + KoColorSpaceMaths< qreal, KoCmykF32Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("c"))) * this->channels()[0]->getUIUnitValue(); p->magenta = this->channels()[1]->getUIMin() + KoColorSpaceMaths< qreal, KoCmykF32Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("m"))) * this->channels()[1]->getUIUnitValue(); p->yellow = this->channels()[2]->getUIMin() + KoColorSpaceMaths< qreal, KoCmykF32Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("y"))) * this->channels()[2]->getUIUnitValue(); - p->black = this->channels()[2]->getUIMin() + KoColorSpaceMaths< qreal, KoCmykF32Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("k"))) * this->channels()[2]->getUIUnitValue(); + p->black = this->channels()[3]->getUIMin() + KoColorSpaceMaths< qreal, KoCmykF32Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("k"))) * this->channels()[3]->getUIUnitValue(); p->alpha = 1.0; } void CmykF32ColorSpace::toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const { qreal c0 = channelValues[0]; qreal c1 = channelValues[1]; qreal c2 = channelValues[2]; qreal c3 = channelValues[3]; //we use HSI here because we can't linearise CMYK, and HSY doesn't work right with... CMYKToCMY(&c0, &c1, &c2, &c3); c0 = 1.0 - c0; c1 = 1.0 - c1; c2 = 1.0 - c2; RGBToHSI(c0, c1, c2, hue, sat, luma); } QVector CmykF32ColorSpace::fromHSY(qreal *hue, qreal *sat, qreal *luma) const { QVector channelValues(5); channelValues.fill(1.0); HSIToRGB(*hue, *sat, *luma, &channelValues[0],&channelValues[1],&channelValues[2]); channelValues[0] = qBound(0.0,1.0-channelValues[0],1.0); channelValues[1] = qBound(0.0,1.0-channelValues[1],1.0); channelValues[2] = qBound(0.0,1.0-channelValues[2],1.0); CMYToCMYK(&channelValues[0],&channelValues[1],&channelValues[2],&channelValues[3]); return channelValues; } void CmykF32ColorSpace::toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const { qreal c0 = channelValues[0]; qreal c1 = channelValues[1]; qreal c2 = channelValues[2]; qreal c3 = channelValues[3]; CMYKToCMY(&c0, &c1, &c2, &c3); c0 = 1.0 - c0; c1 = 1.0 - c1; c2 = 1.0 - c2; RGBToYUV(c0, c1, c2, y, u, v, (1.0 - 0.299),(1.0 - 0.587), (1.0 - 0.114)); } QVector CmykF32ColorSpace::fromYUV(qreal *y, qreal *u, qreal *v) const { QVector channelValues(5); channelValues.fill(1.0); YUVToRGB(*y, *u, *v, &channelValues[0],&channelValues[1],&channelValues[2], 0.33, 0.33, 0.33); channelValues[0] = qBound(0.0,1.0-channelValues[0],1.0); channelValues[1] = qBound(0.0,1.0-channelValues[1],1.0); channelValues[2] = qBound(0.0,1.0-channelValues[2],1.0); CMYToCMYK(&channelValues[0],&channelValues[1],&channelValues[2],&channelValues[3]); return channelValues; } diff --git a/plugins/dockers/advancedcolorselector/kis_shade_selector_line_combo_box.cpp b/plugins/dockers/advancedcolorselector/kis_shade_selector_line_combo_box.cpp index 993dfa2ef2..4332469cf7 100644 --- a/plugins/dockers/advancedcolorselector/kis_shade_selector_line_combo_box.cpp +++ b/plugins/dockers/advancedcolorselector/kis_shade_selector_line_combo_box.cpp @@ -1,175 +1,179 @@ /* * Copyright (c) 2010 Adam Celarek * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_shade_selector_line_combo_box.h" #include #include #include #include #include #include "kis_shade_selector_line.h" #include "kis_shade_selector_line_combo_box_popup.h" #include "kis_color_selector_base_proxy.h" #include "kis_global.h" KisShadeSelectorLineComboBox::KisShadeSelectorLineComboBox(QWidget *parent) : QComboBox(parent), m_popup(new KisShadeSelectorLineComboBoxPopup(this)), m_parentProxy(new KisColorSelectorBaseProxyNoop()), m_currentLine(new KisShadeSelectorLine(0,0,0, m_parentProxy.data(), this)) { QGridLayout* l = new QGridLayout(this); l->addWidget(m_currentLine); m_currentLine->setEnabled(false); KoColor color; color.fromQColor(QColor(190, 50, 50)); m_currentLine->setColor(color); updateSettings(); } KisShadeSelectorLineComboBox::~KisShadeSelectorLineComboBox() { } void KisShadeSelectorLineComboBox::hidePopup() { QComboBox::hidePopup(); m_popup->hide(); } void KisShadeSelectorLineComboBox::showPopup() { QComboBox::showPopup(); m_popup->show(); const int widgetMargin = 20; +#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) QScreen *screen = qApp->screenAt(this->geometry().topLeft()); const QRect fitRect = kisGrowRect(screen->availableGeometry(), -widgetMargin); +#else + const QRect fitRect = kisGrowRect(QApplication::desktop()->screenGeometry(), -widgetMargin); +#endif QRect popupRect = m_popup->rect(); popupRect.moveTo(mapToGlobal(QPoint())); popupRect = kisEnsureInRect(popupRect, fitRect); m_popup->move(popupRect.topLeft()); m_popup->setConfiguration(m_currentLine->toString()); } void KisShadeSelectorLineComboBox::setConfiguration(const QString &stri) { m_currentLine->fromString(stri); update(); } QString KisShadeSelectorLineComboBox::configuration() const { return m_currentLine->toString(); } void KisShadeSelectorLineComboBox::setLineNumber(int n) { m_currentLine->setLineNumber(n); for(int i=0; ilayout()->count(); i++) { KisShadeSelectorLine* item = dynamic_cast(m_popup->layout()->itemAt(i)->widget()); if(item!=0) { item->setLineNumber(n); } } } void KisShadeSelectorLineComboBox::resizeEvent(QResizeEvent *e) { Q_UNUSED(e); m_currentLine->setMaximumWidth(width()-30-m_popup->spacing); m_popup->setMinimumWidth(qMax(280, width())); m_popup->setMaximumWidth(qMax(280, width())); } void KisShadeSelectorLineComboBox::updateSettings() { m_currentLine->updateSettings(); for(int i=0; ilayout()->count(); i++) { KisShadeSelectorLine* item = dynamic_cast(m_popup->layout()->itemAt(i)->widget()); if(item!=0) { item->updateSettings(); item->m_lineHeight=30; item->setMaximumHeight(30); item->setMinimumHeight(30); } } setLineHeight(m_currentLine->m_lineHeight); } void KisShadeSelectorLineComboBox::setGradient(bool b) { m_currentLine->m_gradient=b; for(int i=0; ilayout()->count(); i++) { KisShadeSelectorLine* item = dynamic_cast(m_popup->layout()->itemAt(i)->widget()); if(item!=0) { item->m_gradient=b; } } update(); } void KisShadeSelectorLineComboBox::setPatches(bool b) { m_currentLine->m_gradient=!b; for(int i=0; ilayout()->count(); i++) { KisShadeSelectorLine* item = dynamic_cast(m_popup->layout()->itemAt(i)->widget()); if(item!=0) { item->m_gradient=!b; } } update(); } void KisShadeSelectorLineComboBox::setPatchCount(int count) { m_currentLine->m_patchCount=count; for(int i=0; ilayout()->count(); i++) { KisShadeSelectorLine* item = dynamic_cast(m_popup->layout()->itemAt(i)->widget()); if(item!=0) { item->m_patchCount=count; } } update(); } void KisShadeSelectorLineComboBox::setLineHeight(int height) { m_currentLine->m_lineHeight=height; m_currentLine->setMinimumHeight(height); m_currentLine->setMaximumHeight(height); setMinimumHeight(height+m_popup->spacing); setMaximumHeight(height+m_popup->spacing); update(); } diff --git a/plugins/extensions/layersplit/layersplit.cpp b/plugins/extensions/layersplit/layersplit.cpp index af8370cf8e..fd30fea338 100644 --- a/plugins/extensions/layersplit/layersplit.cpp +++ b/plugins/extensions/layersplit/layersplit.cpp @@ -1,226 +1,226 @@ /* * Copyright (C) 2014 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "layersplit.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dlg_layersplit.h" #include "kis_node_manager.h" #include "kis_node_commands_adapter.h" #include "kis_undo_adapter.h" +#include #include #include K_PLUGIN_FACTORY_WITH_JSON(LayerSplitFactory, "kritalayersplit.json", registerPlugin();) LayerSplit::LayerSplit(QObject *parent, const QVariantList &) : KisActionPlugin(parent) { KisAction *action = createAction("layersplit"); connect(action, SIGNAL(triggered()), this, SLOT(slotLayerSplit())); } LayerSplit::~LayerSplit() { } struct Layer { KoColor color; KisPaintDeviceSP device; KisRandomAccessorSP accessor; int pixelsWritten; bool operator<(const Layer& other) const { return pixelsWritten < other.pixelsWritten; } }; void LayerSplit::slotLayerSplit() { DlgLayerSplit dlg; if (dlg.exec() == QDialog::Accepted) { dlg.hide(); QApplication::setOverrideCursor(Qt::WaitCursor); QPointer updater = viewManager()->createUnthreadedUpdater(i18n("Split into Layers")); KisImageSP image = viewManager()->image(); if (!image) return; - image->lock(); + KisImageBarrierLocker locker(image); KisNodeSP node = viewManager()->activeNode(); if (!node) return; KisPaintDeviceSP projection = node->projection(); if (!projection) return; QList colorMap; const KoColorSpace *cs = projection->colorSpace(); QRect rc = image->bounds(); int fuzziness = dlg.fuzziness(); updater->setProgress(0); KisRandomConstAccessorSP acc = projection->createRandomConstAccessorNG(rc.x(), rc.y()); for (int row = rc.y(); row < rc.height(); ++row) { for (int col = rc.x(); col < rc.width(); ++col) { acc->moveTo(col, row); KoColor c(cs); c.setColor(acc->rawDataConst(), cs); if (c.opacityU8() == OPACITY_TRANSPARENT_U8) { continue; } if (dlg.disregardOpacity()) { c.setOpacity(OPACITY_OPAQUE_U8); } bool found = false; Q_FOREACH (const Layer &l, colorMap) { if (fuzziness == 0) { found = (l.color == c); } else { quint8 match = cs->difference(l.color.data(), c.data()); found = (match <= fuzziness); } if (found) { KisRandomAccessorSP dstAcc = l.accessor; dstAcc->moveTo(col, row); memcpy(dstAcc->rawData(), acc->rawDataConst(), cs->pixelSize()); const_cast(&l)->pixelsWritten++; break; } } if (!found) { QString name = ""; if (dlg.palette()) { name = dlg.palette()->getClosestColorInfo(c).swatch.name(); } if (name.toLower() == "untitled" || name.toLower() == "none" || name.toLower() == "") { name = KoColor::toQString(c); } Layer l; l.color = c; l.device = new KisPaintDevice(cs, name); l.accessor = l.device->createRandomAccessorNG(col, row); l.accessor->moveTo(col, row); memcpy(l.accessor->rawData(), acc->rawDataConst(), cs->pixelSize()); l.pixelsWritten = 1; colorMap << l; } } if (updater->interrupted()) { return; } updater->setProgress((row - rc.y()) * 100 / rc.height() - rc.y()); } updater->setProgress(100); dbgKrita << "Created" << colorMap.size() << "layers"; // Q_FOREACH (const Layer &l, colorMap) { // dbgKrita << "\t" << l.device->objectName() << ":" << l.pixelsWritten; // } if (dlg.sortLayers()) { std::sort(colorMap.begin(), colorMap.end()); } KisUndoAdapter *undo = image->undoAdapter(); undo->beginMacro(kundo2_i18n("Split Layer")); KisNodeCommandsAdapter adapter(viewManager()); KisGroupLayerSP baseGroup = dynamic_cast(node->parent().data()); if (!baseGroup) { // Masks are never nested baseGroup = dynamic_cast(node->parent()->parent().data()); } if (dlg.hideOriginal()) { node->setVisible(false); } if (dlg.createBaseGroup()) { KisGroupLayerSP grp = new KisGroupLayer(image, i18n("Color"), OPACITY_OPAQUE_U8); adapter.addNode(grp, baseGroup, 1); baseGroup = grp; } Q_FOREACH (const Layer &l, colorMap) { KisGroupLayerSP grp = baseGroup; if (dlg.createSeparateGroups()) { grp = new KisGroupLayer(image, l.device->objectName(), OPACITY_OPAQUE_U8); adapter.addNode(grp, baseGroup, 1); } KisPaintLayerSP paintLayer = new KisPaintLayer(image, l.device->objectName(), OPACITY_OPAQUE_U8, l.device); adapter.addNode(paintLayer, grp, 0); paintLayer->setAlphaLocked(dlg.lockAlpha()); } undo->endMacro(); - image->unlock(); image->setModified(); } QApplication::restoreOverrideCursor(); } #include "layersplit.moc" diff --git a/plugins/extensions/qmic/QMic.cpp b/plugins/extensions/qmic/QMic.cpp index 83f27d3490..6b28f0436f 100644 --- a/plugins/extensions/qmic/QMic.cpp +++ b/plugins/extensions/qmic/QMic.cpp @@ -1,463 +1,461 @@ /* * Copyright (c) 2017 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "QMic.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 #include #include #include #include #include #include #include #include #include "kis_input_output_mapper.h" #include "kis_qmic_simple_convertor.h" #include "kis_import_qmic_processing_visitor.h" #include +#include #include "kis_qmic_applicator.h" static const char ack[] = "ack"; K_PLUGIN_FACTORY_WITH_JSON(QMicFactory, "kritaqmic.json", registerPlugin();) QMic::QMic(QObject *parent, const QVariantList &) : KisActionPlugin(parent) , m_gmicApplicator(0) { #ifndef Q_OS_MAC KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance(); PluginSettingsFactory* settingsFactory = new PluginSettingsFactory(); preferenceSetRegistry->add("QMicPluginSettingsFactory", settingsFactory); m_qmicAction = createAction("QMic"); m_qmicAction->setActivationFlags(KisAction::ACTIVE_DEVICE); connect(m_qmicAction , SIGNAL(triggered()), this, SLOT(slotQMic())); m_againAction = createAction("QMicAgain"); m_againAction->setActivationFlags(KisAction::ACTIVE_DEVICE); m_againAction->setEnabled(false); connect(m_againAction, SIGNAL(triggered()), this, SLOT(slotQMicAgain())); m_gmicApplicator = new KisQmicApplicator(); connect(m_gmicApplicator, SIGNAL(gmicFinished(bool,int,QString)), this, SLOT(slotGmicFinished(bool,int,QString))); #endif } QMic::~QMic() { Q_FOREACH(QSharedMemory *memorySegment, m_sharedMemorySegments) { // dbgPlugins << "detaching" << memorySegment->key(); memorySegment->detach(); } qDeleteAll(m_sharedMemorySegments); m_sharedMemorySegments.clear(); if (m_pluginProcess) { m_pluginProcess->close(); } delete m_gmicApplicator; delete m_localServer; } void QMic::slotQMicAgain() { slotQMic(true); } void QMic::slotQMic(bool again) { m_qmicAction->setEnabled(false); m_againAction->setEnabled(false); // find the krita-gmic-qt plugin QString pluginPath = PluginSettings::gmicQtPath(); if (pluginPath.isEmpty() || !QFileInfo(pluginPath).exists() || !QFileInfo(pluginPath).isFile()) { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Krita cannot find the gmic-qt plugin. You can set the location of the gmic-qt plugin in Settings/Configure Krita.")); return; } m_key = QUuid::createUuid().toString(); m_localServer = new QLocalServer(); m_localServer->listen(m_key); connect(m_localServer, SIGNAL(newConnection()), SLOT(connected())); m_pluginProcess = new QProcess(this); connect(viewManager(), SIGNAL(destroyed(QObject*)), m_pluginProcess, SLOT(terminate())); m_pluginProcess->setProcessChannelMode(QProcess::ForwardedChannels); connect(m_pluginProcess, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(pluginFinished(int,QProcess::ExitStatus))); connect(m_pluginProcess, SIGNAL(stateChanged(QProcess::ProcessState)), this, SLOT(pluginStateChanged(QProcess::ProcessState))); m_pluginProcess->start(pluginPath, QStringList() << m_key << (again ? QString(" reapply") : QString())); bool r = m_pluginProcess->waitForStarted(); while (m_pluginProcess->waitForFinished(10)) { qApp->processEvents(QEventLoop::ExcludeUserInputEvents); } dbgPlugins << "Plugin started" << r << m_pluginProcess->state(); } void QMic::connected() { dbgPlugins << "connected"; if (!viewManager()) return; QLocalSocket *socket = m_localServer->nextPendingConnection(); if (!socket) { return; } while (socket->bytesAvailable() < static_cast(sizeof(quint32))) { if (!socket->isValid()) { // stale request return; } socket->waitForReadyRead(1000); } QDataStream ds(socket); QByteArray msg; quint32 remaining; ds >> remaining; msg.resize(remaining); int got = 0; char* uMsgBuf = msg.data(); // FIXME: Should use read transaction for Qt >= 5.7: // https://doc.qt.io/qt-5/qdatastream.html#using-read-transactions do { got = ds.readRawData(uMsgBuf, remaining); remaining -= got; uMsgBuf += got; } while (remaining && got >= 0 && socket->waitForReadyRead(2000)); if (got < 0) { qWarning() << "Message reception failed" << socket->errorString(); delete socket; m_localServer->close(); delete m_localServer; m_localServer = 0; return; } QString message = QString::fromUtf8(msg); dbgPlugins << "Received" << message; // Check the message: we can get three different ones QMultiMap messageMap; Q_FOREACH(QString line, message.split('\n', QString::SkipEmptyParts)) { QList kv = line.split('=', QString::SkipEmptyParts); if (kv.size() == 2) { messageMap.insert(kv[0], kv[1]); } else { qWarning() << "line" << line << "is invalid."; } } if (!messageMap.contains("command")) { qWarning() << "Message did not contain a command"; return; } int mode = 0; if (messageMap.contains("mode")) { mode = messageMap.values("mode").first().toInt(); } QByteArray ba; QString messageBoxWarningText; if (messageMap.values("command").first() == "gmic_qt_get_image_size") { KisSelectionSP selection = viewManager()->image()->globalSelection(); if (selection) { QRect selectionRect = selection->selectedExactRect(); ba = QByteArray::number(selectionRect.width()) + "," + QByteArray::number(selectionRect.height()); } else { ba = QByteArray::number(viewManager()->image()->width()) + "," + QByteArray::number(viewManager()->image()->height()); } } else if (messageMap.values("command").first() == "gmic_qt_get_cropped_images") { // Parse the message, create the shared memory segments, and create a new message to send back and waid for ack QRectF cropRect(0.0, 0.0, 1.0, 1.0); if (!messageMap.contains("croprect") || messageMap.values("croprect").first().split(',', QString::SkipEmptyParts).size() != 4) { qWarning() << "gmic-qt didn't send a croprect or not a valid croprect"; } else { QStringList cr = messageMap.values("croprect").first().split(',', QString::SkipEmptyParts); cropRect.setX(cr[0].toFloat()); cropRect.setY(cr[1].toFloat()); cropRect.setWidth(cr[2].toFloat()); cropRect.setHeight(cr[3].toFloat()); } if (!prepareCroppedImages(&ba, cropRect, mode)) { qWarning() << "Failed to prepare images for gmic-qt"; } } else if (messageMap.values("command").first() == "gmic_qt_output_images") { // Parse the message. read the shared memory segments, fix up the current image and send an ack dbgPlugins << "gmic_qt_output_images"; QStringList layers = messageMap.values("layer"); m_outputMode = (OutputMode)mode; if (m_outputMode != IN_PLACE) { messageBoxWarningText = i18n("Sorry, this output mode is not implemented yet."); m_outputMode = IN_PLACE; } slotStartApplicator(layers); } else if (messageMap.values("command").first() == "gmic_qt_detach") { Q_FOREACH(QSharedMemory *memorySegment, m_sharedMemorySegments) { dbgPlugins << "detaching" << memorySegment->key() << memorySegment->isAttached(); if (memorySegment->isAttached()) { if (!memorySegment->detach()) { dbgPlugins << "\t" << memorySegment->error() << memorySegment->errorString(); } } } qDeleteAll(m_sharedMemorySegments); m_sharedMemorySegments.clear(); } else { qWarning() << "Received unknown command" << messageMap.values("command"); } dbgPlugins << "Sending" << QString::fromUtf8(ba); // HACK: Make sure QDataStream does not refuse to write! // Proper fix: Change the above read to use read transaction ds.resetStatus(); ds.writeBytes(ba.constData(), ba.length()); // Flush the socket because we might not return to the event loop! if (!socket->waitForBytesWritten(2000)) { qWarning() << "Failed to write response:" << socket->error(); } // Wait for the ack bool r = true; r &= socket->waitForReadyRead(2000); // wait for ack r &= (socket->read(qstrlen(ack)) == ack); if (!socket->waitForDisconnected(2000)) { qWarning() << "Remote not disconnected:" << socket->error(); // Wait again socket->disconnectFromServer(); if (socket->waitForDisconnected(2000)) { qWarning() << "Disconnect timed out:" << socket->error(); } } if (!messageBoxWarningText.isEmpty()) { // Defer the message box to the event loop QTimer::singleShot(0, [messageBoxWarningText]() { QMessageBox::warning(KisPart::instance()->currentMainwindow(), i18nc("@title:window", "Krita"), messageBoxWarningText); }); } } void QMic::pluginStateChanged(QProcess::ProcessState state) { dbgPlugins << "stateChanged" << state; } void QMic::pluginFinished(int exitCode, QProcess::ExitStatus exitStatus) { dbgPlugins << "pluginFinished" << exitCode << exitStatus; delete m_pluginProcess; m_pluginProcess = 0; delete m_localServer; m_localServer = 0; m_qmicAction->setEnabled(true); m_againAction->setEnabled(true); } void QMic::slotGmicFinished(bool successfully, int milliseconds, const QString &msg) { dbgPlugins << "slotGmicFinished();" << successfully << milliseconds << msg; if (successfully) { m_gmicApplicator->finish(); } else { m_gmicApplicator->cancel(); QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("G'Mic failed, reason:") + msg); } } void QMic::slotStartApplicator(QStringList gmicImages) { dbgPlugins << "slotStartApplicator();" << gmicImages; if (!viewManager()) return; // Create a vector of gmic images QVector *> images; Q_FOREACH(const QString &image, gmicImages) { QStringList parts = image.split(',', QString::SkipEmptyParts); KIS_SAFE_ASSERT_RECOVER_BREAK(parts.size() == 5); QString key = parts[0]; QString layerName = QByteArray::fromHex(parts[1].toLatin1()); int spectrum = parts[2].toInt(); int width = parts[3].toInt(); int height = parts[4].toInt(); dbgPlugins << key << layerName << width << height; QSharedMemory m(key); if (!m.attach(QSharedMemory::ReadOnly)) { qWarning() << "Could not attach to shared memory area." << m.error() << m.errorString(); } if (m.isAttached()) { if (!m.lock()) { dbgPlugins << "Could not lock memory segment" << m.error() << m.errorString(); } dbgPlugins << "Memory segment" << key << m.size() << m.constData() << m.data(); gmic_image *gimg = new gmic_image(); gimg->assign(width, height, 1, spectrum); gimg->name = layerName; gimg->_data = new float[width * height * spectrum * sizeof(float)]; dbgPlugins << "width" << width << "height" << height << "size" << width * height * spectrum * sizeof(float) << "shared memory size" << m.size(); memcpy(gimg->_data, m.constData(), width * height * spectrum * sizeof(float)); dbgPlugins << "created gmic image" << gimg->name << gimg->_width << gimg->_height; if (!m.unlock()) { dbgPlugins << "Could not unlock memory segment" << m.error() << m.errorString(); } if (!m.detach()) { dbgPlugins << "Could not detach from memory segment" << m.error() << m.errorString(); } images.append(gimg); } } dbgPlugins << "Got" << images.size() << "gmic images"; // Start the applicator KUndo2MagicString actionName = kundo2_i18n("Gmic filter"); KisNodeSP rootNode = viewManager()->image()->root(); KisInputOutputMapper mapper(viewManager()->image(), viewManager()->activeNode()); KisNodeListSP layers = mapper.inputNodes(m_inputMode); m_gmicApplicator->setProperties(viewManager()->image(), rootNode, images, actionName, layers); m_gmicApplicator->apply(); } bool QMic::prepareCroppedImages(QByteArray *message, QRectF &rc, int inputMode) { if (!viewManager()) return false; - viewManager()->image()->lock(); + KisImageBarrierLocker locker(viewManager()->image()); m_inputMode = (InputLayerMode)inputMode; dbgPlugins << "prepareCroppedImages()" << QString::fromUtf8(*message) << rc << inputMode; KisInputOutputMapper mapper(viewManager()->image(), viewManager()->activeNode()); KisNodeListSP nodes = mapper.inputNodes(m_inputMode); if (nodes->isEmpty()) { - viewManager()->image()->unlock(); return false; } for (int i = 0; i < nodes->size(); ++i) { KisNodeSP node = nodes->at(i); if (node && node->paintDevice()) { QRect cropRect; KisSelectionSP selection = viewManager()->image()->globalSelection(); if (selection) { cropRect = selection->selectedExactRect(); } else { cropRect = viewManager()->image()->bounds(); } dbgPlugins << "Converting node" << node->name() << cropRect; const QRectF mappedRect = KisAlgebra2D::mapToRect(cropRect).mapRect(rc); const QRect resultRect = mappedRect.toAlignedRect(); QSharedMemory *m = new QSharedMemory(QString("key_%1").arg(QUuid::createUuid().toString())); m_sharedMemorySegments.append(m); if (!m->create(resultRect.width() * resultRect.height() * 4 * sizeof(float))) { //buf.size())) { qWarning() << "Could not create shared memory segment" << m->error() << m->errorString(); return false; } m->lock(); gmic_image img; img.assign(resultRect.width(), resultRect.height(), 1, 4); img._data = reinterpret_cast(m->data()); KisQmicSimpleConvertor::convertToGmicImageFast(node->paintDevice(), &img, resultRect); message->append(m->key().toUtf8()); m->unlock(); message->append(","); message->append(node->name().toUtf8().toHex()); message->append(","); message->append(QByteArray::number(resultRect.width())); message->append(","); message->append(QByteArray::number(resultRect.height())); message->append("\n"); } } dbgPlugins << QString::fromUtf8(*message); - viewManager()->image()->unlock(); - return true; } #include "QMic.moc" diff --git a/plugins/extensions/separate_channels/kis_channel_separator.cc b/plugins/extensions/separate_channels/kis_channel_separator.cc index cd1a5677a6..208a3b6cdf 100644 --- a/plugins/extensions/separate_channels/kis_channel_separator.cc +++ b/plugins/extensions/separate_channels/kis_channel_separator.cc @@ -1,237 +1,237 @@ /* * This file is part of Krita * * Copyright (c) 2005 Michael Thaler * * ported from Gimp, Copyright (C) 1997 Eiichi Takamori * original pixelize.c for GIMP 0.54 by Tracy Scott * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_channel_separator.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 "kis_iterator_ng.h" #include #include #include #include #include #include +#include "kis_image_barrier_locker.h" KisChannelSeparator::KisChannelSeparator(KisViewManager * view) : m_viewManager(view) { } void KisChannelSeparator::separate(KoUpdater * progressUpdater, enumSepAlphaOptions alphaOps, enumSepSource sourceOps, bool downscale, bool toColor, bool activateCurrentChannel) { KisImageSP image = m_viewManager->image(); if (!image) return; KisPaintDeviceSP src; // Use the flattened image, if required switch (sourceOps) { case ALL_LAYERS: // the content will be locked later src = image->projection(); break; case CURRENT_LAYER: src = m_viewManager->activeDevice(); break; default: break; } if (!src) return; progressUpdater->setProgress(1); const KoColorSpace * dstCs = 0; quint32 numberOfChannels = src->channelCount(); const KoColorSpace * srcCs = src->colorSpace(); QList channels = srcCs->channels(); vKisPaintDeviceSP paintDevices; QRect rect = src->exactBounds(); - image->lock(); + KisImageBarrierLocker locker(image); int i = 0; for (QList::const_iterator it = channels.constBegin(); it != channels.constEnd(); ++it) { KoChannelInfo *ch = (*it); if (ch->channelType() == KoChannelInfo::ALPHA && alphaOps != CREATE_ALPHA_SEPARATION) { continue; } qint32 channelSize = ch->size(); qint32 channelPos = ch->pos(); qint32 destSize = 1; KisPaintDeviceSP dev; if (toColor) { // We don't downscale if we separate to color channels dev = new KisPaintDevice(srcCs); } else { if (channelSize == 1 || downscale) { dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), 0)); } else { dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer16BitsColorDepthID.id(), 0)); destSize = 2; } } dstCs = dev->colorSpace(); paintDevices.push_back(dev); KisHLineConstIteratorSP srcIt = src->createHLineConstIteratorNG(rect.x(), rect.y(), rect.width()); KisHLineIteratorSP dstIt = dev->createHLineIteratorNG(rect.x(), rect.y(), rect.width()); for (qint32 row = 0; row < rect.height(); ++row) { do { if (toColor) { dstCs->singleChannelPixel(dstIt->rawData(), srcIt->oldRawData(), channelPos); if (alphaOps == COPY_ALPHA_TO_SEPARATIONS) { //dstCs->setAlpha(dstIt->rawData(), srcIt->oldRawData()[srcAlphaPos], 1); dstCs->setOpacity(dstIt->rawData(), srcCs->opacityU8(srcIt->oldRawData()), 1); } else { dstCs->setOpacity(dstIt->rawData(), OPACITY_OPAQUE_U8, 1); } } else { // To grayscale // Decide whether we need downscaling if (channelSize == 1 && destSize == 1) { // Both 8-bit channels dstIt->rawData()[0] = srcIt->oldRawData()[channelPos]; if (alphaOps == COPY_ALPHA_TO_SEPARATIONS) { dstCs->setOpacity(dstIt->rawData(), srcCs->opacityU8(srcIt->oldRawData()), 1); } else { dstCs->setOpacity(dstIt->rawData(), OPACITY_OPAQUE_U8, 1); } } else if (channelSize == 2 && destSize == 2) { // Both 16-bit dstIt->rawData()[0] = srcIt->oldRawData()[channelPos]; dstIt->rawData()[1] = srcIt->oldRawData()[channelPos + 1]; if (alphaOps == COPY_ALPHA_TO_SEPARATIONS) { dstCs->setOpacity(dstIt->rawData(), srcCs->opacityU8(srcIt->oldRawData()), 1); } else { dstCs->setOpacity(dstIt->rawData(), OPACITY_OPAQUE_U8, 1); } } else if (channelSize != 1 && destSize == 1) { // Downscale memset(dstIt->rawData(), srcCs->scaleToU8(srcIt->oldRawData(), channelPos), 1); // XXX: Do alpha dstCs->setOpacity(dstIt->rawData(), OPACITY_OPAQUE_U8, 1); } else if (channelSize != 2 && destSize == 2) { // Upscale dstIt->rawData()[0] = srcCs->scaleToU8(srcIt->oldRawData(), channelPos); // XXX: Do alpha dstCs->setOpacity(dstIt->rawData(), OPACITY_OPAQUE_U8, 1); } } } while (dstIt->nextPixel() && srcIt->nextPixel()); dstIt->nextRow(); srcIt->nextRow(); } ++i; progressUpdater->setProgress((i * 100) / numberOfChannels); if (progressUpdater->interrupted()) { break; } } vKisPaintDeviceSP::const_iterator paintDeviceIterator = paintDevices.cbegin(); if (!progressUpdater->interrupted()) { KisNodeCommandsAdapter adapter(m_viewManager); adapter.beginMacro(kundo2_i18n("Separate Image")); for (QList::const_iterator it = channels.constBegin(); it != channels.constEnd(); ++it) { KoChannelInfo *ch = (*it); if (ch->channelType() == KoChannelInfo::ALPHA && alphaOps != CREATE_ALPHA_SEPARATION) { // Don't make an separate separation of the alpha channel if the user didn't ask for it. continue; } KisPaintLayerSP l = new KisPaintLayer(image, ch->name(), OPACITY_OPAQUE_U8, *paintDeviceIterator); if (toColor && activateCurrentChannel) { QBitArray channelFlags(channels.count()); int i = 0; for (QList::const_iterator it2 = channels.constBegin(); it2 != channels.constEnd(); ++it2) { channelFlags.setBit(i, (it == it2)); i++; } l->setChannelFlags(channelFlags); } adapter.addNode(l.data(), image->rootLayer(), 0); ++paintDeviceIterator; } adapter.endMacro(); image->setModified(); } - image->unlock(); progressUpdater->setProgress(100); } diff --git a/plugins/impex/csv/csv_loader.cpp b/plugins/impex/csv/csv_loader.cpp index 7ebf6d03fb..762b89691a 100644 --- a/plugins/impex/csv/csv_loader.cpp +++ b/plugins/impex/csv/csv_loader.cpp @@ -1,488 +1,488 @@ /* * Copyright (c) 2016 Laszlo Fazekas * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "csv_loader.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "csv_read_line.h" #include "csv_layer_record.h" CSVLoader::CSVLoader(KisDocument *doc, bool batchMode) : m_image(0) , m_doc(doc) , m_batchMode(batchMode) , m_stop(false) { } CSVLoader::~CSVLoader() { } KisImportExportErrorCode CSVLoader::decode(QIODevice *io, const QString &filename) { QString field; int idx; int frame = 0; QString projName; int width = 0; int height = 0; int frameCount = 1; float framerate = 24.0; float pixelRatio = 1.0; int projNameIdx = -1; int widthIdx = -1; int heightIdx = -1; int frameCountIdx = -1; int framerateIdx = -1; int pixelRatioIdx = -1; QVector layers; QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); idx = filename.lastIndexOf(QRegExp("[\\/]")); QString base = (idx == -1) ? QString() : filename.left(idx + 1); //include separator QString path = filename; if (path.right(4).toUpper() == ".CSV") path = path.left(path.size() - 4); //according to the QT docs, the slash is a universal directory separator path.append(".frames/"); KisImportExportErrorCode retval = ImportExportCodes::OK; dbgFile << "pos:" << io->pos(); CSVReadLine readLine; QScopedPointer importDoc(KisPart::instance()->createDocument()); importDoc->setInfiniteAutoSaveInterval(); importDoc->setFileBatchMode(true); KisView *setView(0); if (!m_batchMode) { // TODO: use other systems of progress reporting (KisViewManager::createUnthreadedUpdater() // //show the statusbar message even if no view // Q_FOREACH (KisView* view, KisPart::instance()->views()) { // if (view && view->document() == m_doc) { // setView = view; // break; // } // } // if (!setView) { // QStatusBar *sb = KisPart::instance()->currentMainwindow()->statusBar(); // if (sb) { // sb->showMessage(i18n("Loading CSV file...")); // } // } else { // emit m_doc->statusBarMessage(i18n("Loading CSV file...")); // } // emit m_doc->sigProgress(0); // connect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel())); } int step = 0; do { qApp->processEvents(); if (m_stop) { retval = ImportExportCodes::Cancelled; break; } if ((idx = readLine.nextLine(io)) <= 0) { if ((idx < 0) ||(step < 5)) retval = ImportExportCodes::FileFormatIncorrect; break; } field = readLine.nextField(); //first field of the line if (field.isNull()) continue; //empty row switch (step) { case 0 : //skip first row step = 1; break; case 1 : //scene header names step = 2; for (idx = 0; !field.isNull(); idx++) { if (field == "Project Name") { projNameIdx = idx; } else if (field == "Width") { widthIdx = idx; } else if (field == "Height") { heightIdx = idx; } else if (field == "Frame Count") { frameCountIdx = idx; } else if (field == "Frame Rate") { framerateIdx = idx; } else if (field == "Pixel Aspect Ratio") { pixelRatioIdx = idx; } field= readLine.nextField(); } break; case 2 : //scene header values step= 3; for (idx= 0; !field.isNull(); idx++) { if (idx == projNameIdx) { projName = field; } else if (idx == widthIdx) { width = field.toInt(); } else if (idx == heightIdx) { height = field.toInt(); } else if (idx == frameCountIdx) { frameCount = field.toInt(); if (frameCount < 1) frameCount= 1; } else if (idx == framerateIdx) { framerate = field.toFloat(); } else if (idx == pixelRatioIdx) { pixelRatio = field.toFloat(); } field= readLine.nextField(); } if ((width < 1) || (height < 1)) { retval = ImportExportCodes::Failure; break; } retval = createNewImage(width, height, pixelRatio, projName.isNull() ? filename : projName); break; case 3 : //create level headers if (field[0] != '#') break; for (; !(field = readLine.nextField()).isNull(); ) { CSVLayerRecord* layerRecord = new CSVLayerRecord(); layers.append(layerRecord); } readLine.rewind(); field = readLine.nextField(); step = 4; Q_FALLTHROUGH(); case 4 : //level header if (field == "#Layers") { //layer name for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) layers.at(idx)->name = field; break; } if (field == "#Density") { //layer opacity for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) layers.at(idx)->density = field.toFloat(); break; } if (field == "#Blending") { //layer blending mode for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) layers.at(idx)->blending = field; break; } if (field == "#Visible") { //layer visibility for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) layers.at(idx)->visible = field.toInt(); break; } if (field == "#Folder") { //CSV 1.1 folder location for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) layers.at(idx)->path = validPath(field, base); break; } if ((field.size() < 2) || (field[0] != '#') || !field[1].isDigit()) break; step = 5; Q_FALLTHROUGH(); case 5 : //frames if ((field.size() < 2) || (field[0] != '#') || !field[1].isDigit()) break; for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) { CSVLayerRecord* layer = layers.at(idx); if (layer->last != field) { if (!m_batchMode) { //emit m_doc->sigProgress((frame * layers.size() + idx) * 100 / // (frameCount * layers.size())); } retval = setLayer(layer, importDoc.data(), path); layer->last = field; layer->frame = frame; } } frame++; break; } } while (retval.isOk()); //finish the layers if (retval.isOk()) { if (m_image) { KisImageAnimationInterface *animation = m_image->animationInterface(); if (frame > frameCount) frameCount = frame; animation->setFullClipRange(KisTimeRange::fromTime(0,frameCount - 1)); animation->setFramerate((int)framerate); } for (idx = 0; idx < layers.size(); idx++) { CSVLayerRecord* layer = layers.at(idx); //empty layers without any pictures are dropped if ((layer->frame > 0) || !layer->last.isEmpty()) { retval = setLayer(layer, importDoc.data(), path); if (!retval.isOk()) break; } } } if (m_image) { //insert the existing layers by the right order for (idx = layers.size() - 1; idx >= 0; idx--) { CSVLayerRecord* layer = layers.at(idx); if (layer->layer) { m_image->addNode(layer->layer, m_image->root()); } } m_image->unlock(); } qDeleteAll(layers); io->close(); if (!m_batchMode) { // disconnect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel())); // emit m_doc->sigProgress(100); if (!setView) { QStatusBar *sb = KisPart::instance()->currentMainwindow()->statusBar(); if (sb) { sb->clearMessage(); } } else { emit m_doc->clearStatusBarMessage(); } } QApplication::restoreOverrideCursor(); return retval; } QString CSVLoader::convertBlending(const QString &blending) { if (blending == "Color") return COMPOSITE_OVER; if (blending == "Behind") return COMPOSITE_BEHIND; if (blending == "Erase") return COMPOSITE_ERASE; // "Shade" if (blending == "Light") return COMPOSITE_LINEAR_LIGHT; if (blending == "Colorize") return COMPOSITE_COLORIZE; if (blending == "Hue") return COMPOSITE_HUE; if (blending == "Add") return COMPOSITE_ADD; if (blending == "Sub") return COMPOSITE_INVERSE_SUBTRACT; if (blending == "Multiply") return COMPOSITE_MULT; if (blending == "Screen") return COMPOSITE_SCREEN; // "Replace" // "Substitute" if (blending == "Difference") return COMPOSITE_DIFF; if (blending == "Divide") return COMPOSITE_DIVIDE; if (blending == "Overlay") return COMPOSITE_OVERLAY; if (blending == "Light2") return COMPOSITE_DODGE; if (blending == "Shade2") return COMPOSITE_BURN; if (blending == "HardLight") return COMPOSITE_HARD_LIGHT; if (blending == "SoftLight") return COMPOSITE_SOFT_LIGHT_PHOTOSHOP; if (blending == "GrainExtract") return COMPOSITE_GRAIN_EXTRACT; if (blending == "GrainMerge") return COMPOSITE_GRAIN_MERGE; if (blending == "Sub2") return COMPOSITE_SUBTRACT; if (blending == "Darken") return COMPOSITE_DARKEN; if (blending == "Lighten") return COMPOSITE_LIGHTEN; if (blending == "Saturation") return COMPOSITE_SATURATION; return COMPOSITE_OVER; } QString CSVLoader::validPath(const QString &path,const QString &base) { //replace Windows directory separators with the universal / QString tryPath= QString(path).replace(QString("\\"), QString("/")); int i = tryPath.lastIndexOf("/"); if (i == (tryPath.size() - 1)) tryPath= tryPath.left(i); //remove the ending separator if exists if (QFileInfo(tryPath).isDir()) return tryPath.append("/"); QString scan(tryPath); i = -1; while ((i= (scan.lastIndexOf("/",i) - 1)) > 0) { //avoid testing if the next level will be the default xxxx.layers folder if ((i >= 6) && (scan.mid(i - 6, 7) == ".layers")) continue; tryPath= QString(base).append(scan.mid(i + 2)); //base already ending with a / if (QFileInfo(tryPath).isDir()) return tryPath.append("/"); } return QString(); //NULL string } KisImportExportErrorCode CSVLoader::setLayer(CSVLayerRecord* layer, KisDocument *importDoc, const QString &path) { bool result = true; if (layer->channel == 0) { //create a new document layer float opacity = layer->density; if (opacity > 1.0) opacity = 1.0; else if (opacity < 0.0) opacity = 0.0; const KoColorSpace* cs = m_image->colorSpace(); const QString layerName = (layer->name).isEmpty() ? m_image->nextLayerName() : layer->name; KisPaintLayer* paintLayer = new KisPaintLayer(m_image, layerName, (quint8)(opacity * OPACITY_OPAQUE_U8), cs); paintLayer->setCompositeOpId(convertBlending(layer->blending)); paintLayer->setVisible(layer->visible); paintLayer->enableAnimation(); layer->layer = paintLayer; layer->channel = qobject_cast (paintLayer->getKeyframeChannel(KisKeyframeChannel::Content.id(), true)); } if (!layer->last.isEmpty()) { //png image QString filename = layer->path.isNull() ? path : layer->path; filename.append(layer->last); result = importDoc->openUrl(QUrl::fromLocalFile(filename), KisDocument::DontAddToRecent); if (result) layer->channel->importFrame(layer->frame, importDoc->image()->projection(), 0); } else { //blank layer->channel->addKeyframe(layer->frame); } return (result) ? ImportExportCodes::OK : ImportExportCodes::Failure; } KisImportExportErrorCode CSVLoader::createNewImage(int width, int height, float ratio, const QString &name) { //the CSV is RGBA 8bits, sRGB if (!m_image) { const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), 0); if (cs) m_image = new KisImage(m_doc->createUndoStore(), width, height, cs, name); if (!m_image) return ImportExportCodes::Failure; m_image->setResolution(ratio, 1.0); - m_image->lock(); + m_image->barrierLock(); } return ImportExportCodes::OK; } KisImportExportErrorCode CSVLoader::buildAnimation(QIODevice *io, const QString &filename) { return decode(io, filename); } KisImageSP CSVLoader::image() { return m_image; } void CSVLoader::cancel() { m_stop = true; } diff --git a/plugins/impex/libkra/tests/kis_kra_loader_test.cpp b/plugins/impex/libkra/tests/kis_kra_loader_test.cpp index 500295dcd7..b40dbdd515 100644 --- a/plugins/impex/libkra/tests/kis_kra_loader_test.cpp +++ b/plugins/impex/libkra/tests/kis_kra_loader_test.cpp @@ -1,192 +1,192 @@ /* * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_kra_loader_test.h" #include #include #include #include #include #include #include "kis_image.h" #include "testutil.h" #include "KisPart.h" #include #include #include "kis_image_animation_interface.h" #include "kis_keyframe_channel.h" #include "kis_time_range.h" #include #include const QString KraMimetype = "application/x-krita"; void KisKraLoaderTest::initTestCase() { KisFilterRegistry::instance(); KisGeneratorRegistry::instance(); } void KisKraLoaderTest::testLoading() { QScopedPointer doc(KisPart::instance()->createDocument()); doc->loadNativeFormat(QString(FILES_DATA_DIR) + QDir::separator() + "load_test.kra"); KisImageSP image = doc->image(); - image->lock(); + image->waitForDone(); QCOMPARE(image->nlayers(), 12); QCOMPARE(doc->documentInfo()->aboutInfo("title"), QString("test image for loading")); QCOMPARE(image->height(), 753); QCOMPARE(image->width(), 1000); QCOMPARE(image->colorSpace()->id(), KoColorSpaceRegistry::instance()->rgb8()->id()); KisNodeSP node = image->root()->firstChild(); QVERIFY(node); QCOMPARE(node->name(), QString("Background")); QVERIFY(node->inherits("KisPaintLayer")); node = node->nextSibling(); QVERIFY(node); QCOMPARE(node->name(), QString("Group 1")); QVERIFY(node->inherits("KisGroupLayer")); QCOMPARE((int) node->childCount(), 2); } void testObligeSingleChildImpl(bool transpDefaultPixel) { QString id = !transpDefaultPixel ? "single_layer_no_channel_flags_nontransp_def_pixel.kra" : "single_layer_no_channel_flags_transp_def_pixel.kra"; QString fileName = TestUtil::fetchDataFileLazy(id); QScopedPointer doc(KisPart::instance()->createDocument()); const bool result = doc->loadNativeFormat(fileName); QVERIFY(result); KisImageSP image = doc->image(); QVERIFY(image); QCOMPARE(image->nlayers(), 2); KisNodeSP root = image->root(); KisNodeSP child = root->firstChild(); QVERIFY(child); QCOMPARE(root->original(), root->projection()); if (transpDefaultPixel) { QCOMPARE(root->original(), child->projection()); } else { QVERIFY(root->original() != child->projection()); } } void KisKraLoaderTest::testObligeSingleChild() { testObligeSingleChildImpl(true); } void KisKraLoaderTest::testObligeSingleChildNonTranspPixel() { testObligeSingleChildImpl(false); } void KisKraLoaderTest::testLoadAnimated() { QScopedPointer doc(KisPart::instance()->createDocument()); doc->loadNativeFormat(QString(FILES_DATA_DIR) + QDir::separator() + "load_test_animation.kra"); KisImageSP image = doc->image(); KisNodeSP node1 = image->root()->firstChild(); KisNodeSP node2 = node1->nextSibling(); QVERIFY(node1->inherits("KisPaintLayer")); QVERIFY(node2->inherits("KisPaintLayer")); KisPaintLayerSP layer1 = qobject_cast(node1.data()); KisPaintLayerSP layer2 = qobject_cast(node2.data()); QVERIFY(layer1->isAnimated()); QVERIFY(!layer2->isAnimated()); KisKeyframeChannel *channel1 = layer1->getKeyframeChannel(KisKeyframeChannel::Content.id()); QVERIFY(channel1); QCOMPARE(channel1->keyframeCount(), 3); QCOMPARE(image->animationInterface()->framerate(), 17); QCOMPARE(image->animationInterface()->fullClipRange(), KisTimeRange::fromTime(15, 45)); QCOMPARE(image->animationInterface()->currentTime(), 19); KisPaintDeviceSP dev = layer1->paintDevice(); const KoColorSpace *cs = dev->colorSpace(); KoColor transparent(Qt::transparent, cs); KoColor white(Qt::white, cs); KoColor red(Qt::red, cs); image->animationInterface()->switchCurrentTimeAsync(0); image->waitForDone(); QCOMPARE(dev->exactBounds(), QRect(506, 378, 198, 198)); QCOMPARE(dev->x(), -26); QCOMPARE(dev->y(), -128); QCOMPARE(dev->defaultPixel(), transparent); image->animationInterface()->switchCurrentTimeAsync(20); image->waitForDone(); QCOMPARE(dev->nonDefaultPixelArea(), QRect(615, 416, 129, 129)); QCOMPARE(dev->x(), 502); QCOMPARE(dev->y(), 224); QCOMPARE(dev->defaultPixel(), white); image->animationInterface()->switchCurrentTimeAsync(30); image->waitForDone(); QCOMPARE(dev->nonDefaultPixelArea(), QRect(729, 452, 45, 44)); QCOMPARE(dev->x(), 645); QCOMPARE(dev->y(), -10); QCOMPARE(dev->defaultPixel(), red); } void KisKraLoaderTest::testImportFromWriteonly() { TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), KraMimetype); } void KisKraLoaderTest::testImportIncorrectFormat() { TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), KraMimetype); } KISTEST_MAIN(KisKraLoaderTest) diff --git a/plugins/impex/psd/psd_loader.cpp b/plugins/impex/psd/psd_loader.cpp index 5649f64b69..a695f89514 100644 --- a/plugins/impex/psd/psd_loader.cpp +++ b/plugins/impex/psd/psd_loader.cpp @@ -1,388 +1,386 @@ /* * Copyright (c) 2009 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "psd_loader.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisResourceServerProvider.h" #include "psd.h" #include "psd_header.h" #include "psd_colormode_block.h" #include "psd_utils.h" #include "psd_resource_section.h" #include "psd_layer_section.h" #include "psd_resource_block.h" #include "psd_image_data.h" +#include "kis_image_barrier_locker.h" PSDLoader::PSDLoader(KisDocument *doc) : m_image(0) , m_doc(doc) , m_stop(false) { } PSDLoader::~PSDLoader() { } KisImportExportErrorCode PSDLoader::decode(QIODevice *io) { // open the file dbgFile << "pos:" << io->pos(); PSDHeader header; if (!header.read(io)) { dbgFile << "failed reading header: " << header.error; return ImportExportCodes::FileFormatIncorrect; } dbgFile << header; dbgFile << "Read header. pos:" << io->pos(); PSDColorModeBlock colorModeBlock(header.colormode); if (!colorModeBlock.read(io)) { dbgFile << "failed reading colormode block: " << colorModeBlock.error; return ImportExportCodes::FileFormatIncorrect; } dbgFile << "Read color mode block. pos:" << io->pos(); PSDImageResourceSection resourceSection; if (!resourceSection.read(io)) { dbgFile << "failed image reading resource section: " << resourceSection.error; return ImportExportCodes::FileFormatIncorrect; } dbgFile << "Read image resource section. pos:" << io->pos(); PSDLayerMaskSection layerSection(header); if (!layerSection.read(io)) { dbgFile << "failed reading layer/mask section: " << layerSection.error; return ImportExportCodes::FileFormatIncorrect; } dbgFile << "Read layer/mask section. " << layerSection.nLayers << "layers. pos:" << io->pos(); // Done reading, except possibly for the image data block, which is only relevant if there // are no layers. // Get the right colorspace QPair colorSpaceId = psd_colormode_to_colormodelid(header.colormode, header.channelDepth); if (colorSpaceId.first.isNull()) { dbgFile << "Unsupported colorspace" << header.colormode << header.channelDepth; return ImportExportCodes::FormatColorSpaceUnsupported; } // Get the icc profile from the image resource section const KoColorProfile* profile = 0; if (resourceSection.resources.contains(PSDImageResourceSection::ICC_PROFILE)) { ICC_PROFILE_1039 *iccProfileData = dynamic_cast(resourceSection.resources[PSDImageResourceSection::ICC_PROFILE]->resource); if (iccProfileData ) { profile = KoColorSpaceRegistry::instance()->createColorProfile(colorSpaceId.first, colorSpaceId.second, iccProfileData->icc); dbgFile << "Loaded ICC profile" << profile->name(); delete resourceSection.resources.take(PSDImageResourceSection::ICC_PROFILE); } } // Create the colorspace const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceId.first, colorSpaceId.second, profile); if (!cs) { return ImportExportCodes::FormatColorSpaceUnsupported; } // Creating the KisImage QFile *file = dynamic_cast(io); QString name = file ? file->fileName() : "Imported"; m_image = new KisImage(m_doc->createUndoStore(), header.width, header.height, cs, name); Q_CHECK_PTR(m_image); - m_image->lock(); + KisImageBarrierLocker locker(m_image); // set the correct resolution if (resourceSection.resources.contains(PSDImageResourceSection::RESN_INFO)) { RESN_INFO_1005 *resInfo = dynamic_cast(resourceSection.resources[PSDImageResourceSection::RESN_INFO]->resource); if (resInfo) { // check resolution size is not zero if (resInfo->hRes * resInfo->vRes > 0) m_image->setResolution(POINT_TO_INCH(resInfo->hRes), POINT_TO_INCH(resInfo->vRes)); // let's skip the unit for now; we can only set that on the KisDocument, and krita doesn't use it. delete resourceSection.resources.take(PSDImageResourceSection::RESN_INFO); } } // Preserve all the annotations Q_FOREACH (PSDResourceBlock *resourceBlock, resourceSection.resources.values()) { m_image->addAnnotation(resourceBlock); } // Preserve the duotone colormode block for saving back to psd if (header.colormode == DuoTone) { KisAnnotationSP annotation = new KisAnnotation("DuotoneColormodeBlock", i18n("Duotone Colormode Block"), colorModeBlock.data); m_image->addAnnotation(annotation); } // Read the projection into our single layer. Since we only read the projection when // we have just one layer, we don't need to later on apply the alpha channel of the // first layer to the projection if the number of layers is negative/ // See https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_16000. if (layerSection.nLayers == 0) { dbgFile << "Position" << io->pos() << "Going to read the projection into the first layer, which Photoshop calls 'Background'"; KisPaintLayerSP layer = new KisPaintLayer(m_image, i18n("Background"), OPACITY_OPAQUE_U8); PSDImageData imageData(&header); imageData.read(io, layer->paintDevice()); m_image->addNode(layer, m_image->rootLayer()); // Only one layer, the background layer, so we're done. - m_image->unlock(); return ImportExportCodes::OK; } // More than one layer, so now construct the Krita image from the info we read. QStack groupStack; groupStack.push(m_image->rootLayer()); /** * PSD has a weird "optimization": if a group layer has only one * child layer, it omits it's 'psd_bounding_divider' section. So * fi you ever see an unbalanced layers group in PSD, most * probably, it is just a single layered group. */ KisNodeSP lastAddedLayer; typedef QPair LayerStyleMapping; QVector allStylesXml; // read the channels for the various layers for(int i = 0; i < layerSection.nLayers; ++i) { PSDLayerRecord* layerRecord = layerSection.layers.at(i); dbgFile << "Going to read channels for layer" << i << layerRecord->layerName; KisLayerSP newLayer; if (layerRecord->infoBlocks.keys.contains("lsct") && layerRecord->infoBlocks.sectionDividerType != psd_other) { if (layerRecord->infoBlocks.sectionDividerType == psd_bounding_divider && !groupStack.isEmpty()) { KisGroupLayerSP groupLayer = new KisGroupLayer(m_image, "temp", OPACITY_OPAQUE_U8); m_image->addNode(groupLayer, groupStack.top()); groupStack.push(groupLayer); newLayer = groupLayer; } else if ((layerRecord->infoBlocks.sectionDividerType == psd_open_folder || layerRecord->infoBlocks.sectionDividerType == psd_closed_folder) && (groupStack.size() > 1 || (lastAddedLayer && !groupStack.isEmpty()))) { KisGroupLayerSP groupLayer; if (groupStack.size() <= 1) { groupLayer = new KisGroupLayer(m_image, "temp", OPACITY_OPAQUE_U8); m_image->addNode(groupLayer, groupStack.top()); m_image->moveNode(lastAddedLayer, groupLayer, KisNodeSP()); } else { groupLayer = groupStack.pop(); } const QDomDocument &styleXml = layerRecord->infoBlocks.layerStyleXml; if (!styleXml.isNull()) { allStylesXml << LayerStyleMapping(styleXml, groupLayer); } groupLayer->setName(layerRecord->layerName); groupLayer->setVisible(layerRecord->visible); QString compositeOp = psd_blendmode_to_composite_op(layerRecord->infoBlocks.sectionDividerBlendMode); // Krita doesn't support pass-through blend // mode. Instead it is just a property of a group // layer, so flip it if (compositeOp == COMPOSITE_PASS_THROUGH) { compositeOp = COMPOSITE_OVER; groupLayer->setPassThroughMode(true); } groupLayer->setCompositeOpId(compositeOp); newLayer = groupLayer; } else { /** * In some files saved by PS CS6 the group layer sections seem * to be unbalanced. I don't know why it happens because the * reporter didn't provide us an example file. So here we just * check if the new layer was created, and if not, skip the * initialization of masks. * * See bug: 357559 */ warnKrita << "WARNING: Provided PSD has unbalanced group " << "layer markers. Some masks and/or layers can " << "be lost while loading this file. Please " << "report a bug to Krita developers and attach " << "this file to the bugreport\n" << " " << ppVar(layerRecord->layerName) << "\n" << " " << ppVar(layerRecord->infoBlocks.sectionDividerType) << "\n" << " " << ppVar(groupStack.size()); continue; } } else { KisPaintLayerSP layer = new KisPaintLayer(m_image, layerRecord->layerName, layerRecord->opacity); layer->setCompositeOpId(psd_blendmode_to_composite_op(layerRecord->blendModeKey)); const QDomDocument &styleXml = layerRecord->infoBlocks.layerStyleXml; if (!styleXml.isNull()) { allStylesXml << LayerStyleMapping(styleXml, layer); } if (!layerRecord->readPixelData(io, layer->paintDevice())) { dbgFile << "failed reading channels for layer: " << layerRecord->layerName << layerRecord->error; return ImportExportCodes::FileFormatIncorrect; } if (!groupStack.isEmpty()) { m_image->addNode(layer, groupStack.top()); } else { m_image->addNode(layer, m_image->root()); } layer->setVisible(layerRecord->visible); newLayer = layer; } Q_FOREACH (ChannelInfo *channelInfo, layerRecord->channelInfoRecords) { if (channelInfo->channelId < -1) { KisTransparencyMaskSP mask = new KisTransparencyMask(); mask->setName(i18n("Transparency Mask")); mask->initSelection(newLayer); if (!layerRecord->readMask(io, mask->paintDevice(), channelInfo)) { dbgFile << "failed reading masks for layer: " << layerRecord->layerName << layerRecord->error; } m_image->addNode(mask, newLayer); } } lastAddedLayer = newLayer; } const QVector &embeddedPatterns = layerSection.globalInfoSection.embeddedPatterns; KisAslLayerStyleSerializer serializer; if (!embeddedPatterns.isEmpty()) { Q_FOREACH (const QDomDocument &doc, embeddedPatterns) { serializer.registerPSDPattern(doc); } } QVector allStylesForServer; if (!allStylesXml.isEmpty()) { Q_FOREACH (const LayerStyleMapping &mapping, allStylesXml) { serializer.readFromPSDXML(mapping.first); if (serializer.styles().size() == 1) { KisPSDLayerStyleSP layerStyle = serializer.styles().first(); KisLayerSP layer = mapping.second; layerStyle->setName(layer->name()); allStylesForServer << layerStyle; layer->setLayerStyle(layerStyle->clone().dynamicCast()); } else { warnKrita << "WARNING: Couldn't read layer style!" << ppVar(serializer.styles()); } } } if (!allStylesForServer.isEmpty()) { warnKrita << "WARNING: Asl Layer Styles cannot be read (part of resource rewrite)."; // TODO: RESOURCES: needs implementing of creation of the storage and linking it to the database etc. // Note: it would be possible to just read them and add to the server, but it would be better to do it through the storage /* KisPSDLayerStyleCollectionResourceSP collection(new KisPSDLayerStyleCollectionResource("Embedded PSD Styles.asl")); collection->setName(i18nc("Auto-generated layer style collection name for embedded styles (collection)", "<%1> (embedded)", m_image->objectName())); KIS_SAFE_ASSERT_RECOVER_NOOP(!collection->valid()); collection->setLayerStyles(allStylesForServer); KIS_SAFE_ASSERT_RECOVER_NOOP(collection->valid()); KoResourceServer *server = KisResourceServerProvider::instance()->layerStyleServer(); server->addResource(collection, false); */ } - - m_image->unlock(); return ImportExportCodes::OK; } KisImportExportErrorCode PSDLoader::buildImage(QIODevice *io) { return decode(io); } KisImageSP PSDLoader::image() { return m_image; } void PSDLoader::cancel() { m_stop = true; } diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/rwidgetutils.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/rwidgetutils.cpp index edf757459b..53a05b166d 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/rwidgetutils.cpp +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/rwidgetutils.cpp @@ -1,626 +1,631 @@ /** =========================================================== * @file * * This file is a part of digiKam project * https://www.digikam.org * * @date 2014-09-12 * @brief Simple helper widgets collection * * @author Copyright (C) 2014-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "rwidgetutils.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "libkdcraw_debug.h" namespace KDcrawIface { RActiveLabel::RActiveLabel(const QUrl& url, const QString& imgPath, QWidget* const parent) : QLabel(parent) { setMargin(0); setScaledContents(false); setOpenExternalLinks(true); setTextFormat(Qt::RichText); setFocusPolicy(Qt::NoFocus); setTextInteractionFlags(Qt::LinksAccessibleByMouse); setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum)); QImage img = QImage(imgPath); updateData(url, img); } RActiveLabel::~RActiveLabel() { } void RActiveLabel::updateData(const QUrl& url, const QImage& img) { QByteArray byteArray; QBuffer buffer(&byteArray); img.save(&buffer, "PNG"); setText(QString::fromLatin1("%2") .arg(url.url()) .arg(QString::fromLatin1("") .arg(QString::fromLatin1(byteArray.toBase64().data())))); } // ------------------------------------------------------------------------------------ RLineWidget::RLineWidget(Qt::Orientation orientation, QWidget* const parent) : QFrame(parent) { setLineWidth(1); setMidLineWidth(0); if (orientation == Qt::Vertical) { setFrameShape(QFrame::VLine); setFrameShadow(QFrame::Sunken); setMinimumSize(2, 0); } else { setFrameShape(QFrame::HLine); setFrameShadow(QFrame::Sunken); setMinimumSize(0, 2); } updateGeometry(); } RLineWidget::~RLineWidget() { } // ------------------------------------------------------------------------------------ RHBox::RHBox(QWidget* const parent) : QFrame(parent) { QHBoxLayout* const layout = new QHBoxLayout(this); layout->setSpacing(0); layout->setMargin(0); setLayout(layout); } RHBox::RHBox(bool /*vertical*/, QWidget* const parent) : QFrame(parent) { QVBoxLayout* const layout = new QVBoxLayout(this); layout->setSpacing(0); layout->setMargin(0); setLayout(layout); } RHBox::~RHBox() { } void RHBox::childEvent(QChildEvent* e) { switch (e->type()) { case QEvent::ChildAdded: { QChildEvent* const ce = static_cast(e); if (ce->child()->isWidgetType()) { QWidget* const w = static_cast(ce->child()); static_cast(layout())->addWidget(w); } break; } case QEvent::ChildRemoved: { QChildEvent* const ce = static_cast(e); if (ce->child()->isWidgetType()) { QWidget* const w = static_cast(ce->child()); static_cast(layout())->removeWidget(w); } break; } default: break; } QFrame::childEvent(e); } QSize RHBox::sizeHint() const { RHBox* const b = const_cast(this); QApplication::sendPostedEvents(b, QEvent::ChildAdded); return QFrame::sizeHint(); } QSize RHBox::minimumSizeHint() const { RHBox* const b = const_cast(this); QApplication::sendPostedEvents(b, QEvent::ChildAdded ); return QFrame::minimumSizeHint(); } void RHBox::setSpacing(int spacing) { layout()->setSpacing(spacing); } void RHBox::setMargin(int margin) { layout()->setMargin(margin); } void RHBox::setStretchFactor(QWidget* const widget, int stretch) { static_cast(layout())->setStretchFactor(widget, stretch); } // ------------------------------------------------------------------------------------ RVBox::RVBox(QWidget* const parent) : RHBox(true, parent) { } RVBox::~RVBox() { } // ------------------------------------------------------------------------------------ class Q_DECL_HIDDEN RAdjustableLabel::Private { public: Private() { emode = Qt::ElideMiddle; } QString ajdText; Qt::TextElideMode emode; }; RAdjustableLabel::RAdjustableLabel(QWidget* const parent) : QLabel(parent), d(new Private) { setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); } RAdjustableLabel::~RAdjustableLabel() { delete d; } void RAdjustableLabel::resizeEvent(QResizeEvent*) { adjustTextToLabel(); } QSize RAdjustableLabel::minimumSizeHint() const { QSize sh = QLabel::minimumSizeHint(); sh.setWidth(-1); return sh; } QSize RAdjustableLabel::sizeHint() const { QFontMetrics fm(fontMetrics()); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) QScreen *s = qApp->screenAt(geometry().topLeft()); int maxW = s->availableGeometry().width() * 3 / 4; +#else + int maxW = QApplication::desktop()->screenGeometry(this).width() * 3 / 4; +#endif + #if QT_VERSION >= QT_VERSION_CHECK(5,11,0) int currentW = fm.horizontalAdvance(d->ajdText); #else int currentW = fm.width(d->ajdText); #endif return (QSize(currentW > maxW ? maxW : currentW, QLabel::sizeHint().height())); } void RAdjustableLabel::setAdjustedText(const QString& text) { d->ajdText = text; if (d->ajdText.isNull()) QLabel::clear(); adjustTextToLabel(); } QString RAdjustableLabel::adjustedText() const { return d->ajdText; } void RAdjustableLabel::setAlignment(Qt::Alignment alignment) { QString tmp(d->ajdText); QLabel::setAlignment(alignment); d->ajdText = tmp; } void RAdjustableLabel::setElideMode(Qt::TextElideMode mode) { d->emode = mode; adjustTextToLabel(); } void RAdjustableLabel::adjustTextToLabel() { QFontMetrics fm(fontMetrics()); QStringList adjustedLines; int lblW = size().width(); bool adjusted = false; Q_FOREACH(const QString& line, d->ajdText.split(QLatin1Char('\n'))) { #if QT_VERSION >= QT_VERSION_CHECK(5,11,0) int lineW = fm.horizontalAdvance(line); #else int lineW = fm.width(line); #endif if (lineW > lblW) { adjusted = true; adjustedLines << fm.elidedText(line, d->emode, lblW); } else { adjustedLines << line; } } if (adjusted) { QLabel::setText(adjustedLines.join(QStringLiteral("\n"))); setToolTip(d->ajdText); } else { QLabel::setText(d->ajdText); setToolTip(QString()); } } // ------------------------------------------------------------------------------------ class Q_DECL_HIDDEN RFileSelector::Private { public: Private() { edit = 0; btn = 0; fdMode = QFileDialog::ExistingFile; fdOptions = QFileDialog::DontUseNativeDialog; } QLineEdit* edit; QPushButton* btn; QFileDialog::FileMode fdMode; QString fdFilter; QString fdTitle; QFileDialog::Options fdOptions; }; RFileSelector::RFileSelector(QWidget* const parent) : RHBox(parent), d(new Private) { d->edit = new QLineEdit(this); d->btn = new QPushButton(i18n("Browse..."), this); setStretchFactor(d->edit, 10); connect(d->btn, SIGNAL(clicked()), this, SLOT(slotBtnClicked())); } RFileSelector::~RFileSelector() { delete d; } QLineEdit* RFileSelector::lineEdit() const { return d->edit; } void RFileSelector::setFileDlgMode(QFileDialog::FileMode mode) { d->fdMode = mode; } void RFileSelector::setFileDlgFilter(const QString& filter) { d->fdFilter = filter; } void RFileSelector::setFileDlgTitle(const QString& title) { d->fdTitle = title; } void RFileSelector::setFileDlgOptions(QFileDialog::Options opts) { d->fdOptions = opts; } void RFileSelector::slotBtnClicked() { if (d->fdMode == QFileDialog::ExistingFiles) { qCDebug(LIBKDCRAW_LOG) << "Multiple selection is not supported"; return; } QFileDialog* const fileDlg = new QFileDialog(this); fileDlg->setOptions(d->fdOptions); fileDlg->setDirectory(QFileInfo(d->edit->text()).dir()); fileDlg->setFileMode(d->fdMode); if (!d->fdFilter.isNull()) fileDlg->setNameFilter(d->fdFilter); if (!d->fdTitle.isNull()) fileDlg->setWindowTitle(d->fdTitle); connect(fileDlg, SIGNAL(urlSelected(QUrl)), this, SIGNAL(signalUrlSelected(QUrl))); emit signalOpenFileDialog(); if (fileDlg->exec() == QDialog::Accepted) { QStringList sel = fileDlg->selectedFiles(); if (!sel.isEmpty()) { d->edit->setText(sel.first()); } } delete fileDlg; } // --------------------------------------------------------------------------------------- WorkingPixmap::WorkingPixmap() { QPixmap pix(QStandardPaths::locate(QStandardPaths::AppDataLocation, QLatin1String("libkdcraw/pics/process-working.png"))); QSize size(22, 22); if (pix.isNull()) { qCWarning(LIBKDCRAW_LOG) << "Invalid pixmap specified."; return; } if (!size.isValid()) { size = QSize(pix.width(), pix.width()); } if (pix.width() % size.width() || pix.height() % size.height()) { qCWarning(LIBKDCRAW_LOG) << "Invalid framesize."; return; } const int rowCount = pix.height() / size.height(); const int colCount = pix.width() / size.width(); m_frames.resize(rowCount * colCount); int pos = 0; for (int row = 0; row < rowCount; ++row) { for (int col = 0; col < colCount; ++col) { QPixmap frm = pix.copy(col * size.width(), row * size.height(), size.width(), size.height()); m_frames[pos++] = frm; } } } WorkingPixmap::~WorkingPixmap() { } bool WorkingPixmap::isEmpty() const { return m_frames.isEmpty(); } QSize WorkingPixmap::frameSize() const { if (isEmpty()) { qCWarning(LIBKDCRAW_LOG) << "No frame loaded."; return QSize(); } return m_frames[0].size(); } int WorkingPixmap::frameCount() const { return m_frames.size(); } QPixmap WorkingPixmap::frameAt(int index) const { if (isEmpty()) { qCWarning(LIBKDCRAW_LOG) << "No frame loaded."; return QPixmap(); } return m_frames.at(index); } // ------------------------------------------------------------------------------------ class Q_DECL_HIDDEN RColorSelector::Private { public: Private() { } QColor color; }; RColorSelector::RColorSelector(QWidget* const parent) : QPushButton(parent), d(new Private) { connect(this, SIGNAL(clicked()), this, SLOT(slotBtnClicked())); } RColorSelector::~RColorSelector() { delete d; } void RColorSelector::setColor(const QColor& color) { if (color.isValid()) { d->color = color; update(); } } QColor RColorSelector::color() const { return d->color; } void RColorSelector::slotBtnClicked() { QColor color = QColorDialog::getColor(d->color); if (color.isValid()) { setColor(color); emit signalColorSelected(color); } } void RColorSelector::paintEvent(QPaintEvent*) { QPainter painter(this); QStyle* const style = QWidget::style(); QStyleOptionButton opt; opt.initFrom(this); opt.state |= isDown() ? QStyle::State_Sunken : QStyle::State_Raised; opt.features = QStyleOptionButton::None; opt.icon = QIcon(); opt.text.clear(); style->drawControl(QStyle::CE_PushButtonBevel, &opt, &painter, this); QRect labelRect = style->subElementRect(QStyle::SE_PushButtonContents, &opt, this); int shift = style->pixelMetric(QStyle::PM_ButtonMargin, &opt, this) / 2; labelRect.adjust(shift, shift, -shift, -shift); int x, y, w, h; labelRect.getRect(&x, &y, &w, &h); if (isChecked() || isDown()) { x += style->pixelMetric(QStyle::PM_ButtonShiftHorizontal, &opt, this); y += style->pixelMetric(QStyle::PM_ButtonShiftVertical, &opt, this); } QColor fillCol = isEnabled() ? d->color : palette().color(backgroundRole()); qDrawShadePanel(&painter, x, y, w, h, palette(), true, 1, 0); if (fillCol.isValid()) { const QRect rect(x + 1, y + 1, w - 2, h - 2); if (fillCol.alpha() < 255) { QPixmap chessboardPattern(16, 16); QPainter patternPainter(&chessboardPattern); patternPainter.fillRect(0, 0, 8, 8, Qt::black); patternPainter.fillRect(8, 8, 8, 8, Qt::black); patternPainter.fillRect(0, 8, 8, 8, Qt::white); patternPainter.fillRect(8, 0, 8, 8, Qt::white); patternPainter.end(); painter.fillRect(rect, QBrush(chessboardPattern)); } painter.fillRect(rect, fillCol); } if (hasFocus()) { QRect focusRect = style->subElementRect(QStyle::SE_PushButtonFocusRect, &opt, this); QStyleOptionFocusRect focusOpt; focusOpt.init(this); focusOpt.rect = focusRect; focusOpt.backgroundColor = palette().window().color(); style->drawPrimitive(QStyle::PE_FrameFocusRect, &focusOpt, &painter, this); } } } // namespace KDcrawIface diff --git a/plugins/paintops/libpaintop/kis_custom_brush_widget.cpp b/plugins/paintops/libpaintop/kis_custom_brush_widget.cpp index 76eea6edf2..5a7103e4b9 100644 --- a/plugins/paintops/libpaintop/kis_custom_brush_widget.cpp +++ b/plugins/paintops/libpaintop/kis_custom_brush_widget.cpp @@ -1,268 +1,268 @@ /* * Copyright (c) 2005 Bart Coppens * Copyright (c) 2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_custom_brush_widget.h" #include #include #include #include #include #include #include #include #include #include #include "kis_image.h" #include "kis_layer.h" #include "kis_paint_device.h" #include "kis_gbr_brush.h" #include "kis_imagepipe_brush.h" #include #include "KisBrushServerProvider.h" #include "kis_paint_layer.h" #include "kis_group_layer.h" #include #include #include "kis_iterator_ng.h" +#include "kis_image_barrier_locker.h" KisCustomBrushWidget::KisCustomBrushWidget(QWidget *parent, const QString& caption, KisImageWSP image) : KisWdgCustomBrush(parent) , m_image(image) { setWindowTitle(caption); preview->setScaledContents(false); preview->setFixedSize(preview->size()); preview->setStyleSheet("border: 2px solid #222; border-radius: 4px; padding: 5px; font: normal 10px;"); m_rServer = KisBrushServerProvider::instance()->brushServer(); m_brush = 0; connect(this, SIGNAL(accepted()), SLOT(slotAddPredefined())); connect(brushStyle, SIGNAL(activated(int)), this, SLOT(slotUpdateCurrentBrush(int))); connect(colorAsMask, SIGNAL(toggled(bool)), this, SLOT(slotUpdateUseColorAsMask(bool))); connect(comboBox2, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateCurrentBrush(int))); colorAsMask->setChecked(true); // use color as mask by default. This is by far the most common way to make tip. spacingWidget->setSpacing(true, 1.0); connect(spacingWidget, SIGNAL(sigSpacingChanged()), SLOT(slotSpacingChanged())); } KisCustomBrushWidget::~KisCustomBrushWidget() { } KisBrushSP KisCustomBrushWidget::brush() { return m_brush; } void KisCustomBrushWidget::setImage(KisImageWSP image){ m_image = image; createBrush(); updatePreviewImage(); } void KisCustomBrushWidget::showEvent(QShowEvent *) { slotUpdateCurrentBrush(0); } void KisCustomBrushWidget::updatePreviewImage() { QImage brushImage = m_brush ? m_brush->brushTipImage() : QImage(); if (!brushImage.isNull()) { int w = preview->size().width() - 10; // 10 for the padding... brushImage = brushImage.scaled(w, w, Qt::KeepAspectRatio); } preview->setPixmap(QPixmap::fromImage(brushImage)); } void KisCustomBrushWidget::slotUpdateCurrentBrush(int) { if (brushStyle->currentIndex() == 0) { comboBox2->setEnabled(false); } else { comboBox2->setEnabled(true); } if (m_image) { createBrush(); updatePreviewImage(); } } void KisCustomBrushWidget::slotSpacingChanged() { if (m_brush) { m_brush->setSpacing(spacingWidget->spacing()); m_brush->setAutoSpacing(spacingWidget->autoSpacingActive(), spacingWidget->autoSpacingCoeff()); } } void KisCustomBrushWidget::slotUpdateUseColorAsMask(bool useColorAsMask) { if (m_brush) { static_cast(m_brush.data())->setUseColorAsMask(useColorAsMask); updatePreviewImage(); } } void KisCustomBrushWidget::slotUpdateSaveButton() { QString suffix = ".gbr"; if (brushStyle->currentIndex() != 0) { suffix = ".gih"; } if (QFileInfo(m_rServer->saveLocation() + "/" + nameLineEdit->text().split(" ").join("_") + suffix).exists()) { buttonBox->button(QDialogButtonBox::Save)->setText(i18n("Overwrite")); } else { buttonBox->button(QDialogButtonBox::Save)->setText(i18n("Save")); } } void KisCustomBrushWidget::slotAddPredefined() { QString dir = KoResourcePaths::saveLocation("data", ResourceType::Brushes); QString name = nameLineEdit->text(); if (nameLineEdit->text().isEmpty()) { name = (QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm")); } // Add it to the brush server, so that it automatically gets to the mediators, and // so to the other brush choosers can pick it up, if they want to if (m_rServer && m_brush) { if (m_brush->clone().dynamicCast()) { KisGbrBrushSP resource = m_brush->clone().dynamicCast(); resource->setName(name); resource->setFilename(resource->name().split(" ").join("_") + resource->defaultFileExtension()); if (colorAsMask->isChecked()) { resource->makeMaskImage(); } m_rServer->addResource(resource.dynamicCast()); emit sigNewPredefinedBrush(resource); } else { KisImagePipeBrushSP resource = m_brush->clone().dynamicCast(); resource->setName(name); resource->setFilename(resource->name().split(" ").join("_") + resource->defaultFileExtension()); if (colorAsMask->isChecked()) { resource->makeMaskImage(); } m_rServer->addResource(resource.dynamicCast()); emit sigNewPredefinedBrush(resource); } } close(); } void KisCustomBrushWidget::createBrush() { if (!m_image) return; if (brushStyle->currentIndex() == 0) { KisSelectionSP selection = m_image->globalSelection(); // create copy of the data - m_image->lock(); + m_image->barrierLock(); KisPaintDeviceSP dev = new KisPaintDevice(*m_image->projection()); m_image->unlock(); if (!selection) { m_brush = KisBrushSP(new KisGbrBrush(dev, 0, 0, m_image->width(), m_image->height())); } else { // apply selection mask QRect r = selection->selectedExactRect(); KisHLineIteratorSP pixelIt = dev->createHLineIteratorNG(r.x(), r.top(), r.width()); KisHLineConstIteratorSP maskIt = selection->projection()->createHLineIteratorNG(r.x(), r.top(), r.width()); for (qint32 y = r.top(); y <= r.bottom(); ++y) { do { dev->colorSpace()->applyAlphaU8Mask(pixelIt->rawData(), maskIt->oldRawData(), 1); } while (pixelIt->nextPixel() && maskIt->nextPixel()); pixelIt->nextRow(); maskIt->nextRow(); } m_brush = KisBrushSP(new KisGbrBrush(dev, r.x(), r.y(), r.width(), r.height())); } } else { // For each layer in the current image, create a new image, and add it to the list QVector< QVector > devices; devices.push_back(QVector()); int w = m_image->width(); int h = m_image->height(); - m_image->lock(); + KisImageBarrierLocker locker(m_image); // We only loop over the rootLayer. Since we actually should have a layer selection // list, no need to elaborate on that here and now KoProperties properties; properties.setProperty("visible", true); QList layers = m_image->root()->childNodes(QStringList("KisLayer"), properties); KisNodeSP node; Q_FOREACH (KisNodeSP node, layers) { devices[0].push_back(node->projection().data()); } QVector modes; switch (comboBox2->currentIndex()) { case 0: modes.push_back(KisParasite::Constant); break; case 1: modes.push_back(KisParasite::Random); break; case 2: modes.push_back(KisParasite::Incremental); break; case 3: modes.push_back(KisParasite::Pressure); break; case 4: modes.push_back(KisParasite::Angular); break; default: modes.push_back(KisParasite::Incremental); } m_brush = KisBrushSP(new KisImagePipeBrush(m_image->objectName(), w, h, devices, modes)); - m_image->unlock(); } static_cast(m_brush.data())->setUseColorAsMask(colorAsMask->isChecked()); m_brush->setSpacing(spacingWidget->spacing()); m_brush->setAutoSpacing(spacingWidget->autoSpacingActive(), spacingWidget->autoSpacingCoeff()); m_brush->setFilename(TEMPORARY_FILENAME); m_brush->setName(TEMPORARY_BRUSH_NAME); m_brush->setValid(true); } diff --git a/plugins/tools/selectiontools/kis_tool_select_contiguous.cc b/plugins/tools/selectiontools/kis_tool_select_contiguous.cc index 34acfb5865..ed61aa0271 100644 --- a/plugins/tools/selectiontools/kis_tool_select_contiguous.cc +++ b/plugins/tools/selectiontools/kis_tool_select_contiguous.cc @@ -1,250 +1,250 @@ /* * kis_tool_select_contiguous - part of Krayon^WKrita * * Copyright (c) 1999 Michael Koch * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2012 José Luis Vergara * Copyright (c) 2015 Michael Abrahams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_select_contiguous.h" #include #include #include #include #include #include #include #include #include #include "KoPointerEvent.h" #include "KoViewConverter.h" #include "kis_cursor.h" #include "kis_selection_manager.h" #include "kis_image.h" #include "canvas/kis_canvas2.h" #include "kis_layer.h" #include "kis_selection_options.h" #include "kis_paint_device.h" #include "kis_fill_painter.h" #include "kis_pixel_selection.h" #include "kis_selection_tool_helper.h" #include "kis_slider_spin_box.h" #include "tiles3/kis_hline_iterator.h" KisToolSelectContiguous::KisToolSelectContiguous(KoCanvasBase *canvas) : KisToolSelect(canvas, KisCursor::load("tool_contiguous_selection_cursor.png", 6, 6), i18n("Contiguous Area Selection")), m_fuzziness(20), m_sizemod(0), m_feather(0), m_limitToCurrentLayer(false) { setObjectName("tool_select_contiguous"); } KisToolSelectContiguous::~KisToolSelectContiguous() { } void KisToolSelectContiguous::activate(ToolActivation toolActivation, const QSet &shapes) { KisToolSelect::activate(toolActivation, shapes); m_configGroup = KSharedConfig::openConfig()->group(toolId()); } void KisToolSelectContiguous::beginPrimaryAction(KoPointerEvent *event) { KisToolSelectBase::beginPrimaryAction(event); KisPaintDeviceSP dev; if (!currentNode() || !(dev = currentNode()->projection()) || !currentNode()->visible() || !selectionEditable()) { event->ignore(); return; } if (KisToolSelect::selectionDidMove()) { return; } QApplication::setOverrideCursor(KisCursor::waitCursor()); QPoint pos = convertToImagePixelCoordFloored(event); QRect rc = currentImage()->bounds(); KisFillPainter fillpainter(dev); fillpainter.setHeight(rc.height()); fillpainter.setWidth(rc.width()); fillpainter.setFillThreshold(m_fuzziness); fillpainter.setFeather(m_feather); fillpainter.setSizemod(m_sizemod); - KisImageWSP image = currentImage(); + KisImageSP image = currentImage(); KisPaintDeviceSP sourceDevice = m_limitToCurrentLayer ? dev : image->projection(); - image->lock(); + image->barrierLock(); KisSelectionSP selection = fillpainter.createFloodSelection(pos.x(), pos.y(), sourceDevice); image->unlock(); // If we're not antialiasing, threshold the entire selection if (!antiAliasSelection()) { QRect r = selection->selectedExactRect(); if (r.isValid()) { KisHLineIteratorSP selectionIt = selection->pixelSelection()->createHLineIteratorNG(r.x(), r.y(), r.width()); for (qint32 y = 0; y < r.height(); y++) { do { if (selectionIt->rawData()[0] > 0) { selection->pixelSelection()->colorSpace()->setOpacity(selectionIt->rawData(), OPACITY_OPAQUE_U8, 1); } } while (selectionIt->nextPixel()); selectionIt->nextRow(); } } } KisCanvas2 * kisCanvas = dynamic_cast(canvas()); if (!kisCanvas || !selection->pixelSelection()) { QApplication::restoreOverrideCursor(); return; } selection->pixelSelection()->invalidateOutlineCache(); KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Select Contiguous Area")); helper.selectPixelSelection(selection->pixelSelection(), selectionAction()); QApplication::restoreOverrideCursor(); } void KisToolSelectContiguous::paint(QPainter &painter, const KoViewConverter &converter) { Q_UNUSED(painter); Q_UNUSED(converter); } void KisToolSelectContiguous::slotSetFuzziness(int fuzziness) { m_fuzziness = fuzziness; m_configGroup.writeEntry("fuzziness", fuzziness); } void KisToolSelectContiguous::slotSetSizemod(int sizemod) { m_sizemod = sizemod; m_configGroup.writeEntry("sizemod", sizemod); } void KisToolSelectContiguous::slotSetFeather(int feather) { m_feather = feather; m_configGroup.writeEntry("feather", feather); } QWidget* KisToolSelectContiguous::createOptionWidget() { KisToolSelectBase::createOptionWidget(); KisSelectionOptions *selectionWidget = selectionOptionWidget(); QVBoxLayout * l = dynamic_cast(selectionWidget->layout()); Q_ASSERT(l); if (l) { QGridLayout * gridLayout = new QGridLayout(); l->insertLayout(1, gridLayout); QLabel * lbl = new QLabel(i18n("Fuzziness: "), selectionWidget); gridLayout->addWidget(lbl, 0, 0, 1, 1); KisSliderSpinBox *input = new KisSliderSpinBox(selectionWidget); Q_CHECK_PTR(input); input->setObjectName("fuzziness"); input->setRange(1, 100); input->setSingleStep(1); input->setExponentRatio(2); gridLayout->addWidget(input, 0, 1, 1, 1); lbl = new QLabel(i18n("Grow/shrink selection: "), selectionWidget); gridLayout->addWidget(lbl, 1, 0, 1, 1); KisSliderSpinBox *sizemod = new KisSliderSpinBox(selectionWidget); Q_CHECK_PTR(sizemod); sizemod->setObjectName("sizemod"); //grow/shrink selection sizemod->setRange(-40, 40); sizemod->setSingleStep(1); gridLayout->addWidget(sizemod, 1, 1, 1, 1); lbl = new QLabel(i18n("Feathering radius: "), selectionWidget); gridLayout->addWidget(lbl, 2, 0, 1, 1); KisSliderSpinBox *feather = new KisSliderSpinBox(selectionWidget); Q_CHECK_PTR(feather); feather->setObjectName("feathering"); feather->setRange(0, 40); feather->setSingleStep(1); gridLayout->addWidget(feather, 2, 1, 1, 1); connect (input , SIGNAL(valueChanged(int)), this, SLOT(slotSetFuzziness(int))); connect (sizemod, SIGNAL(valueChanged(int)), this, SLOT(slotSetSizemod(int))); connect (feather, SIGNAL(valueChanged(int)), this, SLOT(slotSetFeather(int))); QCheckBox* limitToCurrentLayer = new QCheckBox(i18n("Limit to current layer"), selectionWidget); l->insertWidget(4, limitToCurrentLayer); connect (limitToCurrentLayer, SIGNAL(stateChanged(int)), this, SLOT(slotLimitToCurrentLayer(int))); // load configuration settings into tool options input->setValue(m_configGroup.readEntry("fuzziness", 20)); // fuzziness sizemod->setValue( m_configGroup.readEntry("sizemod", 0)); //grow/shrink sizemod->setSuffix(i18n(" px")); feather->setValue(m_configGroup.readEntry("feather", 0)); feather->setSuffix(i18n(" px")); limitToCurrentLayer->setChecked(m_configGroup.readEntry("limitToCurrentLayer", false)); } return selectionWidget; } void KisToolSelectContiguous::slotLimitToCurrentLayer(int state) { if (state == Qt::PartiallyChecked) return; m_limitToCurrentLayer = (state == Qt::Checked); m_configGroup.writeEntry("limitToCurrentLayer", state); } void KisToolSelectContiguous::resetCursorStyle() { if (selectionAction() == SELECTION_ADD) { useCursor(KisCursor::load("tool_contiguous_selection_cursor_add.png", 6, 6)); } else if (selectionAction() == SELECTION_SUBTRACT) { useCursor(KisCursor::load("tool_contiguous_selection_cursor_sub.png", 6, 6)); } else { KisToolSelect::resetCursorStyle(); } } diff --git a/sdk/tests/stroke_testing_utils.cpp b/sdk/tests/stroke_testing_utils.cpp index ec3bda982a..0bdbc1ea9a 100644 --- a/sdk/tests/stroke_testing_utils.cpp +++ b/sdk/tests/stroke_testing_utils.cpp @@ -1,369 +1,369 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "stroke_testing_utils.h" #include #include #include #include #include #include #include #include #include "kis_canvas_resource_provider.h" #include "kis_image.h" #include "kis_paint_device.h" #include "kis_paint_layer.h" #include "kis_group_layer.h" #include #include "testutil.h" KisImageSP utils::createImage(KisUndoStore *undoStore, const QSize &imageSize) { QRect imageRect(0,0,imageSize.width(),imageSize.height()); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(undoStore, imageRect.width(), imageRect.height(), cs, "stroke test"); KisPaintLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisPaintLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); KisPaintLayerSP paintLayer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE_U8); KisPaintLayerSP paintLayer4 = new KisPaintLayer(image, "paint4", OPACITY_OPAQUE_U8); KisPaintLayerSP paintLayer5 = new KisPaintLayer(image, "paint5", OPACITY_OPAQUE_U8); - image->lock(); + image->barrierLock(); image->addNode(paintLayer1); image->addNode(paintLayer2); image->addNode(paintLayer3); image->addNode(paintLayer4); image->addNode(paintLayer5); image->unlock(); return image; } KoCanvasResourceProvider* utils::createResourceManager(KisImageWSP image, KisNodeSP node, const QString &presetFileName) { KoCanvasResourceProvider *manager = new KoCanvasResourceProvider(); KisViewManager::initializeResourceManager(manager); QVariant i; i.setValue(KoColor(Qt::black, image->colorSpace())); manager->setResource(KoCanvasResourceProvider::ForegroundColor, i); i.setValue(KoColor(Qt::white, image->colorSpace())); manager->setResource(KoCanvasResourceProvider::BackgroundColor, i); i.setValue(static_cast(0)); manager->setResource(KisCanvasResourceProvider::CurrentPattern, i); manager->setResource(KisCanvasResourceProvider::CurrentGradient, i); manager->setResource(KisCanvasResourceProvider::CurrentGeneratorConfiguration, i); if(!node) { node = image->root(); while(node && !dynamic_cast(node.data())) { node = node->firstChild(); } Q_ASSERT(node && dynamic_cast(node.data())); } i.setValue(node); manager->setResource(KisCanvasResourceProvider::CurrentKritaNode, i); KisPaintOpPresetSP preset; if (!presetFileName.isEmpty()) { QString fullFileName = TestUtil::fetchDataFileLazy(presetFileName); preset = KisPaintOpPresetSP(new KisPaintOpPreset(fullFileName)); bool presetValid = preset->load(); Q_ASSERT(presetValid); Q_UNUSED(presetValid); i.setValue(preset); manager->setResource(KisCanvasResourceProvider::CurrentPaintOpPreset, i); } i.setValue(COMPOSITE_OVER); manager->setResource(KisCanvasResourceProvider::CurrentCompositeOp, i); i.setValue(false); manager->setResource(KisCanvasResourceProvider::MirrorHorizontal, i); i.setValue(false); manager->setResource(KisCanvasResourceProvider::MirrorVertical, i); i.setValue(1.0); manager->setResource(KisCanvasResourceProvider::Opacity, i); i.setValue(1.0); manager->setResource(KisCanvasResourceProvider::HdrExposure, i); return manager; } utils::StrokeTester::StrokeTester(const QString &name, const QSize &imageSize, const QString &presetFilename) : m_name(name), m_imageSize(imageSize), m_presetFilename(presetFilename), m_numIterations(1), m_baseFuzziness(1) { } utils::StrokeTester::~StrokeTester() { } void utils::StrokeTester::setNumIterations(int value) { m_numIterations = value; } void utils::StrokeTester::setBaseFuzziness(int value) { m_baseFuzziness = value; } void utils::StrokeTester::testSimpleStroke() { testOneStroke(false, true, false, true); } int utils::StrokeTester::lastStrokeTime() const { return m_strokeTime; } void utils::StrokeTester::test() { testOneStroke(false, false, false); testOneStroke(false, true, false); testOneStroke(true, false, false); testOneStroke(true, true, false); // The same but with updates (compare against projection) testOneStroke(false, false, false, true); testOneStroke(false, true, false, true); testOneStroke(true, false, false, true); testOneStroke(true, true, false, true); // The same, but with an external layer testOneStroke(false, false, true); testOneStroke(false, true, true); testOneStroke(true, false, true); testOneStroke(true, true, true); } void utils::StrokeTester::benchmark() { // not cancelled, indirect painting, internal, no updates, no qimage doStroke(false, false, false, false); } void utils::StrokeTester::testSimpleStrokeNoVerification() { doStroke(false, false, true, false); } void utils::StrokeTester::testOneStroke(bool cancelled, bool indirectPainting, bool externalLayer, bool testUpdates) { // TODO: indirectPainting option is not used anymore! The real value is // taken from the preset! QString testName = formatTestName(m_name, cancelled, indirectPainting, externalLayer); dbgKrita << "Testcase:" << testName << "(compare against " << (testUpdates ? "projection" : "layer") << ")"; QImage resultImage; resultImage = doStroke(cancelled, externalLayer, testUpdates); QImage refImage; refImage.load(referenceFile(testName)); QPoint temp; if(!TestUtil::compareQImages(temp, refImage, resultImage, m_baseFuzziness, m_baseFuzziness)) { refImage.save(dumpReferenceFile(testName)); resultImage.save(resultFile(testName)); QFAIL("Images do not coincide"); } } QString utils::StrokeTester::formatTestName(const QString &baseName, bool cancelled, bool indirectPainting, bool externalLayer) { QString result = baseName; result += "_" + m_presetFilename; result += indirectPainting ? "_indirect" : "_incremental"; result += cancelled ? "_cancelled" : "_finished"; result += externalLayer ? "_external" : "_internal"; return result; } QString utils::StrokeTester::referenceFile(const QString &testName) { QString path = QString(FILES_DATA_DIR) + QDir::separator() + m_name + QDir::separator(); path += testName; path += ".png"; return path; } QString utils::StrokeTester::dumpReferenceFile(const QString &testName) { QString path = QString(FILES_OUTPUT_DIR) + QDir::separator(); path += testName; path += "_expected"; path += ".png"; return path; } QString utils::StrokeTester::resultFile(const QString &testName) { QString path = QString(FILES_OUTPUT_DIR) + QDir::separator(); path += testName; path += ".png"; return path; } QImage utils::StrokeTester::doStroke(bool cancelled, bool externalLayer, bool testUpdates, bool needQImage) { KisImageSP image = utils::createImage(0, m_imageSize); KoCanvasResourceProvider *manager = utils::createResourceManager(image, 0, m_presetFilename); KisNodeSP currentNode; for (int i = 0; i < m_numIterations; i++) { modifyResourceManager(manager, image, i); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image, image->rootLayer()->firstChild(), manager); if(externalLayer) { KisNodeSP externalNode = new KisPaintLayer(0, "extlyr", OPACITY_OPAQUE_U8, image->colorSpace()); resources->setCurrentNode(externalNode); Q_ASSERT(resources->currentNode() == externalNode); } initImage(image, resources->currentNode(), i); QElapsedTimer strokeTime; strokeTime.start(); KisStrokeStrategy *stroke = createStroke(resources, image); m_strokeId = image->startStroke(stroke); addPaintingJobs(image, resources, i); if(!cancelled) { image->endStroke(m_strokeId); } else { image->cancelStroke(m_strokeId); } image->waitForDone(); m_strokeTime = strokeTime.elapsed(); currentNode = resources->currentNode(); } beforeCheckingResult(image, currentNode); QImage resultImage; if(needQImage) { KisPaintDeviceSP device = testUpdates ? image->projection() : currentNode->paintDevice(); resultImage = device->convertToQImage(0, 0, 0, image->width(), image->height()); } image = 0; delete manager; return resultImage; } void utils::StrokeTester::modifyResourceManager(KoCanvasResourceProvider *manager, KisImageWSP image, int iteration) { Q_UNUSED(iteration); modifyResourceManager(manager, image); } void utils::StrokeTester::modifyResourceManager(KoCanvasResourceProvider *manager, KisImageWSP image) { Q_UNUSED(manager); Q_UNUSED(image); } void utils::StrokeTester::initImage(KisImageWSP image, KisNodeSP activeNode, int iteration) { Q_UNUSED(iteration); initImage(image, activeNode); } void utils::StrokeTester::initImage(KisImageWSP image, KisNodeSP activeNode) { Q_UNUSED(image); Q_UNUSED(activeNode); } void utils::StrokeTester::addPaintingJobs(KisImageWSP image, KisResourcesSnapshotSP resources, int iteration) { Q_UNUSED(iteration); addPaintingJobs(image, resources); } void utils::StrokeTester::addPaintingJobs(KisImageWSP image, KisResourcesSnapshotSP resources) { Q_UNUSED(image); Q_UNUSED(resources); } void utils::StrokeTester::beforeCheckingResult(KisImageWSP image, KisNodeSP activeNode) { Q_UNUSED(image); Q_UNUSED(activeNode); }