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 @@
+
+
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 @@
+
+
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"