diff --git a/krita/pics/tools/SVG/16/dark_tool_guided_selection.svg b/krita/pics/tools/SVG/16/dark_tool_guided_selection.svg new file mode 100644 index 0000000000..a1445972ea --- /dev/null +++ b/krita/pics/tools/SVG/16/dark_tool_guided_selection.svg @@ -0,0 +1,54 @@ + + + + + + + image/svg+xml + + + + + Andrei Rudenko + + + + + + + + + + + + + + + + diff --git a/krita/pics/tools/SVG/16/light_tool_guided_selection.svg b/krita/pics/tools/SVG/16/light_tool_guided_selection.svg new file mode 100644 index 0000000000..6fdca8b192 --- /dev/null +++ b/krita/pics/tools/SVG/16/light_tool_guided_selection.svg @@ -0,0 +1,54 @@ + + + + + + + image/svg+xml + + + + + Andrei Rudenko + + + + + + + + + + + + + + + + diff --git a/libs/image/CMakeLists.txt b/libs/image/CMakeLists.txt index 6e1acda047..a47f551f8c 100644 --- a/libs/image/CMakeLists.txt +++ b/libs/image/CMakeLists.txt @@ -1,392 +1,393 @@ 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 commands_new/KisMergeLabeledLayersCommand.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_box_kernel.cpp kis_gaussian_kernel.cpp kis_edge_detection_kernel.cpp kis_cubic_curve.cpp kis_default_bounds.cpp kis_default_bounds_node_wrapper.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 kritapigment kritacommand kritawidgetutils kritametadata kritaresources 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/kis_box_kernel.cpp b/libs/image/kis_box_kernel.cpp new file mode 100644 index 0000000000..0abf6e8c5a --- /dev/null +++ b/libs/image/kis_box_kernel.cpp @@ -0,0 +1,160 @@ +/* + * 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_box_kernel.h" + +#include "kis_global.h" +#include "kis_convolution_kernel.h" +#include +#include +#include + +int KisBoxKernel::kernelSizeFromRadius(qreal radius) +{ + return 2*radius+1; +} + + +Eigen::Matrix +KisBoxKernel::createHorizontalMatrix(qreal radius) +{ + int kernelSize = kernelSizeFromRadius(radius); + Eigen::Matrix matrix(1, kernelSize); + + for (int x = 0; x < kernelSize; x++) { + matrix(0, x) = 1/kernelSize; + } + + return matrix; +} + +Eigen::Matrix +KisBoxKernel::createVerticalMatrix(qreal radius) +{ + int kernelSize = kernelSizeFromRadius(radius); + Eigen::Matrix matrix(kernelSize, 1); + + for (int y = 0; y < kernelSize; y++) { + matrix(y, 0) = 1/kernelSize; + } + + return matrix; +} + +KisConvolutionKernelSP +KisBoxKernel::createHorizontalKernel(qreal radius) +{ + Eigen::Matrix matrix = createHorizontalMatrix(radius); + return KisConvolutionKernel::fromMatrix(matrix, 0, matrix.sum()); +} + +KisConvolutionKernelSP +KisBoxKernel::createVerticalKernel(qreal radius) +{ + Eigen::Matrix matrix = createVerticalMatrix(radius); + return KisConvolutionKernel::fromMatrix(matrix, 0, matrix.sum()); +} + +KisConvolutionKernelSP +KisBoxKernel::createUniform2DKernel(qreal xRadius, qreal yRadius) +{ + Eigen::Matrix h = createHorizontalMatrix(xRadius); + Eigen::Matrix v = createVerticalMatrix(yRadius); + + Eigen::Matrix uni = v * h; + return KisConvolutionKernel::fromMatrix(uni, 0, uni.sum()); +} + + +void KisBoxKernel::applyBox(KisPaintDeviceSP device, + const QRect& rect, + qreal xRadius, qreal yRadius, + const QBitArray &channelFlags, + KoUpdater *progressUpdater, + bool createTransaction, + KisConvolutionBorderOp borderOp) +{ + QPoint srcTopLeft = rect.topLeft(); + + + if (KisConvolutionPainter::supportsFFTW()) { + KisConvolutionPainter painter(device, KisConvolutionPainter::FFTW); + painter.setChannelFlags(channelFlags); + painter.setProgress(progressUpdater); + + KisConvolutionKernelSP kernel2D = KisBoxKernel::createUniform2DKernel(xRadius, yRadius); + + QScopedPointer transaction; + if (createTransaction && painter.needsTransaction(kernel2D)) { + transaction.reset(new KisTransaction(device)); + } + + painter.applyMatrix(kernel2D, device, srcTopLeft, srcTopLeft, rect.size(), borderOp); + + } else if (xRadius > 0.0 && yRadius > 0.0) { + KisPaintDeviceSP interm = new KisPaintDevice(device->colorSpace()); + interm->prepareClone(device); + + KisConvolutionKernelSP kernelHoriz = KisBoxKernel::createHorizontalKernel(xRadius); + KisConvolutionKernelSP kernelVertical = KisBoxKernel::createVerticalKernel(yRadius); + + qreal verticalCenter = qreal(kernelVertical->height()) / 2.0; + + KisConvolutionPainter horizPainter(interm); + horizPainter.setChannelFlags(channelFlags); + horizPainter.setProgress(progressUpdater); + horizPainter.applyMatrix(kernelHoriz, device, + srcTopLeft - QPoint(0, ceil(verticalCenter)), + srcTopLeft - QPoint(0, ceil(verticalCenter)), + rect.size() + QSize(0, 2 * ceil(verticalCenter)), borderOp); + + + KisConvolutionPainter verticalPainter(device); + verticalPainter.setChannelFlags(channelFlags); + verticalPainter.setProgress(progressUpdater); + verticalPainter.applyMatrix(kernelVertical, interm, srcTopLeft, srcTopLeft, rect.size(), borderOp); + + } else if (xRadius > 0.0) { + KisConvolutionPainter painter(device); + painter.setChannelFlags(channelFlags); + painter.setProgress(progressUpdater); + + KisConvolutionKernelSP kernelHoriz = KisBoxKernel::createHorizontalKernel(xRadius); + + QScopedPointer transaction; + if (createTransaction && painter.needsTransaction(kernelHoriz)) { + transaction.reset(new KisTransaction(device)); + } + + painter.applyMatrix(kernelHoriz, device, srcTopLeft, srcTopLeft, rect.size(), borderOp); + + } else if (yRadius > 0.0) { + KisConvolutionPainter painter(device); + painter.setChannelFlags(channelFlags); + painter.setProgress(progressUpdater); + + KisConvolutionKernelSP kernelVertical = KisBoxKernel::createVerticalKernel(yRadius); + + QScopedPointer transaction; + if (createTransaction && painter.needsTransaction(kernelVertical)) { + transaction.reset(new KisTransaction(device)); + } + + painter.applyMatrix(kernelVertical, device, srcTopLeft, srcTopLeft, rect.size(), borderOp); + } +} diff --git a/libs/image/kis_box_kernel.h b/libs/image/kis_box_kernel.h new file mode 100644 index 0000000000..504c8855d1 --- /dev/null +++ b/libs/image/kis_box_kernel.h @@ -0,0 +1,59 @@ +/* + * 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_BOX_KERNEL_H +#define __KIS_BOX_KERNEL_H + +#include "kritaimage_export.h" +#include "kis_types.h" +#include "kis_convolution_painter.h" + +#include + +class QRect; + +class KRITAIMAGE_EXPORT KisBoxKernel +{ +public: + static Eigen::Matrix + createHorizontalMatrix(qreal radius); + + static Eigen::Matrix + createVerticalMatrix(qreal radius); + + static KisConvolutionKernelSP + createHorizontalKernel(qreal radius); + + static KisConvolutionKernelSP + createVerticalKernel(qreal radius); + + static KisConvolutionKernelSP + createUniform2DKernel(qreal xRadius, qreal yRadius); + + static int kernelSizeFromRadius(qreal radius); + + static void applyBox(KisPaintDeviceSP device, + const QRect& rect, + qreal xRadius, qreal yRadius, + const QBitArray &channelFlags, + KoUpdater *updater, + bool createTransaction = false, + KisConvolutionBorderOp borderOp = BORDER_REPEAT); +}; + +#endif /* __KIS_BOX_KERNEL_H */ diff --git a/libs/ui/widgets/kis_selection_options.cc b/libs/ui/widgets/kis_selection_options.cc index c574096ebc..c6854f4866 100644 --- a/libs/ui/widgets/kis_selection_options.cc +++ b/libs/ui/widgets/kis_selection_options.cc @@ -1,314 +1,313 @@ /* * Copyright (c) 2005 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_selection_options.h" #include #include #include #include #include #include #include #include "kis_types.h" #include "kis_layer.h" #include "kis_image.h" #include "kis_selection.h" #include "kis_paint_device.h" #include "canvas/kis_canvas2.h" #include "KisViewManager.h" #include "kis_signal_compressor.h" #include "kis_shape_controller.h" #include "kis_canvas2.h" #include "KisDocument.h" #include "kis_dummies_facade_base.h" #include #include KisSelectionOptions::KisSelectionOptions(KisCanvas2 * /*canvas*/) : m_colorLabelsCompressor(900, KisSignalCompressor::FIRST_INACTIVE) { m_page = new WdgSelectionOptions(this); Q_CHECK_PTR(m_page); QVBoxLayout * l = new QVBoxLayout(this); l->addWidget(m_page); l->addSpacerItem(new QSpacerItem(0,0, QSizePolicy::Preferred, QSizePolicy::Expanding)); l->setContentsMargins(0,0,0,0); m_mode = new QButtonGroup(this); m_mode->addButton(m_page->pixel, PIXEL_SELECTION); m_mode->addButton(m_page->shape, SHAPE_PROTECTION); m_action = new QButtonGroup(this); m_action->addButton(m_page->add, SELECTION_ADD); m_action->addButton(m_page->subtract, SELECTION_SUBTRACT); m_action->addButton(m_page->replace, SELECTION_REPLACE); m_action->addButton(m_page->intersect, SELECTION_INTERSECT); m_action->addButton(m_page->symmetricdifference, SELECTION_SYMMETRICDIFFERENCE); m_page->pixel->setGroupPosition(KoGroupButton::GroupLeft); m_page->shape->setGroupPosition(KoGroupButton::GroupRight); m_page->pixel->setIcon(KisIconUtils::loadIcon("select_pixel")); m_page->shape->setIcon(KisIconUtils::loadIcon("select_shape")); m_page->add->setGroupPosition(KoGroupButton::GroupCenter); m_page->subtract->setGroupPosition(KoGroupButton::GroupCenter); m_page->replace->setGroupPosition(KoGroupButton::GroupLeft); m_page->intersect->setGroupPosition(KoGroupButton::GroupCenter); m_page->symmetricdifference->setGroupPosition(KoGroupButton::GroupRight); m_page->add->setIcon(KisIconUtils::loadIcon("selection_add")); m_page->subtract->setIcon(KisIconUtils::loadIcon("selection_subtract")); m_page->replace->setIcon(KisIconUtils::loadIcon("selection_replace")); m_page->intersect->setIcon(KisIconUtils::loadIcon("selection_intersect")); m_page->symmetricdifference->setIcon(KisIconUtils::loadIcon("selection_symmetric_difference")); m_page->cmbSampleLayersMode->addItem(sampleLayerModeToUserString(SAMPLE_LAYERS_MODE_CURRENT), SAMPLE_LAYERS_MODE_CURRENT); m_page->cmbSampleLayersMode->addItem(sampleLayerModeToUserString(SAMPLE_LAYERS_MODE_ALL), SAMPLE_LAYERS_MODE_ALL); m_page->cmbSampleLayersMode->addItem(sampleLayerModeToUserString(SAMPLE_LAYERS_MODE_COLOR_LABELED), SAMPLE_LAYERS_MODE_COLOR_LABELED); m_page->cmbSampleLayersMode->setEditable(false); m_page->cmbColorLabels->setModes(false, false); connect(m_mode, SIGNAL(buttonClicked(int)), this, SIGNAL(modeChanged(int))); connect(m_action, SIGNAL(buttonClicked(int)), this, SIGNAL(actionChanged(int))); connect(m_mode, SIGNAL(buttonClicked(int)), this, SLOT(hideActionsForSelectionMode(int))); connect(m_page->chkAntiAliasing, SIGNAL(toggled(bool)), this, SIGNAL(antiAliasSelectionChanged(bool))); connect(m_page->cmbColorLabels, SIGNAL(selectedColorsChanged()), this, SIGNAL(selectedColorLabelsChanged())); connect(m_page->cmbSampleLayersMode, SIGNAL(currentIndexChanged(int)), this, SLOT(slotSampleLayersModeChanged(int))); KConfigGroup cfg = KSharedConfig::openConfig()->group("KisToolSelectBase"); m_page->chkAntiAliasing->setChecked(cfg.readEntry("antiAliasSelection", true)); connect(&m_colorLabelsCompressor, SIGNAL(timeout()), this, SLOT(slotUpdateAvailableColorLabels())); } KisSelectionOptions::~KisSelectionOptions() { } int KisSelectionOptions::action() { return m_action->checkedId(); } void KisSelectionOptions::setAction(int action) { QAbstractButton* button = m_action->button(action); KIS_SAFE_ASSERT_RECOVER_RETURN(button); button->setChecked(true); } void KisSelectionOptions::setMode(int mode) { QAbstractButton* button = m_mode->button(mode); KIS_SAFE_ASSERT_RECOVER_RETURN(button); button->setChecked(true); hideActionsForSelectionMode(mode); } void KisSelectionOptions::setAntiAliasSelection(bool value) { m_page->chkAntiAliasing->setChecked(value); } void KisSelectionOptions::setSampleLayersMode(QString mode) { if (mode != SAMPLE_LAYERS_MODE_ALL && mode != SAMPLE_LAYERS_MODE_COLOR_LABELED && mode != SAMPLE_LAYERS_MODE_CURRENT) { mode = SAMPLE_LAYERS_MODE_CURRENT; } setCmbSampleLayersMode(mode); } void KisSelectionOptions::enablePixelOnlySelectionMode() { setMode(PIXEL_SELECTION); disableSelectionModeOption(); } void KisSelectionOptions::setColorLabelsEnabled(bool enabled) { if (enabled) { m_page->cmbColorLabels->show(); m_page->cmbSampleLayersMode->show(); } else { m_page->cmbColorLabels->hide(); m_page->cmbSampleLayersMode->hide(); } } void KisSelectionOptions::updateActionButtonToolTip(int action, const QKeySequence &shortcut) { const QString shortcutString = shortcut.toString(QKeySequence::NativeText); QString toolTipText; switch ((SelectionAction)action) { case SELECTION_DEFAULT: case SELECTION_REPLACE: toolTipText = shortcutString.isEmpty() ? i18nc("@info:tooltip", "Replace") : i18nc("@info:tooltip", "Replace (%1)", shortcutString); m_action->button(SELECTION_REPLACE)->setToolTip(toolTipText); break; case SELECTION_ADD: toolTipText = shortcutString.isEmpty() ? i18nc("@info:tooltip", "Add") : i18nc("@info:tooltip", "Add (%1)", shortcutString); m_action->button(SELECTION_ADD)->setToolTip(toolTipText); break; case SELECTION_SUBTRACT: toolTipText = shortcutString.isEmpty() ? i18nc("@info:tooltip", "Subtract") : i18nc("@info:tooltip", "Subtract (%1)", shortcutString); m_action->button(SELECTION_SUBTRACT)->setToolTip(toolTipText); break; case SELECTION_INTERSECT: toolTipText = shortcutString.isEmpty() ? i18nc("@info:tooltip", "Intersect") : i18nc("@info:tooltip", "Intersect (%1)", shortcutString); m_action->button(SELECTION_INTERSECT)->setToolTip(toolTipText); break; case SELECTION_SYMMETRICDIFFERENCE: toolTipText = shortcutString.isEmpty() ? i18nc("@info:tooltip", "Symmetric Difference") : i18nc("@info:tooltip", "Symmetric Difference (%1)", shortcutString); m_action->button(SELECTION_SYMMETRICDIFFERENCE)->setToolTip(toolTipText); break; } } void KisSelectionOptions::attachToImage(KisImageSP image, KisCanvas2* canvas) { m_image = image; m_canvas = canvas; activateConnectionToImage(); } void KisSelectionOptions::activateConnectionToImage() { if (m_image && m_canvas) { m_page->cmbColorLabels->updateAvailableLabels(m_image->root()); KIS_SAFE_ASSERT_RECOVER_RETURN(m_canvas); KisDocument *doc = m_canvas->imageView()->document(); KisShapeController *kritaShapeController = dynamic_cast(doc->shapeController()); KisDummiesFacadeBase* m_dummiesFacade = static_cast(kritaShapeController); if (m_dummiesFacade) { m_nodesUpdatesConnectionsStore.addConnection(m_dummiesFacade, SIGNAL(sigEndInsertDummy(KisNodeDummy*)), &m_colorLabelsCompressor, SLOT(start())); m_nodesUpdatesConnectionsStore.addConnection(m_dummiesFacade, SIGNAL(sigEndRemoveDummy()), &m_colorLabelsCompressor, SLOT(start())); m_nodesUpdatesConnectionsStore.addConnection(m_dummiesFacade, SIGNAL(sigDummyChanged(KisNodeDummy*)), &m_colorLabelsCompressor, SLOT(start())); } } } void KisSelectionOptions::deactivateConnectionToImage() { m_nodesUpdatesConnectionsStore.clear(); } //hide action buttons and antialiasing, if shape selection is active (actions currently don't work on shape selection) void KisSelectionOptions::hideActionsForSelectionMode(int mode) { const bool isPixelSelection = (mode == (int)PIXEL_SELECTION); m_page->chkAntiAliasing->setVisible(isPixelSelection); } void KisSelectionOptions::slotUpdateAvailableColorLabels() { if (m_image) { m_page->cmbColorLabels->updateAvailableLabels(m_image->root()); } } void KisSelectionOptions::slotSampleLayersModeChanged(int index) { QString newSampleLayersMode = m_page->cmbSampleLayersMode->itemData(index).toString(); m_page->cmbColorLabels->setEnabled(newSampleLayersMode == SAMPLE_LAYERS_MODE_COLOR_LABELED); emit sampleLayersModeChanged(newSampleLayersMode); } QString KisSelectionOptions::sampleLayerModeToUserString(QString sampleLayersModeId) { QString currentLayer = i18nc("Option in selection tool: take only the current layer into account when calculating the selection", "Current Layer"); if (sampleLayersModeId == SAMPLE_LAYERS_MODE_CURRENT) { return currentLayer; } else if (sampleLayersModeId == SAMPLE_LAYERS_MODE_ALL) { return i18nc("Option in selection tool: take all layers (merged) into account when calculating the selection", "All Layers"); } else if (sampleLayersModeId == SAMPLE_LAYERS_MODE_COLOR_LABELED) { return i18nc("Option in selection tool: take all layers that were marked with specific color labels (more precisely, all of them merged) " "into account when calculating the selection", "Color Labeled Layers"); } KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(false, currentLayer); return currentLayer; } void KisSelectionOptions::setCmbSampleLayersMode(QString sampleLayersModeId) { for (int i = 0; i < m_page->cmbSampleLayersMode->count(); i++) { if (m_page->cmbSampleLayersMode->itemData(i).toString() == sampleLayersModeId) { m_page->cmbSampleLayersMode->setCurrentIndex(i); break; } } m_page->cmbColorLabels->setEnabled(sampleLayersModeId == SAMPLE_LAYERS_MODE_COLOR_LABELED); } bool KisSelectionOptions::antiAliasSelection() { return m_page->chkAntiAliasing->isChecked(); } QList KisSelectionOptions::colorLabelsSelected() { return m_page->cmbColorLabels->selectedColors(); } QString KisSelectionOptions::sampleLayersMode() { return m_page->cmbSampleLayersMode->currentData().toString(); } void KisSelectionOptions::disableAntiAliasSelectionOption() { m_page->chkAntiAliasing->hide(); disconnect(m_page->pixel, SIGNAL(clicked()), m_page->chkAntiAliasing, SLOT(show())); } void KisSelectionOptions::disableSelectionModeOption() { m_page->lblMode->hide(); m_page->pixel->hide(); m_page->shape->hide(); } - diff --git a/plugins/tools/selectiontools/CMakeLists.txt b/plugins/tools/selectiontools/CMakeLists.txt index 8b9aff2c17..c94bc3ef02 100644 --- a/plugins/tools/selectiontools/CMakeLists.txt +++ b/plugins/tools/selectiontools/CMakeLists.txt @@ -1,35 +1,40 @@ set(kritaselectiontools_SOURCES selection_tools.cc kis_tool_select_rectangular.cc kis_tool_select_polygonal.cc kis_tool_select_elliptical.cc kis_tool_select_contiguous.cc kis_tool_select_outline.cc kis_tool_select_path.cc kis_tool_select_similar.cc kis_selection_modifier_mapper.cc KisMagneticWorker.cc KisToolSelectMagnetic.cc + kis_tool_select_guided.cpp + kis_tool_select_guided_options_widget.cpp ) +ki18n_wrap_ui(kritaselectiontools_SOURCES kis_tool_select_guided_options_widget.ui) + qt5_add_resources(kritaselectiontools_SOURCES selectiontools.qrc) add_library(kritaselectiontools MODULE ${kritaselectiontools_SOURCES}) generate_export_header(kritaselectiontools BASE_NAME kritaselectiontools) target_link_libraries(kritaselectiontools kritaui kritabasicflakes kritaimage) install(TARGETS kritaselectiontools DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install( FILES KisToolSelectPolygonal.action KisToolSelectElliptical.action KisToolSelectSimilar.action KisToolSelectContiguous.action KisToolSelectRectangular.action KisToolSelectOutline.action KisToolSelectPath.action KisToolSelectMagnetic.action + KisToolSelectGuided.action DESTINATION ${DATA_INSTALL_DIR}/krita/actions ) diff --git a/plugins/tools/selectiontools/KisToolSelectGuided.action b/plugins/tools/selectiontools/KisToolSelectGuided.action new file mode 100644 index 0000000000..6da9d839e1 --- /dev/null +++ b/plugins/tools/selectiontools/KisToolSelectGuided.action @@ -0,0 +1,6 @@ + + + + Guided Selection Tool + + diff --git a/plugins/tools/selectiontools/kis_inpaint.cpp b/plugins/tools/selectiontools/kis_inpaint.cpp new file mode 100644 index 0000000000..08b225f192 --- /dev/null +++ b/plugins/tools/selectiontools/kis_inpaint.cpp @@ -0,0 +1,994 @@ +/* + * Copyright (c) 2017 Eugene Ingerman + * + * This program is free software; you can 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. + */ + +/** + * Inpaint using the PatchMatch Algorithm + * + * | PatchMatch : A Randomized Correspondence Algorithm for Structural Image Editing + * | by Connelly Barnes and Eli Shechtman and Adam Finkelstein and Dan B Goldman + * | ACM Transactions on Graphics (Proc. SIGGRAPH), vol.28, aug-2009 + * + * Original author Xavier Philippeau + * Code adopted from: David Chatting https://github.com/davidchatting/PatchMatch + */ + +#include +#include +#include +#include + + +#include "kis_paint_device.h" +#include "kis_painter.h" +#include "kis_selection.h" + +#include "kis_debug.h" +#include "kis_paint_device_debug_utils.h" +//#include "kis_random_accessor_ng.h" + +#include +#include +#include +#include "KoColor.h" +#include "KoColorSpace.h" +#include "KoChannelInfo.h" +#include "KoMixColorsOp.h" +#include "KoColorModelStandardIds.h" +#include "KoColorSpaceRegistry.h" +#include "KoColorSpaceTraits.h" + +const int MAX_DIST = 65535; +const quint8 MASK_SET = 255; +const quint8 MASK_CLEAR = 0; + +class MaskedImage; //forward decl for the forward decl below +template float distance_impl(const MaskedImage& my, int x, int y, const MaskedImage& other, int xo, int yo); + + +class ImageView +{ + +protected: + quint8* m_data; + int m_imageWidth; + int m_imageHeight; + int m_pixelSize; + +public: + void Init(quint8* _data, int _imageWidth, int _imageHeight, int _pixelSize) + { + m_data = _data; + m_imageWidth = _imageWidth; + m_imageHeight = _imageHeight; + m_pixelSize = _pixelSize; + } + + ImageView() : m_data(nullptr) + + { + m_imageHeight = m_imageWidth = m_pixelSize = 0; + } + + + ImageView(quint8* _data, int _imageWidth, int _imageHeight, int _pixelSize) + { + Init(_data, _imageWidth, _imageHeight, _pixelSize); + } + + quint8* operator()(int x, int y) const + { + Q_ASSERT(m_data); + Q_ASSERT((x >= 0) && (x < m_imageWidth) && (y >= 0) && (y < m_imageHeight)); + return (m_data + x * m_pixelSize + y * m_imageWidth * m_pixelSize); + } + + ImageView& operator=(const ImageView& other) + { + if (this != &other) { + if (other.num_bytes() != num_bytes()) { + delete[] m_data; + m_data = nullptr; //to preserve invariance if next line throws exception + m_data = new quint8[other.num_bytes()]; + + } + std::copy(other.data(), other.data() + other.num_bytes(), m_data); + m_imageHeight = other.m_imageHeight; + m_imageWidth = other.m_imageWidth; + m_pixelSize = other.m_pixelSize; + } + return *this; + } + + //move assignment operator + ImageView& operator=(ImageView&& other) noexcept + { + if (this != &other) { + delete[] m_data; + m_data = nullptr; + Init(other.data(), other.m_imageWidth, other.m_imageHeight, other.m_pixelSize); + other.m_data = nullptr; + } + return *this; + } + + virtual ~ImageView() {} //this class doesn't own m_data, so it ain't going to delete it either. + + quint8* data(void) const + { + return m_data; + } + + inline int num_elements(void) const + { + return m_imageHeight * m_imageWidth; + } + + inline int num_bytes(void) const + { + return m_imageHeight * m_imageWidth * m_pixelSize; + } + + inline int pixel_size(void) const + { + return m_pixelSize; + } + + void saveToDevice(KisPaintDeviceSP outDev, QRect rect) + { + Q_ASSERT(outDev->colorSpace()->pixelSize() == (quint32) m_pixelSize); + outDev->writeBytes(m_data, rect); + } + + void DebugDump(const QString& fnamePrefix) + { + QRect imSize(QPoint(0, 0), QSize(m_imageWidth, m_imageHeight)); + const KoColorSpace* cs = (m_pixelSize == 1) ? + KoColorSpaceRegistry::instance()->alpha8() : (m_pixelSize == 3) ? KoColorSpaceRegistry::instance()->colorSpace("RGB", "U8", "") : + KoColorSpaceRegistry::instance()->colorSpace("RGBA", "U8", ""); + KisPaintDeviceSP dbout = new KisPaintDevice(cs); + saveToDevice(dbout, imSize); + KIS_DUMP_DEVICE_2(dbout, imSize, fnamePrefix, "./"); + } +}; + +class ImageData : public ImageView +{ + +public: + ImageData() : ImageView() {} + + void Init(int _imageWidth, int _imageHeight, int _pixelSize) + { + m_data = new quint8[ _imageWidth * _imageHeight * _pixelSize ]; + ImageView::Init(m_data, _imageWidth, _imageHeight, _pixelSize); + } + + ImageData(int _imageWidth, int _imageHeight, int _pixelSize) : ImageView() + { + Init(_imageWidth, _imageHeight, _pixelSize); + } + + void Init(KisPaintDeviceSP imageDev, const QRect& imageSize) + { + const KoColorSpace* cs = imageDev->colorSpace(); + m_pixelSize = cs->pixelSize(); + + m_data = new quint8[ imageSize.width()*imageSize.height()*cs->pixelSize() ]; + imageDev->readBytes(m_data, imageSize.x(), imageSize.y(), imageSize.width(), imageSize.height()); + ImageView::Init(m_data, imageSize.width(), imageSize.height(), m_pixelSize); + } + + ImageData(KisPaintDeviceSP imageDev, const QRect& imageSize) : ImageView() + { + Init(imageDev, imageSize); + } + + ~ImageData() override + { + delete[] m_data; //ImageData owns m_data, so it has to delete it + } + +}; + + + +class MaskedImage : public KisShared +{ +private: + + template friend float distance_impl(const MaskedImage& my, int x, int y, const MaskedImage& other, int xo, int yo); + + QRect imageSize; + int nChannels; + + const KoColorSpace* cs; + const KoColorSpace* csMask; + + ImageData maskData; + ImageData imageData; + + + void cacheImage(KisPaintDeviceSP imageDev, QRect rect) + { + cs = imageDev->colorSpace(); + nChannels = cs->channelCount(); + imageData.Init(imageDev, rect); + imageSize = rect; + } + + + void cacheMask(KisPaintDeviceSP maskDev, QRect rect) + { + Q_ASSERT(maskDev->colorSpace()->pixelSize() == 1); + csMask = maskDev->colorSpace(); + maskData.Init(maskDev, rect); + + //hard threshold for the initial mask + //may be optional. needs testing + std::for_each(maskData.data(), maskData.data() + maskData.num_bytes(), [](quint8 & v) { + v = (v > MASK_CLEAR) ? MASK_SET : MASK_CLEAR; + }); + } + + MaskedImage() {} + +public: + std::function< float(const MaskedImage&, int, int, const MaskedImage& , int , int ) > distance; + + void toPaintDevice(KisPaintDeviceSP imageDev, QRect rect) + { + imageData.saveToDevice(imageDev, rect); + } + + void DebugDump(const QString& name) + { + imageData.DebugDump(name + "_img"); + maskData.DebugDump(name + "_mask"); + } + + void clearMask(void) + { + std::fill(maskData.data(), maskData.data() + maskData.num_bytes(), MASK_CLEAR); + } + + void initialize(KisPaintDeviceSP _imageDev, KisPaintDeviceSP _maskDev, QRect _maskRect) + { + cacheImage(_imageDev, _maskRect); + cacheMask(_maskDev, _maskRect); + + //distance function is the only that needs to know the type + //For performance reasons we can't use functions provided by color space + KoID colorDepthId = _imageDev->colorSpace()->colorDepthId(); + + //Use RGB traits to assign actual pixel data types. + distance = &distance_impl; + + if( colorDepthId == Integer16BitsColorDepthID ) + distance = &distance_impl; +#ifdef HAVE_OPENEXR + if( colorDepthId == Float16BitsColorDepthID ) + distance = &distance_impl; +#endif + if( colorDepthId == Float32BitsColorDepthID ) + distance = &distance_impl; + + if( colorDepthId == Float64BitsColorDepthID ) + distance = &distance_impl; + } + + MaskedImage(KisPaintDeviceSP _imageDev, KisPaintDeviceSP _maskDev, QRect _maskRect) + { + initialize(_imageDev, _maskDev, _maskRect); + } + + void downsample2x(void) + { + int H = imageSize.height(); + int W = imageSize.width(); + int newW = W / 2, newH = H / 2; + + KisPaintDeviceSP imageDev = new KisPaintDevice(cs); + KisPaintDeviceSP maskDev = new KisPaintDevice(csMask); + imageDev->writeBytes(imageData.data(), 0, 0, W, H); + maskDev->writeBytes(maskData.data(), 0, 0, W, H); + + ImageData newImage(newW, newH, cs->pixelSize()); + ImageData newMask(newW, newH, 1); + + KoDummyUpdater updater; + KisTransformWorker worker(imageDev, 1. / 2., 1. / 2., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + &updater, KisFilterStrategyRegistry::instance()->value("Bicubic")); + worker.run(); + + KisTransformWorker workerMask(maskDev, 1. / 2., 1. / 2., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + &updater, KisFilterStrategyRegistry::instance()->value("Bicubic")); + workerMask.run(); + + imageDev->readBytes(newImage.data(), 0, 0, newW, newH); + maskDev->readBytes(newMask.data(), 0, 0, newW, newH); + imageData = std::move(newImage); + maskData = std::move(newMask); + + for (int i = 0; i < imageData.num_elements(); ++i) { + quint8* maskPix = maskData.data() + i * maskData.pixel_size(); + if (*maskPix == MASK_SET) { + for (int k = 0; k < imageData.pixel_size(); k++) + *(imageData.data() + i * imageData.pixel_size() + k) = 0; + } else { + *maskPix = MASK_CLEAR; + } + } + imageSize = QRect(0, 0, newW, newH); + } + + void upscale(int newW, int newH) + { + int H = imageSize.height(); + int W = imageSize.width(); + + ImageData newImage(newW, newH, cs->pixelSize()); + ImageData newMask(newW, newH, 1); + + QVector colors(nChannels, 0.f); + QVector v(nChannels, 0.f); + + for (int y = 0; y < newH; ++y) { + for (int x = 0; x < newW; ++x) { + + // original pixel + int xs = (x * W) / newW; + int ys = (y * H) / newH; + + // copy to new image + if (!isMasked(xs, ys)) { + std::copy(imageData(xs, ys), imageData(xs, ys) + imageData.pixel_size(), newImage(x, y)); + *newMask(x, y) = MASK_CLEAR; + } else { + std::fill(newImage(x, y), newImage(x, y) + newImage.pixel_size(), 0); + *newMask(x, y) = MASK_SET; + } + } + } + + imageData = std::move(newImage); + maskData = std::move(newMask); + imageSize = QRect(0, 0, newW, newH); + } + + QRect size() + { + return imageSize; + } + + KisSharedPtr copy(void) + { + KisSharedPtr clone = new MaskedImage(); + clone->imageSize = this->imageSize; + clone->nChannels = this->nChannels; + clone->maskData = this->maskData; + clone->imageData = this->imageData; + clone->cs = this->cs; + clone->csMask = this->csMask; + clone->distance = this->distance; + return clone; + } + + int countMasked(void) + { + int count = std::count_if(maskData.data(), maskData.data() + maskData.num_elements(), [](quint8 v) { + return v > MASK_CLEAR; + }); + return count; + } + + inline bool isMasked(int x, int y) + { + return (*maskData(x, y) > MASK_CLEAR); + } + + //returns true if the patch contains a masked pixel + bool containsMasked(int x, int y, int S) + { + for (int dy = -S; dy <= S; ++dy) { + int ys = y + dy; + if (ys < 0 || ys >= imageSize.height()) + continue; + + for (int dx = -S; dx <= S; ++dx) { + int xs = x + dx; + if (xs < 0 || xs >= imageSize.width()) + continue; + if (isMasked(xs, ys)) + return true; + } + } + return false; + } + + inline quint8 getImagePixelU8(int x, int y, int chan) const + { + return cs->scaleToU8(imageData(x, y), chan); + } + + inline QVector getImagePixels(int x, int y) const + { + QVector v(cs->channelCount()); + cs->normalisedChannelsValue(imageData(x, y), v); + return v; + } + + inline quint8* getImagePixel(int x, int y) + { + return imageData(x, y); + } + + inline void setImagePixels(int x, int y, QVector& value) + { + cs->fromNormalisedChannelsValue(imageData(x, y), value); + } + + inline void mixColors(std::vector< quint8* > pixels, std::vector< float > w, float wsum, quint8* dst) + { + const KoMixColorsOp* mixOp = cs->mixColorsOp(); + + size_t n = w.size(); + assert(pixels.size() == n); + std::vector< qint16 > weights; + weights.clear(); + + float dif = 0; + + float scale = 255 / (wsum + 0.001); + + for (auto& v : w) { + //compensated summation to increase accuracy + float v1 = v * scale + dif; + float v2 = std::round(v1); + dif = v1 - v2; + weights.push_back(v2); + } + + mixOp->mixColors(pixels.data(), weights.data(), n, dst); + } + + inline void setMask(int x, int y, quint8 v) + { + *(maskData(x, y)) = v; + } + + inline int channelCount(void) const + { + return cs->channelCount(); + } +}; + + +//Generic version of the distance function. produces distance between colors in the range [0, MAX_DIST]. This +//is a fast distance computation. More accurate, but very slow implementation is to use color space operations. +template float distance_impl(const MaskedImage& my, int x, int y, const MaskedImage& other, int xo, int yo) +{ + float dsq = 0; + quint32 nchannels = my.channelCount(); + quint8* v1 = my.imageData(x, y); + quint8* v2 = other.imageData(xo, yo); + + for (quint32 chan = 0; chan < nchannels; chan++) { + //It's very important not to lose precision in the next line + float v = ((float)(*((T*)v1 + chan)) - (float)(*((T*)v2 + chan))); + dsq += v * v; + } + return dsq / ( (float)KoColorSpaceMathsTraits::unitValue * (float)KoColorSpaceMathsTraits::unitValue / MAX_DIST ); +} + + +typedef KisSharedPtr MaskedImageSP; + +struct NNPixel { + int x; + int y; + int distance; +}; +typedef boost::multi_array NNArray_type; + +struct Vote_elem { + QVector channel_values; + float w; +}; +typedef boost::multi_array Vote_type; + + + +class NearestNeighborField : public KisShared +{ + +private: + template< typename T> T randomInt(T range) + { + return rand() % range; + } + + //compute initial value of the distance term + void initialize(void) + { + for (int y = 0; y < imSize.height(); y++) { + for (int x = 0; x < imSize.width(); x++) { + field[x][y].distance = distance(x, y, field[x][y].x, field[x][y].y); + + //if the distance is "infinity", try to find a better link + int iter = 0; + const int maxretry = 20; + while (field[x][y].distance == MAX_DIST && iter < maxretry) { + field[x][y].x = randomInt(imSize.width() + 1); + field[x][y].y = randomInt(imSize.height() + 1); + field[x][y].distance = distance(x, y, field[x][y].x, field[x][y].y); + iter++; + } + } + } + } + + void init_similarity_curve(void) + { + float s_zero = 0.999; + float t_halfmax = 0.10; + + float x = (s_zero - 0.5) * 2; + float invtanh = 0.5 * std::log((1. + x) / (1. - x)); + float coef = invtanh / t_halfmax; + + similarity.resize(MAX_DIST + 1); + for (int i = 0; i < (int)similarity.size(); i++) { + float t = (float)i / similarity.size(); + similarity[i] = 0.5 - 0.5 * std::tanh(coef * (t - t_halfmax)); + } + } + + +private: + int patchSize; //patch size +public: + MaskedImageSP input; + MaskedImageSP output; + QRect imSize; + NNArray_type field; + std::vector similarity; + quint32 nColors; + QList channels; + +public: + NearestNeighborField(const MaskedImageSP _input, MaskedImageSP _output, int _patchsize) : patchSize(_patchsize), input(_input), output(_output) + { + imSize = input->size(); + field.resize(boost::extents[imSize.width()][imSize.height()]); + init_similarity_curve(); + + nColors = input->channelCount(); //only color count, doesn't include alpha channels + } + + void randomize(void) + { + for (int y = 0; y < imSize.height(); y++) { + for (int x = 0; x < imSize.width(); x++) { + field[x][y].x = randomInt(imSize.width() + 1); + field[x][y].y = randomInt(imSize.height() + 1); + field[x][y].distance = MAX_DIST; + } + } + initialize(); + } + + //initialize field from an existing (possibly smaller) nearest neighbor field + void initialize(const NearestNeighborField& nnf) + { + float xscale = qreal(imSize.width()) / nnf.imSize.width(); + float yscale = qreal(imSize.height()) / nnf.imSize.height(); + + for (int y = 0; y < imSize.height(); y++) { + for (int x = 0; x < imSize.width(); x++) { + int xlow = std::min((int)(x / xscale), nnf.imSize.width() - 1); + int ylow = std::min((int)(y / yscale), nnf.imSize.height() - 1); + + field[x][y].x = nnf.field[xlow][ylow].x * xscale; + field[x][y].y = nnf.field[xlow][ylow].y * yscale; + field[x][y].distance = MAX_DIST; + } + } + initialize(); + } + + //multi-pass NN-field minimization (see "PatchMatch" paper referenced above - page 4) + void minimize(int pass) + { + int min_x = 0; + int min_y = 0; + int max_x = imSize.width() - 1; + int max_y = imSize.height() - 1; + + for (int i = 0; i < pass; i++) { + //scanline order + for (int y = min_y; y < max_y; y++) + for (int x = min_x; x <= max_x; x++) + if (field[x][y].distance > 0) + minimizeLink(x, y, 1); + + //reverse scanline order + for (int y = max_y; y >= min_y; y--) + for (int x = max_x; x >= min_x; x--) + if (field[x][y].distance > 0) + minimizeLink(x, y, -1); + } + } + + void minimizeLink(int x, int y, int dir) + { + int xp, yp, dp; + + //Propagation Left/Right + if (x - dir > 0 && x - dir < imSize.width()) { + xp = field[x - dir][y].x + dir; + yp = field[x - dir][y].y; + dp = distance(x, y, xp, yp); + if (dp < field[x][y].distance) { + field[x][y].x = xp; + field[x][y].y = yp; + field[x][y].distance = dp; + } + } + + //Propagation Up/Down + if (y - dir > 0 && y - dir < imSize.height()) { + xp = field[x][y - dir].x; + yp = field[x][y - dir].y + dir; + dp = distance(x, y, xp, yp); + if (dp < field[x][y].distance) { + field[x][y].x = xp; + field[x][y].y = yp; + field[x][y].distance = dp; + } + } + + //Random search + int wi = std::max(output->size().width(), output->size().height()); + int xpi = field[x][y].x; + int ypi = field[x][y].y; + while (wi > 0) { + xp = xpi + randomInt(2 * wi) - wi; + yp = ypi + randomInt(2 * wi) - wi; + xp = std::max(0, std::min(output->size().width() - 1, xp)); + yp = std::max(0, std::min(output->size().height() - 1, yp)); + + dp = distance(x, y, xp, yp); + if (dp < field[x][y].distance) { + field[x][y].x = xp; + field[x][y].y = yp; + field[x][y].distance = dp; + } + wi /= 2; + } + } + + //compute distance between two patches + int distance(int x, int y, int xp, int yp) + { + float distance = 0; + float wsum = 0; + float ssdmax = nColors * 255 * 255; + + //for each pixel in the source patch + for (int dy = -patchSize; dy <= patchSize; dy++) { + for (int dx = -patchSize; dx <= patchSize; dx++) { + wsum += ssdmax; + int xks = x + dx; + int yks = y + dy; + + if (xks < 0 || xks >= input->size().width()) { + distance += ssdmax; + continue; + } + + if (yks < 0 || yks >= input->size().height()) { + distance += ssdmax; + continue; + } + + //cannot use masked pixels as a valid source of information + if (input->isMasked(xks, yks)) { + distance += ssdmax; + continue; + } + + //corresponding pixel in target patch + int xkt = xp + dx; + int ykt = yp + dy; + if (xkt < 0 || xkt >= output->size().width()) { + distance += ssdmax; + continue; + } + if (ykt < 0 || ykt >= output->size().height()) { + distance += ssdmax; + continue; + } + + //cannot use masked pixels as a valid source of information + if (output->isMasked(xkt, ykt)) { + distance += ssdmax; + continue; + } + + //SSD distance between pixels + float ssd = input->distance(*input, xks, yks, *output, xkt, ykt); + distance += ssd; + + } + } + return (int)(MAX_DIST * (distance / wsum)); + } + + static MaskedImageSP ExpectationMaximization(KisSharedPtr TargetToSource, int level, int radius, QList& pyramid); + + static void ExpectationStep(KisSharedPtr nnf, MaskedImageSP source, MaskedImageSP target, bool upscale); + + void EM_Step(MaskedImageSP source, MaskedImageSP target, int R, bool upscaled); +}; +typedef KisSharedPtr NearestNeighborFieldSP; + + +class Inpaint +{ +private: + KisPaintDeviceSP devCache; + MaskedImageSP initial; + NearestNeighborFieldSP nnf_TargetToSource; + NearestNeighborFieldSP nnf_SourceToTarget; + int radius; + QList pyramid; + + +public: + Inpaint(KisPaintDeviceSP dev, KisPaintDeviceSP devMask, int _radius, QRect maskRect) + : devCache(dev) + , initial(new MaskedImage(dev, devMask, maskRect)) + , radius(_radius) + { + } + MaskedImageSP patch(void); + MaskedImageSP patch_simple(void); +}; + + + +MaskedImageSP Inpaint::patch() +{ + MaskedImageSP source = initial->copy(); + + pyramid.append(initial); + + QRect size = source->size(); + + //qDebug() << "countMasked: " << source->countMasked() << "\n"; + while ((size.width() > radius) && (size.height() > radius) && source->countMasked() > 0) { + source->downsample2x(); + //source->DebugDump("Pyramid"); + //qDebug() << "countMasked1: " << source->countMasked() << "\n"; + pyramid.append(source->copy()); + size = source->size(); + } + int maxlevel = pyramid.size(); + //qDebug() << "MaxLevel: " << maxlevel << "\n"; + + // The initial target is the same as the smallest source. + // We consider that this target contains no masked pixels + MaskedImageSP target = source->copy(); + target->clearMask(); + + //recursively building nearest neighbor field + for (int level = maxlevel - 1; level > 0; level--) { + source = pyramid.at(level); + + if (level == maxlevel - 1) { + //random initial guess + nnf_TargetToSource = new NearestNeighborField(target, source, radius); + nnf_TargetToSource->randomize(); + } else { + // then, we use the rebuilt (upscaled) target + // and reuse the previous NNF as initial guess + + NearestNeighborFieldSP new_nnf_rev = new NearestNeighborField(target, source, radius); + new_nnf_rev->initialize(*nnf_TargetToSource); + nnf_TargetToSource = new_nnf_rev; + } + + //Build an upscaled target by EM-like algorithm (see "PatchMatch" paper referenced above - page 6) + target = NearestNeighborField::ExpectationMaximization(nnf_TargetToSource, level, radius, pyramid); + //target->DebugDump( "target" ); + } + return target; +} + + +//EM-Like algorithm (see "PatchMatch" - page 6) +//Returns a float sized target image +MaskedImageSP NearestNeighborField::ExpectationMaximization(NearestNeighborFieldSP nnf_TargetToSource, int level, int radius, QList& pyramid) +{ + int iterEM = std::min(2 * level, 4); + int iterNNF = std::min(5, 1 + level); + + MaskedImageSP source = nnf_TargetToSource->output; + MaskedImageSP target = nnf_TargetToSource->input; + MaskedImageSP newtarget = nullptr; + + //EM loop + for (int emloop = 1; emloop <= iterEM; emloop++) { + //set the new target as current target + if (!newtarget.isNull()) { + nnf_TargetToSource->input = newtarget; + target = newtarget; + newtarget = nullptr; + } + + for (int x = 0; x < target->size().width(); ++x) { + for (int y = 0; y < target->size().height(); ++y) { + if (!source->containsMasked(x, y, radius)) { + nnf_TargetToSource->field[x][y].x = x; + nnf_TargetToSource->field[x][y].y = y; + nnf_TargetToSource->field[x][y].distance = 0; + } + } + } + + //minimize the NNF + nnf_TargetToSource->minimize(iterNNF); + + //Now we rebuild the target using best patches from source + MaskedImageSP newsource = nullptr; + bool upscaled = false; + + // Instead of upsizing the final target, we build the last target from the next level source image + // So the final target is less blurry (see "Space-Time Video Completion" - page 5) + if (level >= 1 && (emloop == iterEM)) { + newsource = pyramid.at(level - 1); + QRect sz = newsource->size(); + newtarget = target->copy(); + newtarget->upscale(sz.width(), sz.height()); + upscaled = true; + } else { + newsource = pyramid.at(level); + newtarget = target->copy(); + upscaled = false; + } + //EM Step + + //EM_Step(newsource, newtarget, radius, upscaled); + ExpectationStep(nnf_TargetToSource, newsource, newtarget, upscaled); + } + + return newtarget; +} + + +void NearestNeighborField::ExpectationStep(NearestNeighborFieldSP nnf, MaskedImageSP source, MaskedImageSP target, bool upscale) +{ + //int*** field = nnf->field; + int R = nnf->patchSize; + if (upscale) + R *= 2; + + int H_nnf = nnf->input->size().height(); + int W_nnf = nnf->input->size().width(); + int H_target = target->size().height(); + int W_target = target->size().width(); + int H_source = source->size().height(); + int W_source = source->size().width(); + + std::vector< quint8* > pixels; + std::vector< float > weights; + pixels.reserve(R * R); + weights.reserve(R * R); + for (int x = 0 ; x < W_target ; ++x) { + for (int y = 0 ; y < H_target; ++y) { + float wsum = 0; + pixels.clear(); + weights.clear(); + + + if (!source->containsMasked(x, y, R + 4) /*&& upscale*/) { + //speedup computation by copying parts that are not masked. + pixels.push_back(source->getImagePixel(x, y)); + weights.push_back(1.f); + target->mixColors(pixels, weights, 1.f, target->getImagePixel(x, y)); + } else { + for (int dx = -R ; dx <= R; ++dx) { + for (int dy = -R ; dy <= R ; ++dy) { + // xpt,ypt = center pixel of the target patch + int xpt = x + dx; + int ypt = y + dy; + + int xst, yst; + float w; + + if (!upscale) { + if (xpt < 0 || xpt >= W_nnf || ypt < 0 || ypt >= H_nnf) + continue; + + xst = nnf->field[xpt][ypt].x; + yst = nnf->field[xpt][ypt].y; + float dp = nnf->field[xpt][ypt].distance; + // similarity measure between the two patches + w = nnf->similarity[dp]; + + } else { + if (xpt < 0 || (xpt / 2) >= W_nnf || ypt < 0 || (ypt / 2) >= H_nnf) + continue; + xst = 2 * nnf->field[xpt / 2][ypt / 2].x + (xpt % 2); + yst = 2 * nnf->field[xpt / 2][ypt / 2].y + (ypt % 2); + float dp = nnf->field[xpt / 2][ypt / 2].distance; + // similarity measure between the two patches + w = nnf->similarity[dp]; + } + + int xs = xst - dx; + int ys = yst - dy; + + if (xs < 0 || xs >= W_source || ys < 0 || ys >= H_source) + continue; + + if (source->isMasked(xs, ys)) + continue; + + pixels.push_back(source->getImagePixel(xs, ys)); + weights.push_back(w); + wsum += w; + } + } + + if (wsum < 1) + continue; + + target->mixColors(pixels, weights, wsum, target->getImagePixel(x, y)); + } + } + } +} + +QRect getMaskBoundingBox(KisPaintDeviceSP maskDev) +{ + QRect maskRect = maskDev->nonDefaultPixelArea(); + return maskRect; +} + + +QRect patchImage(const KisPaintDeviceSP imageDev, const KisPaintDeviceSP maskDev, int patchRadius, int accuracy) +{ + QRect maskRect = getMaskBoundingBox(maskDev); + QRect imageRect = imageDev->exactBounds(); + + float scale = 1.0 + (accuracy / 25.0); //higher accuracy means we include more surrounding area around the patch. Minimum 2x padding. + int dx = maskRect.width() * scale; + int dy = maskRect.height() * scale; + maskRect.adjust(-dx, -dy, dx, dy); + maskRect = maskRect.intersected(imageRect); + + if (!maskRect.isEmpty()) { + Inpaint inpaint(imageDev, maskDev, patchRadius, maskRect); + MaskedImageSP output = inpaint.patch(); + output->toPaintDevice(imageDev, maskRect); + } + + return maskRect; +} + diff --git a/plugins/tools/selectiontools/kis_tool_select_guided.cpp b/plugins/tools/selectiontools/kis_tool_select_guided.cpp new file mode 100644 index 0000000000..bd23b602a4 --- /dev/null +++ b/plugins/tools/selectiontools/kis_tool_select_guided.cpp @@ -0,0 +1,146 @@ +#include "kis_tool_select_guided.h" + +#include "QApplication" +#include "QPainterPath" + +#include +#include +#include +#include "kis_canvas2.h" +#include "kis_cursor.h" +#include "kis_painter.h" +#include "kis_paintop_preset.h" + +#include "kundo2magicstring.h" +#include "kundo2stack.h" +#include "commands_new/kis_transaction_based_command.h" +#include "kis_transaction.h" + +#include "kis_processing_applicator.h" +#include "kis_datamanager.h" + +#include "KoColorSpaceRegistry.h" + +#include "kis_tool_select_guided_options_widget.h" +#include "libs/image/kis_paint_device_debug_utils.h" + +#include "kis_paint_layer.h" +#include "kis_algebra_2d.h" +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "kis_painter.h" +#include +#include "canvas/kis_canvas2.h" +#include "kis_pixel_selection.h" +#include "kis_selection_tool_helper.h" +#include "kis_tool_select_guided_options_widget.h" + +#include "kis_algebra_2d.h" + +#include "KisHandlePainterHelper.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + + +KisToolSelectGuided::KisToolSelectGuided(KoCanvasBase *canvas) + : KisToolSelect(canvas, + KisCursor::crossCursor(), + i18n("Guided Selection")), + m_finished(false), m_filterRadius(3), m_epsilon(70) +{ } + +/*void KisToolSelectGuided::GuidedSelectGenerate() +{ + KisCanvas2 *kisCanvas = dynamic_cast(canvas()); + KIS_ASSERT_RECOVER_RETURN(kisCanvas); + kisCanvas->updateCanvas(); + setMode(KisTool::HOVER_MODE); + + if (mode == PIXEL_SELECTION){ + KisPaintDeviceSP image = KisPaintDeviceSP(new KisPaintDevice(currentImage()->projection())); + + image.ConvertTo(KoColorSpaceRegistry::instance()->rgb32()); + + KisLodTransformScalar t(image); + const qreal BoxBlurAmount = t.scale(m_filterEpsilon ? m_filterEpsilon.toDouble() : 1.0); + QRect applyRect(QPoint(0, 0), image.width(),image.height()); + const QRect boxNeedRect = this->neededRect(applyRect, config, originalimage->defaultBounds()->currentLevelOfDetail()); + + KisSequentialConstIterator it(imageDev, QRect(0, 0, image.width(), image.height())); + + for (int y = 0; y < image.height(); y++) { + for (int x = 0; x < image.width(); x++) { + it.nextPixel(); + const quint8* pixel = it.rawDataConst(); + for (int chan = 0; chan < 4; ++chan) { + image &img = image[chan]; + *(img.scanLine(y) + x) = cs->scaleToU8(pixel, chan); + } + } + } + } +} + +QRect KisToolSelectGuided::neededRect(const QRect & rect, int lod) const +{ + KisLodTransformScalar t(lod); + + const int halfSize = m_filterRadius ? KisGaussianKernel::kernelSizeFromRadius(t.scale(m_filterRadius.toFloat())) / 2 : 5; + + return rect.adjusted( -halfSize * 2, -halfSize * 2, halfSize * 2, halfSize * 2); +} + +QRect KisToolSelectGuided::changedRect(const QRect & rect, int lod) const +{ + KisLodTransformScalar t(lod); + + const int halfSize = m_filterRadius ? KisGaussianKernel::kernelSizeFromRadius(t.scale(m_filterRadius.toFloat())) / 2 : 5; + + return rect.adjusted( -halfSize, -halfSize, halfSize, halfSize); +}*/ + +void KisToolSelectGuided::paint(QPainter& gc, const KoViewConverter &converter) +{ +} + +QWidget * KisToolSelectGuided::createOptionWidget() +{ + KisCanvas2 * kiscanvas = dynamic_cast(canvas()); + + m_d->optionsWidget = new KisToolSelectGuidedOptionsWidget(kiscanvas->viewManager()->canvasResourceProvider(), 0); + m_d->optionsWidget->setObjectName(toolId() + "option widget"); + + return m_d->optionsWidget; +} diff --git a/plugins/tools/selectiontools/kis_tool_select_guided.h b/plugins/tools/selectiontools/kis_tool_select_guided.h new file mode 100644 index 0000000000..5b2eb5275a --- /dev/null +++ b/plugins/tools/selectiontools/kis_tool_select_guided.h @@ -0,0 +1,86 @@ +#ifndef KIS_TOOL_SELECT_GUIDED_H_ +#define KIS_TOOL_SELECT_GUIDED_H_ + +#include +#include +#include +#include +#include "kis_tool_select_guided_options_widget.h" + +#include "kis_tool_paint.h" +#include "KisSelectionToolFactoryBase.h" +#include "KisToolPaintFactoryBase.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include "selection_tools.h" +#include "kis_selection_tool_config_widget_helper.h" + +#include + +class KActionCollection; +class QPainterPath; +class KoCanvasBase; +class KisSpacingInfomation; + +class KisToolSelectGuided : public KisToolSelect +{ + Q_OBJECT + +public: + KisToolSelectGuided(KoCanvasBase * canvas); + ~KisToolSelectGuided() override = default; + + void paint(QPainter& gc, const KoViewConverter &converter) override; + + QWidget * createOptionWidget() override; +public Q_SLOTS: + +protected: + +private: + //const QRect& applyRect; + //const QRect& neededRect; + struct Private; + const QScopedPointer m_d; + bool m_finished; + int m_filterRadius; + qreal m_epsilon; + bool activate_selection_painting; +}; + +struct KisToolSelectGuided::Private { + KisToolSelectGuidedOptionsWidget *optionsWidget = nullptr; +}; + +class KisToolSelectGuidedFactory : public KisSelectionToolFactoryBase +{ +public: + KisToolSelectGuidedFactory() + : KisSelectionToolFactoryBase("KisToolSelectGuided") + { + setToolTip(i18n("Guided Selection Tool")); + setSection(TOOL_TYPE_SELECTION); + setIconName(koIconNameCStr("tool_guided_selection")); + setPriority(9); + setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); + } + + ~KisToolSelectGuidedFactory() override { } + + KoToolBase * createTool(KoCanvasBase *canvas) override + { + return new KisToolSelectGuided(canvas); + } +}; + + +#endif // KIS_TOOL_SELECT_GUIDED_H diff --git a/plugins/tools/selectiontools/kis_tool_select_guided_options_widget.cpp b/plugins/tools/selectiontools/kis_tool_select_guided_options_widget.cpp new file mode 100644 index 0000000000..8517607dff --- /dev/null +++ b/plugins/tools/selectiontools/kis_tool_select_guided_options_widget.cpp @@ -0,0 +1,59 @@ +#include "kis_tool_select_guided_options_widget.h" + +#include "ui_kis_tool_select_guided_options_widget.h" + +#include +#include "KisPaletteModel.h" + +#include "kis_config.h" +#include +#include "kis_canvas_resource_provider.h" + + +struct KisToolSelectGuidedOptionsWidget::Private { + Private() + { + } + + Ui_KisToolSelectGuidedOptionsWidget *ui; + + int getKernelRadius(void) + { + return ui->kernel_radius->value(); + } + qreal getEpsilon(void) + { + return ui->epsilon->value(); + } + bool getSelectActive(void) + { + return ui->activate_selection_painting->checkState(); + } +}; + +KisToolSelectGuidedOptionsWidget::KisToolSelectGuidedOptionsWidget(KisCanvasResourceProvider */*provider*/, QWidget *parent) + : QWidget(parent), + m_d(new Private) +{ + m_d->ui = new Ui_KisToolSelectGuidedOptionsWidget(); + m_d->ui->setupUi(this); +} + +KisToolSelectGuidedOptionsWidget::~KisToolSelectGuidedOptionsWidget() +{ +} + +int KisToolSelectGuidedOptionsWidget::getKernelRadius() +{ + return m_d->getKernelRadius(); +} + +qreal KisToolSelectGuidedOptionsWidget::getEpsilon() +{ + return m_d->getEpsilon(); +} + +bool KisToolSelectGuidedOptionsWidget::getSelectActive() +{ + return m_d->getSelectActive(); +} diff --git a/plugins/tools/selectiontools/kis_tool_select_guided_options_widget.h b/plugins/tools/selectiontools/kis_tool_select_guided_options_widget.h new file mode 100644 index 0000000000..2f8afef838 --- /dev/null +++ b/plugins/tools/selectiontools/kis_tool_select_guided_options_widget.h @@ -0,0 +1,29 @@ +#ifndef __KIS_TOOL_SELECT_GUIDED_OPTIONS_WIDGET_H +#define __KIS_TOOL_SELECT_GUIDED_OPTIONS_WIDGET_H + +#include +#include +#include + +#include "kis_types.h" + +class KisCanvasResourceProvider; +class KoColor; + +class KisToolSelectGuidedOptionsWidget : public QWidget +{ + Q_OBJECT +public: + KisToolSelectGuidedOptionsWidget(KisCanvasResourceProvider *provider, QWidget *parent); + ~KisToolSelectGuidedOptionsWidget() override; + + int getKernelRadius(void); + qreal getEpsilon(void); + bool getSelectActive(void); + +private: + struct Private; + const QScopedPointer m_d; +}; + +#endif /* __KIS_TOOL_SELECT_GUIDED_OPTIONS_WIDGET_H */ diff --git a/plugins/tools/selectiontools/kis_tool_select_guided_options_widget.ui b/plugins/tools/selectiontools/kis_tool_select_guided_options_widget.ui new file mode 100644 index 0000000000..598ed64289 --- /dev/null +++ b/plugins/tools/selectiontools/kis_tool_select_guided_options_widget.ui @@ -0,0 +1,128 @@ + + + KisToolSelectGuidedOptionsWidget + + + + 0 + 0 + 338 + 245 + + + + + + + Selection technique utilizing guided filter provided by Kaiming He. + + + true + + + + + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + Kernel Radius + + + patchRadiusLabel + + + + + + + + 0 + 0 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 1 + + + 100 + + + + + + + + 0 + 0 + + + + Epsilon + + + patchRadiusLabel + + + + + + + + 0 + 0 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 1.000000000000000 + + + 10.000000000000000 + + + 1.000000000000000 + + + + + + + Activate Selection Painting? + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + diff --git a/plugins/tools/selectiontools/selection_tools.cc b/plugins/tools/selectiontools/selection_tools.cc index fb0af54745..1132c9158c 100644 --- a/plugins/tools/selectiontools/selection_tools.cc +++ b/plugins/tools/selectiontools/selection_tools.cc @@ -1,61 +1,63 @@ /* * selection_tools.cc -- Part of Krita * * Copyright (c) 2004 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 "selection_tools.h" #include #include #include #include "KoToolRegistry.h" #include "kis_global.h" #include "kis_types.h" #include "kis_tool_select_outline.h" #include "kis_tool_select_polygonal.h" #include "kis_tool_select_rectangular.h" #include "kis_tool_select_contiguous.h" #include "kis_tool_select_elliptical.h" #include "kis_tool_select_path.h" #include "kis_tool_select_similar.h" #include "KisToolSelectMagnetic.h" +#include "kis_tool_select_guided.h" K_PLUGIN_FACTORY_WITH_JSON(SelectionToolsFactory, "kritaselectiontools.json", registerPlugin();) SelectionTools::SelectionTools(QObject *parent, const QVariantList &) : QObject(parent) { KoToolRegistry::instance()->add(new KisToolSelectOutlineFactory()); KoToolRegistry::instance()->add(new KisToolSelectPolygonalFactory()); KoToolRegistry::instance()->add(new KisToolSelectRectangularFactory()); KoToolRegistry::instance()->add(new KisToolSelectEllipticalFactory()); KoToolRegistry::instance()->add(new KisToolSelectContiguousFactory()); KoToolRegistry::instance()->add(new KisToolSelectPathFactory()); KoToolRegistry::instance()->add(new KisToolSelectSimilarFactory()); KoToolRegistry::instance()->add(new KisToolSelectMagneticFactory()); + KoToolRegistry::instance()->add(new KisToolSelectGuidedFactory()); } SelectionTools::~SelectionTools() { } #include "selection_tools.moc"