diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt
index da4bcbfbdd..cd21452b6b 100644
--- a/benchmarks/CMakeLists.txt
+++ b/benchmarks/CMakeLists.txt
@@ -1,90 +1,91 @@
set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
include_directories(
${CMAKE_SOURCE_DIR}/sdk/tests
${CMAKE_SOURCE_DIR}/libs/pigment
${CMAKE_SOURCE_DIR}/libs/pigment/compositeops
)
include_directories(SYSTEM
${EIGEN3_INCLUDE_DIR}
${Boost_INCLUDE_DIRS}
)
set(LINK_VC_LIB)
if(HAVE_VC)
include_directories(${Vc_INCLUDE_DIR})
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Vc_DEFINITIONS}")
set(LINK_VC_LIB ${Vc_LIBRARIES})
endif()
macro_add_unittest_definitions()
########### next target ###############
set(kis_datamanager_benchmark_SRCS kis_datamanager_benchmark.cpp)
set(kis_hiterator_benchmark_SRCS kis_hline_iterator_benchmark.cpp)
set(kis_viterator_benchmark_SRCS kis_vline_iterator_benchmark.cpp)
set(kis_random_iterator_benchmark_SRCS kis_random_iterator_benchmark.cpp)
set(kis_projection_benchmark_SRCS kis_projection_benchmark.cpp)
set(kis_bcontrast_benchmark_SRCS kis_bcontrast_benchmark.cpp)
set(kis_blur_benchmark_SRCS kis_blur_benchmark.cpp)
set(kis_level_filter_benchmark_SRCS kis_level_filter_benchmark.cpp)
set(kis_painter_benchmark_SRCS kis_painter_benchmark.cpp)
set(kis_stroke_benchmark_SRCS kis_stroke_benchmark.cpp)
set(kis_fast_math_benchmark_SRCS kis_fast_math_benchmark.cpp)
set(kis_floodfill_benchmark_SRCS kis_floodfill_benchmark.cpp)
set(kis_gradient_benchmark_SRCS kis_gradient_benchmark.cpp)
set(kis_mask_generator_benchmark_SRCS kis_mask_generator_benchmark.cpp)
set(kis_low_memory_benchmark_SRCS kis_low_memory_benchmark.cpp)
set(kis_filter_selections_benchmark_SRCS kis_filter_selections_benchmark.cpp)
if (UNIX)
#set(kis_composition_benchmark_SRCS kis_composition_benchmark.cpp)
endif()
set(kis_thumbnail_benchmark_SRCS kis_thumbnail_benchmark.cpp)
krita_add_benchmark(KisDatamanagerBenchmark TESTNAME krita-benchmarks-KisDataManager ${kis_datamanager_benchmark_SRCS})
krita_add_benchmark(KisHLineIteratorBenchmark TESTNAME krita-benchmarks-KisHLineIterator ${kis_hiterator_benchmark_SRCS})
krita_add_benchmark(KisVLineIteratorBenchmark TESTNAME krita-benchmarks-KisVLineIterator ${kis_viterator_benchmark_SRCS})
krita_add_benchmark(KisRandomIteratorBenchmark TESTNAME krita-benchmarks-KisRandomIterator ${kis_random_iterator_benchmark_SRCS})
krita_add_benchmark(KisProjectionBenchmark TESTNAME krita-benchmarks-KisProjectionBenchmark ${kis_projection_benchmark_SRCS})
krita_add_benchmark(KisBContrastBenchmark TESTNAME krita-benchmarks-KisBContrastBenchmark ${kis_bcontrast_benchmark_SRCS})
krita_add_benchmark(KisBlurBenchmark TESTNAME krita-benchmarks-KisBlurBenchmark ${kis_blur_benchmark_SRCS})
krita_add_benchmark(KisLevelFilterBenchmark TESTNAME krita-benchmarks-KisLevelFilterBenchmark ${kis_level_filter_benchmark_SRCS})
krita_add_benchmark(KisPainterBenchmark TESTNAME krita-benchmarks-KisPainterBenchmark ${kis_painter_benchmark_SRCS})
krita_add_benchmark(KisStrokeBenchmark TESTNAME krita-benchmarks-KisStrokeBenchmark ${kis_stroke_benchmark_SRCS})
krita_add_benchmark(KisFastMathBenchmark TESTNAME krita-benchmarks-KisFastMath ${kis_fast_math_benchmark_SRCS})
krita_add_benchmark(KisFloodfillBenchmark TESTNAME krita-benchmarks-KisFloodFill ${kis_floodfill_benchmark_SRCS})
krita_add_benchmark(KisGradientBenchmark TESTNAME krita-benchmarks-KisGradientFill ${kis_gradient_benchmark_SRCS})
krita_add_benchmark(KisMaskGeneratorBenchmark TESTNAME krita-benchmarks-KisMaskGenerator ${kis_mask_generator_benchmark_SRCS})
krita_add_benchmark(KisLowMemoryBenchmark TESTNAME krita-benchmarks-KisLowMemory ${kis_low_memory_benchmark_SRCS})
krita_add_benchmark(KisFilterSelectionsBenchmark TESTNAME krita-image-KisFilterSelectionsBenchmark ${kis_filter_selections_benchmark_SRCS})
if(UNIX)
#krita_add_benchmark(KisCompositionBenchmark TESTNAME krita-benchmarks-KisComposition ${kis_composition_benchmark_SRCS})
endif()
krita_add_benchmark(KisThumbnailBenchmark TESTNAME krita-benchmarks-KisThumbnail ${kis_thumbnail_benchmark_SRCS})
target_link_libraries(KisDatamanagerBenchmark kritaimage Qt5::Test)
target_link_libraries(KisHLineIteratorBenchmark kritaimage Qt5::Test)
target_link_libraries(KisVLineIteratorBenchmark kritaimage Qt5::Test)
target_link_libraries(KisRandomIteratorBenchmark kritaimage Qt5::Test)
target_link_libraries(KisProjectionBenchmark kritaimage kritaui Qt5::Test)
target_link_libraries(KisBContrastBenchmark kritaimage Qt5::Test)
target_link_libraries(KisBlurBenchmark kritaimage Qt5::Test)
target_link_libraries(KisLevelFilterBenchmark kritaimage Qt5::Test)
target_link_libraries(KisPainterBenchmark kritaimage Qt5::Test)
target_link_libraries(KisStrokeBenchmark kritaimage Qt5::Test)
target_link_libraries(KisFastMathBenchmark kritaimage Qt5::Test)
target_link_libraries(KisFloodfillBenchmark kritaimage Qt5::Test)
target_link_libraries(KisGradientBenchmark kritaimage Qt5::Test)
target_link_libraries(KisLowMemoryBenchmark kritaimage Qt5::Test)
target_link_libraries(KisFilterSelectionsBenchmark kritaimage Qt5::Test)
if(UNIX)
#target_link_libraries(KisCompositionBenchmark kritaimage Qt5::Test ${LINK_VC_LIB})
#if(HAVE_VC)
# set_property(TARGET KisCompositionBenchmark APPEND PROPERTY COMPILE_OPTIONS "${Vc_ARCHITECTURE_FLAGS}")
#endif()
endif()
target_link_libraries(KisMaskGeneratorBenchmark kritaimage Qt5::Test)
target_link_libraries(KisThumbnailBenchmark kritaimage Qt5::Test)
+
diff --git a/krita/pics/tools/SVG/16/dark_krita_tool_smart_patch.svg b/krita/pics/tools/SVG/16/dark_krita_tool_smart_patch.svg
new file mode 100644
index 0000000000..749fb5a986
--- /dev/null
+++ b/krita/pics/tools/SVG/16/dark_krita_tool_smart_patch.svg
@@ -0,0 +1,102 @@
+
+Bandage by Lee Mette from the Noun Project image/svg+xml Bandage by Lee Mette from the Noun Project
\ No newline at end of file
diff --git a/krita/pics/tools/SVG/16/light_krita_tool_smart_patch.svg b/krita/pics/tools/SVG/16/light_krita_tool_smart_patch.svg
new file mode 100644
index 0000000000..5371092ed9
--- /dev/null
+++ b/krita/pics/tools/SVG/16/light_krita_tool_smart_patch.svg
@@ -0,0 +1,102 @@
+
+Bandage by Lee Mette from the Noun Project image/svg+xml Bandage by Lee Mette from the Noun Project
\ No newline at end of file
diff --git a/krita/pics/tools/SVG/16/tools-svg-16-icons.qrc b/krita/pics/tools/SVG/16/tools-svg-16-icons.qrc
index f9574cd6a4..f440a8eeb1 100644
--- a/krita/pics/tools/SVG/16/tools-svg-16-icons.qrc
+++ b/krita/pics/tools/SVG/16/tools-svg-16-icons.qrc
@@ -1,80 +1,81 @@
dark_calligraphy.svg
dark_draw-text.svg
dark_format-fill-color.svg
dark_krita_draw_path.svg
dark_krita_tool_color_fill.svg
dark_krita_tool_color_picker.svg
dark_krita_tool_dyna.svg
dark_krita_tool_ellipse.svg
dark_krita_tool_freehand.svg
dark_krita_tool_freehandvector.svg
dark_krita_tool_gradient.svg
dark_krita_tool_grid.svg
dark_krita_tool_line.svg
dark_krita_tool_measure.svg
dark_krita_tool_move.svg
dark_krita_tool_multihand.svg
dark_krita_tool_polygon.svg
dark_krita_tool_rectangle.svg
dark_krita_tool_ruler_assistant.svg
dark_krita_tool_transform.svg
dark_pattern.svg
dark_polyline.svg
dark_select.svg
dark_tool_contiguous_selection.svg
dark_tool_crop.svg
dark_tool_elliptical_selection.svg
dark_tool_outline_selection.svg
dark_tool_pan.svg
dark_tool_path_selection.svg
dark_tool_perspectivegrid.svg
dark_tool_polygonal_selection.svg
dark_tool_rect_selection.svg
dark_tool_similar_selection.svg
dark_tool_zoom.svg
light_calligraphy.svg
light_draw-text.svg
light_format-fill-color.svg
light_krita_draw_path.svg
light_krita_tool_color_fill.svg
light_krita_tool_color_picker.svg
light_krita_tool_dyna.svg
light_krita_tool_ellipse.svg
light_krita_tool_freehand.svg
light_krita_tool_freehandvector.svg
light_krita_tool_gradient.svg
light_krita_tool_grid.svg
light_krita_tool_line.svg
light_krita_tool_measure.svg
light_krita_tool_move.svg
light_krita_tool_multihand.svg
light_krita_tool_polygon.svg
light_krita_tool_rectangle.svg
light_krita_tool_ruler_assistant.svg
light_krita_tool_transform.svg
light_pattern.svg
light_polyline.svg
light_select.svg
light_tool_contiguous_selection.svg
light_tool_crop.svg
light_tool_elliptical_selection.svg
light_tool_outline_selection.svg
light_tool_pan.svg
light_tool_path_selection.svg
light_tool_perspectivegrid.svg
light_tool_polygonal_selection.svg
light_tool_rect_selection.svg
light_tool_similar_selection.svg
light_tool_zoom.svg
dark_shape_handling.svg
dark_artistic_text.svg
light_artistic_text.svg
light_shape_handling.svg
dark_krita_tool_lazybrush.svg
light_krita_tool_lazybrush.svg
-
+ dark_krita_tool_smart_patch.svg
+ light_krita_tool_smart_patch.svg
diff --git a/libs/image/CMakeLists.txt b/libs/image/CMakeLists.txt
index 516254123b..693622f780 100644
--- a/libs/image/CMakeLists.txt
+++ b/libs/image/CMakeLists.txt
@@ -1,388 +1,389 @@
add_subdirectory( tests )
add_subdirectory( tiles3 )
include_directories(
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/metadata
${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_CURRENT_SOURCE_DIR}/recorder
${CMAKE_SOURCE_DIR}/sdk/tests
)
include_directories(SYSTEM
${EIGEN3_INCLUDE_DIR}
${Boost_INCLUDE_DIRS}
)
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/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_marker_painter.cpp
kis_progress_updater.cpp
brushengine/kis_paint_information.cc
brushengine/kis_random_source.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_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
commands/kis_deselect_global_selection_command.cpp
commands/kis_image_change_layers_command.cpp
commands/kis_image_command.cpp
commands/kis_image_set_projection_color_space_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_layer_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/kis_set_global_selection_command.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
processing/kis_do_nothing_processing_visitor.cpp
processing/kis_simple_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
filter/kis_filter.cc
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/kis_colorize_mask.cpp
lazybrush/kis_colorize_stroke_strategy.cpp
KisDelayedUpdateNodeInterface.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
kis_clone_info.cpp
kis_clone_layer.cpp
kis_colorspace_convert_visitor.cpp
kis_config_widget.cpp
kis_convolution_kernel.cc
kis_convolution_painter.cc
kis_gaussian_kernel.cpp
kis_cubic_curve.cpp
kis_default_bounds.cpp
kis_default_bounds_base.cpp
kis_effect_mask.cc
kis_fast_math.cpp
kis_fill_painter.cc
kis_filter_mask.cpp
kis_filter_strategy.cc
kis_transform_mask.cpp
kis_transform_mask_params_interface.cpp
kis_recalculate_transform_mask_job.cpp
kis_recalculate_generator_layer_job.cpp
kis_transform_mask_params_factory_registry.cpp
kis_safe_transform.cpp
kis_gradient_painter.cc
kis_gradient_shape_strategy.cpp
kis_cached_gradient_shape_strategy.cpp
kis_polygonal_gradient_shape_strategy.cpp
kis_iterator_ng.cpp
kis_async_merger.cpp
kis_merge_walker.cc
kis_updater_context.cpp
kis_update_job_item.cpp
kis_stroke_strategy_undo_command_based.cpp
kis_simple_stroke_strategy.cpp
kis_stroke_job_strategy.cpp
kis_stroke_strategy.cpp
kis_stroke.cpp
kis_strokes_queue.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
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
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_thread_safe_signal_compressor.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
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
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
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_inpaint_mask.cpp
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
metadata/kis_meta_data_entry.cc
metadata/kis_meta_data_filter.cc
metadata/kis_meta_data_filter_p.cc
metadata/kis_meta_data_filter_registry.cc
metadata/kis_meta_data_filter_registry_model.cc
metadata/kis_meta_data_io_backend.cc
metadata/kis_meta_data_merge_strategy.cc
metadata/kis_meta_data_merge_strategy_p.cc
metadata/kis_meta_data_merge_strategy_registry.cc
metadata/kis_meta_data_parser.cc
metadata/kis_meta_data_schema.cc
metadata/kis_meta_data_schema_registry.cc
metadata/kis_meta_data_store.cc
metadata/kis_meta_data_type_info.cc
metadata/kis_meta_data_validator.cc
metadata/kis_meta_data_value.cc
recorder/kis_action_recorder.cc
recorder/kis_macro.cc
recorder/kis_macro_player.cc
recorder/kis_node_query_path.cc
recorder/kis_play_info.cc
recorder/kis_recorded_action.cc
recorder/kis_recorded_action_factory_registry.cc
recorder/kis_recorded_action_load_context.cpp
recorder/kis_recorded_action_save_context.cpp
recorder/kis_recorded_filter_action.cpp
recorder/kis_recorded_fill_paint_action.cpp
recorder/kis_recorded_node_action.cc
recorder/kis_recorded_paint_action.cpp
recorder/kis_recorded_path_paint_action.cpp
recorder/kis_recorded_shape_paint_action.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_psd_layer_style.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
KisProofingConfiguration.cpp
)
set(einspline_SRCS
3rdparty/einspline/bspline_create.cpp
3rdparty/einspline/bspline_data.cpp
3rdparty/einspline/multi_bspline_create.cpp
3rdparty/einspline/nubasis.cpp
3rdparty/einspline/nubspline_create.cpp
3rdparty/einspline/nugrid.cpp
)
add_library(kritaimage SHARED ${kritaimage_LIB_SRCS} ${einspline_SRCS})
generate_export_header(kritaimage BASE_NAME kritaimage)
target_link_libraries(kritaimage
PUBLIC
kritaversion
kritawidgets
kritaglobal kritapsd
kritaodf kritapigment
kritacommand
kritawidgetutils
Qt5::Concurrent
)
target_link_libraries(kritaimage PUBLIC ${Boost_SYSTEM_LIBRARY})
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})
########### install schemas #############
install( FILES
metadata/schemas/dc.schema
metadata/schemas/exif.schema
metadata/schemas/tiff.schema
metadata/schemas/mkn.schema
metadata/schemas/xmp.schema
metadata/schemas/xmpmm.schema
metadata/schemas/xmprights.schema
DESTINATION ${DATA_INSTALL_DIR}/krita/metadata/schemas)
diff --git a/libs/image/kis_inpaint_mask.cpp b/libs/image/kis_inpaint_mask.cpp
new file mode 100644
index 0000000000..f5bd1e5cee
--- /dev/null
+++ b/libs/image/kis_inpaint_mask.cpp
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+
+#include "kis_inpaint_mask.h"
+
+#include "kis_debug.h"
+
+#include
+#include
+#include
+#include
+#include
+#include "kis_paint_device.h"
+#include "kis_painter.h"
+#include "kis_node_visitor.h"
+#include "kis_processing_visitor.h"
+#include "KoColorSpaceRegistry.h"
+
+KisInpaintMask::KisInpaintMask()
+ : KisTransparencyMask()
+{
+}
+
+KisInpaintMask::KisInpaintMask(const KisInpaintMask& rhs)
+ : KisTransparencyMask(rhs)
+{
+}
+
+KisInpaintMask::~KisInpaintMask()
+{
+}
+
+QRect KisInpaintMask::decorateRect(KisPaintDeviceSP &src,
+ KisPaintDeviceSP &dst,
+ const QRect & rc,
+ PositionToFilthy maskPos) const
+{
+ Q_UNUSED(maskPos);
+ KIS_ASSERT(dst != src);
+
+ if (src != dst) {
+ KisPainter::copyAreaOptimized(rc.topLeft(), src, dst, rc);
+ src->fill(rc, KoColor(Qt::magenta, src->colorSpace()));
+ }
+
+ return rc;
+}
+
+
diff --git a/libs/image/kis_inpaint_mask.h b/libs/image/kis_inpaint_mask.h
new file mode 100644
index 0000000000..52cc2dfb69
--- /dev/null
+++ b/libs/image/kis_inpaint_mask.h
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+#ifndef _KIS_INPAINT_MASK_
+#define _KIS_INPAINT_MASK_
+
+#include "kis_types.h"
+#include "kis_transparency_mask.h"
+
+class QRect;
+
+/**
+ * A inpaint mask is a single channel mask that works with inpaint operation to denote area affected by inpaint operation.
+ *
+ */
+class KRITAIMAGE_EXPORT KisInpaintMask : public KisTransparencyMask
+{
+ Q_OBJECT
+
+public:
+
+ KisInpaintMask();
+ KisInpaintMask(const KisInpaintMask& rhs);
+ virtual ~KisInpaintMask();
+
+ KisNodeSP clone() const
+ {
+ return KisNodeSP(new KisInpaintMask(*this));
+ }
+
+ QRect decorateRect(KisPaintDeviceSP &src, KisPaintDeviceSP &dst,
+ const QRect & rc,
+ PositionToFilthy maskPos) const;
+};
+
+#endif //_KIS_INPAINT_MASK_
diff --git a/libs/ui/KisImportExportFilter.cpp b/libs/ui/KisImportExportFilter.cpp
index 58cac657db..e156ccc320 100644
--- a/libs/ui/KisImportExportFilter.cpp
+++ b/libs/ui/KisImportExportFilter.cpp
@@ -1,278 +1,278 @@
/* This file is part of the KDE libraries
Copyright (C) 2001 Werner Trobin
2002 Werner Trobin
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "KisImportExportFilter.h"
#include
#include
#include
#include "KisImportExportManager.h"
#include
#include
#include
#include
#include "KoUpdater.h"
#include
class Q_DECL_HIDDEN KisImportExportFilter::Private
{
public:
QPointer updater;
QByteArray mime;
QString filename;
QString realFilename;
bool batchmode;
QMap capabilities;
Private()
- : updater(0)
+ : updater(0), mime("")
, batchmode(false)
{}
~Private()
{
qDeleteAll(capabilities);
}
};
KisImportExportFilter::KisImportExportFilter(QObject *parent)
: QObject(parent)
, d(new Private)
{
}
KisImportExportFilter::~KisImportExportFilter()
{
if (d->updater) {
d->updater->setProgress(100);
}
delete d;
}
QString KisImportExportFilter::filename() const
{
return d->filename;
}
QString KisImportExportFilter::realFilename() const
{
return d->realFilename;
}
bool KisImportExportFilter::batchMode() const
{
return d->batchmode;
}
void KisImportExportFilter::setBatchMode(bool batchmode)
{
d->batchmode = batchmode;
}
void KisImportExportFilter::setFilename(const QString &filename)
{
d->filename = filename;
}
void KisImportExportFilter::setRealFilename(const QString &filename)
{
d->realFilename = filename;
}
void KisImportExportFilter::setMimeType(const QString &mime)
{
d->mime = mime.toLatin1();
}
QByteArray KisImportExportFilter::mimeType() const
{
return d->mime;
}
QString KisImportExportFilter::conversionStatusString(ConversionStatus status)
{
QString msg;
switch (status) {
case OK: break;
case FilterCreationError:
msg = i18n("Could not create the filter plugin"); break;
case CreationError:
msg = i18n("Could not create the output document"); break;
case FileNotFound:
msg = i18n("File not found"); break;
case StorageCreationError:
msg = i18n("Cannot create storage"); break;
case BadMimeType:
msg = i18n("Bad MIME type"); break;
case WrongFormat:
msg = i18n("Format not recognized"); break;
case NotImplemented:
msg = i18n("Not implemented"); break;
case ParsingError:
msg = i18n("Parsing error"); break;
case InvalidFormat:
msg = i18n("Invalid file format"); break;
case InternalError:
case UsageError:
msg = i18n("Internal error"); break;
case ProgressCancelled:
msg = i18n("Cancelled by user"); break;
case BadConversionGraph:
msg = i18n("Unknown file type"); break;
case UnsupportedVersion:
msg = i18n("Unsupported file version"); break;
case UserCancelled:
// intentionally we do not prompt the error message here
break;
default: msg = i18n("Unknown error"); break;
}
return msg;
}
KisPropertiesConfigurationSP KisImportExportFilter::defaultConfiguration(const QByteArray &from, const QByteArray &to) const
{
Q_UNUSED(from);
Q_UNUSED(to);
return 0;
}
KisPropertiesConfigurationSP KisImportExportFilter::lastSavedConfiguration(const QByteArray &from, const QByteArray &to) const
{
return defaultConfiguration(from, to);
}
KisConfigWidget *KisImportExportFilter::createConfigurationWidget(QWidget *, const QByteArray &from, const QByteArray &to) const
{
Q_UNUSED(from);
Q_UNUSED(to);
return 0;
}
QMap KisImportExportFilter::exportChecks()
{
qDeleteAll(d->capabilities);
initializeCapabilities();
return d->capabilities;
}
void KisImportExportFilter::setUpdater(QPointer updater)
{
d->updater = updater;
}
void KisImportExportFilter::setProgress(int value)
{
if (d->updater) {
d->updater->setValue(value);
}
}
void KisImportExportFilter::initializeCapabilities()
{
// XXX: Initialize everything to fully supported?
}
void KisImportExportFilter::addCapability(KisExportCheckBase *capability)
{
d->capabilities[capability->id()] = capability;
}
void KisImportExportFilter::addSupportedColorModels(QList > supportedColorModels, const QString &name, KisExportCheckBase::Level level)
{
Q_ASSERT(level != KisExportCheckBase::SUPPORTED);
QString layerMessage;
QString imageMessage;
QList allColorModels = KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::AllColorSpaces);
Q_FOREACH(const KoID &colorModelID, allColorModels) {
QList allColorDepths = KoColorSpaceRegistry::instance()->colorDepthList(colorModelID.id(), KoColorSpaceRegistry::AllColorSpaces);
Q_FOREACH(const KoID &colorDepthID, allColorDepths) {
KisExportCheckFactory *colorModelCheckFactory =
KisExportCheckRegistry::instance()->get("ColorModelCheck/" + colorModelID.id() + "/" + colorDepthID.id());
KisExportCheckFactory *colorModelPerLayerCheckFactory =
KisExportCheckRegistry::instance()->get("ColorModelPerLayerCheck/" + colorModelID.id() + "/" + colorDepthID.id());
if(!colorModelCheckFactory || !colorModelPerLayerCheckFactory) {
qDebug() << "No factory for" << colorModelID << colorDepthID;
continue;
}
if (supportedColorModels.contains(QPair(colorModelID, colorDepthID))) {
addCapability(colorModelCheckFactory->create(KisExportCheckBase::SUPPORTED));
addCapability(colorModelPerLayerCheckFactory->create(KisExportCheckBase::SUPPORTED));
}
else {
if (level == KisExportCheckBase::PARTIALLY) {
imageMessage = i18nc("image conversion warning",
"%1 cannot save images with color model %2 and depth %3 . The image will be converted."
,name, colorModelID.name(), colorDepthID.name());
layerMessage =
i18nc("image conversion warning",
"%1 cannot save layers with color model %2 and depth %3 . The layers will be converted or skipped."
,name, colorModelID.name(), colorDepthID.name());
}
else {
imageMessage = i18nc("image conversion warning",
"%1 cannot save images with color model %2 and depth %3 . The image will not be saved."
,name, colorModelID.name(), colorDepthID.name());
layerMessage =
i18nc("image conversion warning",
"%1 cannot save layers with color model %2 and depth %3 . The layers will be skipped."
, name, colorModelID.name(), colorDepthID.name());
}
addCapability(colorModelCheckFactory->create(level, imageMessage));
addCapability(colorModelPerLayerCheckFactory->create(level, layerMessage));
}
}
}
}
diff --git a/libs/ui/KisImportExportManager.cpp b/libs/ui/KisImportExportManager.cpp
index d91be7cb6f..983ab2cf9e 100644
--- a/libs/ui/KisImportExportManager.cpp
+++ b/libs/ui/KisImportExportManager.cpp
@@ -1,480 +1,479 @@
/*
* Copyright (C) 2016 Boudewijn Rempt
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KisImportExportManager.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kis_config.h"
#include "KisImportExportFilter.h"
#include "KisDocument.h"
#include
#include
#include "kis_guides_config.h"
#include "kis_grid_config.h"
#include "kis_popup_button.h"
#include
// static cache for import and export mimetypes
QStringList KisImportExportManager::m_importMimeTypes;
QStringList KisImportExportManager::m_exportMimeTypes;
class Q_DECL_HIDDEN KisImportExportManager::Private
{
public:
bool batchMode {false};
QPointer progressUpdater {0};
};
KisImportExportManager::KisImportExportManager(KisDocument* document)
: m_document(document)
, d(new Private)
{
}
KisImportExportManager::~KisImportExportManager()
{
delete d;
}
KisImportExportFilter::ConversionStatus KisImportExportManager::importDocument(const QString& location, const QString& mimeType)
{
return convert(Import, location, location, mimeType, false, 0);
}
KisImportExportFilter::ConversionStatus KisImportExportManager::exportDocument(const QString& location, const QString& realLocation, QByteArray& mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
return convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration);
}
// The static method to figure out to which parts of the
// graph this mimetype has a connection to.
QStringList KisImportExportManager::mimeFilter(Direction direction)
{
// Find the right mimetype by the extension
QSet mimeTypes;
// mimeTypes << KisDocument::nativeFormatMimeType() << "application/x-krita-paintoppreset" << "image/openraster";
if (direction == KisImportExportManager::Import) {
if (m_importMimeTypes.isEmpty()) {
KoJsonTrader trader;
QListlist = trader.query("Krita/FileFilter", "");
Q_FOREACH(QPluginLoader *loader, list) {
QJsonObject json = loader->metaData().value("MetaData").toObject();
Q_FOREACH(const QString &mimetype, json.value("X-KDE-Import").toString().split(",", QString::SkipEmptyParts)) {
//qDebug() << "Adding import mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader;
mimeTypes << mimetype;
}
}
qDeleteAll(list);
m_importMimeTypes = mimeTypes.toList();
}
return m_importMimeTypes;
}
else if (direction == KisImportExportManager::Export) {
if (m_exportMimeTypes.isEmpty()) {
KoJsonTrader trader;
QListlist = trader.query("Krita/FileFilter", "");
Q_FOREACH(QPluginLoader *loader, list) {
QJsonObject json = loader->metaData().value("MetaData").toObject();
Q_FOREACH(const QString &mimetype, json.value("X-KDE-Export").toString().split(",", QString::SkipEmptyParts)) {
//qDebug() << "Adding export mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader;
mimeTypes << mimetype;
}
}
qDeleteAll(list);
m_exportMimeTypes = mimeTypes.toList();
}
return m_exportMimeTypes;
}
return QStringList();
}
KisImportExportFilter *KisImportExportManager::filterForMimeType(const QString &mimetype, KisImportExportManager::Direction direction)
{
int weight = -1;
KisImportExportFilter *filter = 0;
- KoJsonTrader trader;
- QListlist = trader.query("Krita/FileFilter", "");
+ QListlist = KoJsonTrader::instance()->query("Krita/FileFilter", "");
Q_FOREACH(QPluginLoader *loader, list) {
QJsonObject json = loader->metaData().value("MetaData").toObject();
QString directionKey = direction == Export ? "X-KDE-Export" : "X-KDE-Import";
if (json.value(directionKey).toString().split(",", QString::SkipEmptyParts).contains(mimetype)) {
KLibFactory *factory = qobject_cast(loader->instance());
if (!factory) {
warnUI << loader->errorString();
continue;
}
QObject* obj = factory->create(0);
if (!obj || !obj->inherits("KisImportExportFilter")) {
delete obj;
continue;
}
KisImportExportFilter *f = qobject_cast(obj);
if (!f) {
delete obj;
continue;
}
int w = json.value("X-KDE-Weight").toInt();
if (w > weight) {
delete filter;
filter = f;
f->setObjectName(loader->fileName());
weight = w;
}
}
}
qDeleteAll(list);
if (filter) {
filter->setMimeType(mimetype);
}
return filter;
}
void KisImportExportManager::setBatchMode(const bool batch)
{
d->batchMode = batch;
}
bool KisImportExportManager::batchMode(void) const
{
return d->batchMode;
}
void KisImportExportManager::setProgresUpdater(KoProgressUpdater *updater)
{
d->progressUpdater = updater;
}
QString KisImportExportManager::askForAudioFileName(const QString &defaultDir, QWidget *parent)
{
KoFileDialog dialog(parent, KoFileDialog::ImportFiles, "ImportAudio");
if (!defaultDir.isEmpty()) {
dialog.setDefaultDir(defaultDir);
}
QStringList mimeTypes;
mimeTypes << "audio/mpeg";
mimeTypes << "audio/ogg";
mimeTypes << "audio/vorbis";
mimeTypes << "audio/vnd.wave";
mimeTypes << "audio/flac";
dialog.setMimeTypeFilters(mimeTypes);
dialog.setCaption(i18nc("@titile:window", "Open Audio"));
return dialog.filename();
}
KisImportExportFilter::ConversionStatus KisImportExportManager::convert(KisImportExportManager::Direction direction, const QString &location, const QString& realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
QString typeName = mimeType;
if (typeName.isEmpty()) {
typeName = KisMimeDatabase::mimeTypeForFile(location);
}
QSharedPointer filter(filterForMimeType(typeName, direction));
if (!filter) {
return KisImportExportFilter::FilterCreationError;
}
filter->setFilename(location);
filter->setRealFilename(realLocation);
filter->setBatchMode(batchMode());
filter->setMimeType(typeName);
if (d->progressUpdater) {
filter->setUpdater(d->progressUpdater->startSubtask());
}
QByteArray from, to;
if (direction == Export) {
from = m_document->nativeFormatMimeType();
to = mimeType.toLatin1();
}
else {
from = mimeType.toLatin1();
to = m_document->nativeFormatMimeType();
}
if (!exportConfiguration) {
exportConfiguration = filter->lastSavedConfiguration(from, to);
if (exportConfiguration) {
// Fill with some meta information about the image
KisImageWSP image = m_document->image();
KisPaintDeviceSP pd = image->projection();
bool isThereAlpha = false;
KisSequentialConstIterator it(pd, image->bounds());
const KoColorSpace* cs = pd->colorSpace();
do {
if (cs->opacityU8(it.oldRawData()) != OPACITY_OPAQUE_U8) {
isThereAlpha = true;
break;
}
} while (it.nextPixel());
exportConfiguration->setProperty("ImageContainsTransparency", isThereAlpha);
exportConfiguration->setProperty("ColorModelID", cs->colorModelId().id());
exportConfiguration->setProperty("ColorDepthID", cs->colorDepthId().id());
bool sRGB = (cs->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive) && !cs->profile()->name().contains(QLatin1String("g10")));
exportConfiguration->setProperty("sRGB", sRGB);
}
}
KisPreExportChecker checker;
if (direction == Export) {
checker.check(m_document->image(), filter->exportChecks());
}
KisConfigWidget *wdg = filter->createConfigurationWidget(0, from, to);
bool alsoAsKra = false;
QStringList warnings = checker.warnings();
QStringList errors = checker.errors();
// Extra checks that cannot be done by the checker, because the checker only has access to the image.
if (!m_document->assistants().isEmpty() && typeName != m_document->nativeFormatMimeType()) {
warnings.append(i18nc("image conversion warning", "The image contains assistants . The assistants will not be saved."));
}
if (m_document->guidesConfig().hasGuides() && typeName != m_document->nativeFormatMimeType()) {
warnings.append(i18nc("image conversion warning", "The image contains guides . The guides will not be saved."));
}
if (!m_document->gridConfig().isDefault() && typeName != m_document->nativeFormatMimeType()) {
warnings.append(i18nc("image conversion warning", "The image contains a custom grid configuration . The configuration will not be saved."));
}
if (!batchMode() && !errors.isEmpty()) {
QString error = ""
+ i18n("Error: cannot save this image as a %1.", KisMimeDatabase::descriptionForMimeType(typeName))
+ " Reasons:
"
+ "
";
Q_FOREACH(const QString &w, errors) {
error += "\n" + w + " ";
}
error += " ";
QMessageBox::critical(KisPart::instance()->currentMainwindow(), i18nc("@title:window", "Krita: Export Error"), error);
return KisImportExportFilter::UserCancelled;
}
if (!batchMode() && (wdg || !warnings.isEmpty())) {
KoDialog dlg;
dlg.setButtons(KoDialog::Ok | KoDialog::Cancel);
dlg.setWindowTitle(KisMimeDatabase::descriptionForMimeType(mimeType));
QWidget *page = new QWidget(&dlg);
QVBoxLayout *layout = new QVBoxLayout(page);
if (!checker.warnings().isEmpty()) {
if (showWarnings) {
QHBoxLayout *hLayout = new QHBoxLayout();
QLabel *labelWarning = new QLabel();
labelWarning->setPixmap(KisIconUtils::loadIcon("dialog-warning").pixmap(32, 32));
hLayout->addWidget(labelWarning);
KisPopupButton *bn = new KisPopupButton(0);
bn->setText(i18nc("Keep the extra space at the end of the sentence, please", "Warning: saving as %1 will lose information from your image. ", KisMimeDatabase::descriptionForMimeType(mimeType)));
hLayout->addWidget(bn);
layout->addLayout(hLayout);
QTextBrowser *browser = new QTextBrowser();
browser->setMinimumWidth(bn->width());
bn->setPopupWidget(browser);
QString warning = ""
+ i18n("You will lose information when saving this image as a %1.", KisMimeDatabase::descriptionForMimeType(typeName));
if (warnings.size() == 1) {
warning += " Reason:
";
}
else {
warning += " Reasons:
";
}
warning += "
";
Q_FOREACH(const QString &w, warnings) {
warning += "\n" + w + " ";
}
warning += " ";
browser->setHtml(warning);
}
}
if (wdg) {
QGroupBox *box = new QGroupBox(i18n("Options"));
QVBoxLayout *boxLayout = new QVBoxLayout(box);
wdg->setConfiguration(exportConfiguration);
boxLayout->addWidget(wdg);
layout->addWidget(box);
}
QCheckBox *chkAlsoAsKra = 0;
if (showWarnings) {
if (!checker.warnings().isEmpty()) {
chkAlsoAsKra = new QCheckBox(i18n("Also save your image as a Krita file."));
chkAlsoAsKra->setChecked(KisConfig().readEntry("AlsoSaveAsKra", false));
layout->addWidget(chkAlsoAsKra);
}
}
dlg.setMainWidget(page);
dlg.resize(dlg.minimumSize());
if (showWarnings || wdg) {
if (!dlg.exec()) {
return KisImportExportFilter::UserCancelled;
}
}
if (chkAlsoAsKra) {
KisConfig().writeEntry("AlsoSaveAsKra", chkAlsoAsKra->isChecked());
alsoAsKra = chkAlsoAsKra->isChecked();
}
if (wdg) {
exportConfiguration = wdg->configuration();
}
}
QFile io(location);
QSaveFile out(location);
if (direction == Import) {
if (!io.exists()) {
return KisImportExportFilter::FileNotFound;
}
if (!io.open(QFile::ReadOnly) ) {
return KisImportExportFilter::FileNotFound;
}
}
else if (direction == Export) {
if (filter->supportsIO()) {
out.setDirectWriteFallback(true);
if (!out.open(QFile::WriteOnly)) {
out.cancelWriting();
return KisImportExportFilter::CreationError;
}
}
}
else {
return KisImportExportFilter::BadConversionGraph;
}
if (!batchMode()) {
QApplication::setOverrideCursor(Qt::WaitCursor);
}
KisImportExportFilter::ConversionStatus status;
if (direction == Import || !filter->supportsIO()) {
status = filter->convert(m_document, &io, exportConfiguration);
if (io.isOpen()) io.close();
}
else {
status = filter->convert(m_document, &out, exportConfiguration);
if (status != KisImportExportFilter::OK) {
out.cancelWriting();
}
else {
out.commit();
}
}
if (exportConfiguration) {
KisConfig().setExportConfiguration(typeName, exportConfiguration);
}
if (alsoAsKra) {
QString l = location + ".kra";
QByteArray ba = m_document->nativeFormatMimeType();
KisImportExportFilter *filter = filterForMimeType(QString::fromLatin1(ba), Export);
QSaveFile f(l);
f.open(QIODevice::WriteOnly);
if (filter) {
filter->setFilename(l);
if (filter->convert(m_document, &f) == KisImportExportFilter::OK) {
f.commit();
}
else {
f.cancelWriting();
}
}
delete filter;
}
if (!batchMode()) {
QApplication::restoreOverrideCursor();
}
return status;
}
#include
diff --git a/libs/ui/kis_mask_manager.cc b/libs/ui/kis_mask_manager.cc
index 5a86188011..1ca79de71d 100644
--- a/libs/ui/kis_mask_manager.cc
+++ b/libs/ui/kis_mask_manager.cc
@@ -1,307 +1,311 @@
/* This file is part of the KDE project
* Copyright (C) Boudewijn Rempt , (C) 2006
*
* This program is free software; you can 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_mask_manager.h"
#include
#include
#include
#include
#include
#include
#include
#include "KisDocument.h"
#include "KisViewManager.h"
#include
#include
#include
#include
#include
#include
#include
+#include
#include
#include
#include "dialogs/kis_dlg_adjustment_layer.h"
#include "widgets/kis_mask_widgets.h"
#include
#include
#include
#include "dialogs/kis_dlg_adj_layer_props.h"
#include
#include
#include
#include
#include "kis_node_commands_adapter.h"
#include "commands/kis_selection_commands.h"
#include "kis_iterator_ng.h"
KisMaskManager::KisMaskManager(KisViewManager * view)
: m_view(view)
, m_imageView(0)
, m_commandsAdapter(new KisNodeCommandsAdapter(m_view))
{
}
void KisMaskManager::setView(QPointerimageView)
{
m_imageView = imageView;
}
void KisMaskManager::setup(KActionCollection *actionCollection, KisActionManager *actionManager)
{
Q_UNUSED(actionCollection);
Q_UNUSED(actionManager);
}
void KisMaskManager::updateGUI()
{
// XXX: enable/disable menu items according to whether there's a mask selected currently
// XXX: disable the selection mask item if there's already a selection mask
// YYY: doesn't KisAction do that already?
}
KisMaskSP KisMaskManager::activeMask()
{
if (m_imageView) {
return m_imageView->currentMask();
}
return 0;
}
KisPaintDeviceSP KisMaskManager::activeDevice()
{
KisMaskSP mask = activeMask();
return mask ? mask->paintDevice() : 0;
}
void KisMaskManager::activateMask(KisMaskSP mask)
{
Q_UNUSED(mask);
}
void KisMaskManager::masksUpdated()
{
m_view->updateGUI();
}
void KisMaskManager::adjustMaskPosition(KisNodeSP node, KisNodeSP activeNode, bool avoidActiveNode, KisNodeSP &parent, KisNodeSP &above)
{
Q_ASSERT(node);
Q_ASSERT(activeNode);
if (!avoidActiveNode && activeNode->allowAsChild(node)) {
parent = activeNode;
above = activeNode->lastChild();
} else if (activeNode->parent() && activeNode->parent()->allowAsChild(node)) {
parent = activeNode->parent();
above = activeNode;
} else {
KisNodeSP t = activeNode;
while ((t = t->nextSibling())) {
if (t->allowAsChild(node)) {
parent = t;
above = t->lastChild();
break;
}
}
if (!t) {
t = activeNode;
while ((t = t->prevSibling())) {
if (t->allowAsChild(node)) {
parent = t;
above = t->lastChild();
break;
}
}
}
if (!t && activeNode->parent()) {
adjustMaskPosition(node, activeNode->parent(), true, parent, above);
} else if (!t) {
KisImageWSP image = m_view->image();
KisLayerSP layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, image->colorSpace());
m_commandsAdapter->addNode(layer, activeNode, 0);
parent = layer;
above = 0;
}
}
}
void KisMaskManager::createMaskCommon(KisMaskSP mask, KisNodeSP activeNode, KisPaintDeviceSP copyFrom, const KUndo2MagicString& macroName, const QString &nodeType, const QString &nodeName, bool suppressSelection, bool avoidActiveNode, bool updateImage)
{
m_commandsAdapter->beginMacro(macroName);
KisNodeSP parent;
KisNodeSP above;
adjustMaskPosition(mask, activeNode, avoidActiveNode, parent, above);
KisLayerSP parentLayer = qobject_cast(parent.data());
Q_ASSERT(parentLayer);
if (!suppressSelection) {
if (copyFrom) {
mask->initSelection(copyFrom, parentLayer);
} else {
mask->initSelection(m_view->selection(), parentLayer);
}
}
//counting number of KisSelectionMask
QList masks = parentLayer->childNodes(QStringList(nodeType),KoProperties());
int number = masks.count() + 1;
mask->setName(nodeName + QString(" ") + QString::number(number));
m_commandsAdapter->addNode(mask, parentLayer, above, updateImage, updateImage);
m_commandsAdapter->endMacro();
masksUpdated();
}
void KisMaskManager::createSelectionMask(KisNodeSP activeNode, KisPaintDeviceSP copyFrom, bool avoidActiveNode)
{
KisSelectionMaskSP mask = new KisSelectionMask(m_view->image());
createMaskCommon(mask, activeNode, copyFrom, kundo2_i18n("Add Selection Mask"), "KisSelectionMask", i18n("Selection"), false, avoidActiveNode, false);
mask->setActive(true);
}
void KisMaskManager::createTransparencyMask(KisNodeSP activeNode, KisPaintDeviceSP copyFrom, bool avoidActiveNode)
{
KisMaskSP mask = new KisTransparencyMask();
createMaskCommon(mask, activeNode, copyFrom, kundo2_i18n("Add Transparency Mask"), "KisTransparencyMask", i18n("Transparency Mask"), false, avoidActiveNode);
}
+
void KisMaskManager::createFilterMask(KisNodeSP activeNode, KisPaintDeviceSP copyFrom, bool quiet, bool avoidActiveNode)
{
KisFilterMaskSP mask = new KisFilterMask();
createMaskCommon(mask, activeNode, copyFrom, kundo2_i18n("Add Filter Mask"), "KisFilterMask", i18n("Filter Mask"), false, avoidActiveNode);
/**
* FIXME: We'll use layer's original for creation of a thumbnail.
* Actually, we can't use it's projection as newly created mask
* may be going to be inserted in the middle of the masks stack
*/
KisPaintDeviceSP originalDevice = mask->parent()->original();
KisDlgAdjustmentLayer dialog(mask, mask.data(), originalDevice,
mask->name(), i18n("New Filter Mask"),
m_view);
// If we are supposed to not disturb the user, don't start asking them about things.
if(quiet) {
KisFilterConfigurationSP filter = KisFilterRegistry::instance()->values().first()->defaultConfiguration();
if (filter) {
mask->setFilter(filter);
mask->setName(mask->name());
}
return;
}
if (dialog.exec() == QDialog::Accepted) {
KisFilterConfigurationSP filter = dialog.filterConfiguration();
if (filter) {
QString name = dialog.layerName();
mask->setFilter(filter);
mask->setName(name);
}
} else {
m_commandsAdapter->undoLastCommand();
}
}
+
void KisMaskManager::createColorizeMask(KisNodeSP activeNode)
{
KisColorizeMaskSP mask = new KisColorizeMask();
createMaskCommon(mask, activeNode, 0, kundo2_i18n("Add Colorize Mask"), "KisColorizeMask", i18n("Colorize Mask"), true, false);
mask->setImage(m_view->image());
mask->initializeCompositeOp();
delete mask->setColorSpace(mask->parent()->colorSpace());
}
+
void KisMaskManager::createTransformMask(KisNodeSP activeNode)
{
KisTransformMaskSP mask = new KisTransformMask();
createMaskCommon(mask, activeNode, 0, kundo2_i18n("Add Transform Mask"), "KisTransformMask", i18n("Transform Mask"), true, false);
}
void KisMaskManager::maskProperties()
{
if (!activeMask()) return;
if (activeMask()->inherits("KisFilterMask")) {
KisFilterMask *mask = static_cast(activeMask().data());
KisLayerSP layer = qobject_cast(mask->parent().data());
if (! layer)
return;
KisPaintDeviceSP dev = layer->original();
if (!dev) {
return;
}
KisDlgAdjLayerProps dlg(layer, mask, dev, m_view, mask->filter().data(), mask->name(), i18n("Filter Mask Properties"), m_view->mainWindow(), "dlgeffectmaskprops");
KisFilterConfigurationSP configBefore(mask->filter());
Q_ASSERT(configBefore);
QString xmlBefore = configBefore->toXML();
if (dlg.exec() == QDialog::Accepted) {
KisFilterConfigurationSP configAfter(dlg.filterConfiguration());
Q_ASSERT(configAfter);
QString xmlAfter = configAfter->toXML();
mask->setName(dlg.layerName());
if(xmlBefore != xmlAfter) {
KisChangeFilterCmd *cmd
= new KisChangeFilterCmd(mask,
configBefore->name(),
xmlBefore,
configAfter->name(),
xmlAfter,
false);
// FIXME: check whether is needed
cmd->redo();
m_view->undoAdapter()->addCommand(cmd);
m_view->document()->setModified(true);
}
}
else {
KisFilterConfigurationSP configAfter(dlg.filterConfiguration());
Q_ASSERT(configAfter);
QString xmlAfter = configAfter->toXML();
if(xmlBefore != xmlAfter) {
mask->setFilter(KisFilterRegistry::instance()->cloneConfiguration(configBefore.data()));
mask->setDirty();
}
}
} else {
// Not much to show for transparency or selection masks?
}
}
diff --git a/libs/ui/kis_node_manager.cpp b/libs/ui/kis_node_manager.cpp
index a1699698df..271756f048 100644
--- a/libs/ui/kis_node_manager.cpp
+++ b/libs/ui/kis_node_manager.cpp
@@ -1,1344 +1,1343 @@
/*
* Copyright (C) 2007 Boudewijn Rempt
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_node_manager.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "KisPart.h"
#include "canvas/kis_canvas2.h"
#include "kis_shape_controller.h"
#include "kis_canvas_resource_provider.h"
#include "KisViewManager.h"
#include "KisDocument.h"
#include "kis_mask_manager.h"
#include "kis_group_layer.h"
#include "kis_layer_manager.h"
#include "kis_selection_manager.h"
#include "kis_node_commands_adapter.h"
#include "kis_action.h"
#include "kis_action_manager.h"
#include "kis_processing_applicator.h"
#include "kis_sequential_iterator.h"
#include "kis_transaction.h"
#include "kis_node_selection_adapter.h"
#include "kis_node_insertion_adapter.h"
#include "kis_node_juggler_compressed.h"
#include "kis_clipboard.h"
#include "kis_node_dummies_graph.h"
#include "kis_mimedata.h"
#include "kis_layer_utils.h"
#include "krita_utils.h"
#include "processing/kis_mirror_processing_visitor.h"
#include "KisView.h"
struct KisNodeManager::Private {
Private(KisNodeManager *_q, KisViewManager *v)
: q(_q)
, view(v)
, imageView(0)
, layerManager(v)
, maskManager(v)
, commandsAdapter(v)
, nodeSelectionAdapter(new KisNodeSelectionAdapter(q))
, nodeInsertionAdapter(new KisNodeInsertionAdapter(q))
{
}
KisNodeManager * q;
KisViewManager * view;
QPointerimageView;
KisLayerManager layerManager;
KisMaskManager maskManager;
KisNodeCommandsAdapter commandsAdapter;
QScopedPointer nodeSelectionAdapter;
QScopedPointer nodeInsertionAdapter;
KisNodeList selectedNodes;
QPointer nodeJuggler;
KisNodeWSP previouslyActiveNode;
bool activateNodeImpl(KisNodeSP node);
QSignalMapper nodeCreationSignalMapper;
QSignalMapper nodeConversionSignalMapper;
void saveDeviceAsImage(KisPaintDeviceSP device,
const QString &defaultName,
const QRect &bounds,
qreal xRes,
qreal yRes,
quint8 opacity);
void mergeTransparencyMaskAsAlpha(bool writeToLayers);
KisNodeJugglerCompressed* lazyGetJuggler(const KUndo2MagicString &actionName);
};
bool KisNodeManager::Private::activateNodeImpl(KisNodeSP node)
{
Q_ASSERT(view);
Q_ASSERT(view->canvasBase());
Q_ASSERT(view->canvasBase()->globalShapeManager());
Q_ASSERT(imageView);
if (node && node == q->activeNode()) {
return false;
}
// Set the selection on the shape manager to the active layer
// and set call KoSelection::setActiveLayer( KoShapeLayer* layer )
// with the parent of the active layer.
KoSelection *selection = view->canvasBase()->globalShapeManager()->selection();
Q_ASSERT(selection);
selection->deselectAll();
if (!node) {
selection->setActiveLayer(0);
imageView->setCurrentNode(0);
maskManager.activateMask(0);
layerManager.activateLayer(0);
previouslyActiveNode = q->activeNode();
} else {
previouslyActiveNode = q->activeNode();
KoShape * shape = view->document()->shapeForNode(node);
KIS_ASSERT_RECOVER_RETURN_VALUE(shape, false);
selection->select(shape);
KoShapeLayer * shapeLayer = dynamic_cast(shape);
KIS_ASSERT_RECOVER_RETURN_VALUE(shapeLayer, false);
// shapeLayer->setGeometryProtected(node->userLocked());
// shapeLayer->setVisible(node->visible());
selection->setActiveLayer(shapeLayer);
imageView->setCurrentNode(node);
if (KisLayerSP layer = qobject_cast(node.data())) {
maskManager.activateMask(0);
layerManager.activateLayer(layer);
} else if (KisMaskSP mask = dynamic_cast(node.data())) {
maskManager.activateMask(mask);
// XXX_NODE: for now, masks cannot be nested.
layerManager.activateLayer(static_cast(node->parent().data()));
}
}
return true;
}
KisNodeManager::KisNodeManager(KisViewManager *view)
: m_d(new Private(this, view))
{
connect(&m_d->layerManager, SIGNAL(sigLayerActivated(KisLayerSP)), SIGNAL(sigLayerActivated(KisLayerSP)));
}
KisNodeManager::~KisNodeManager()
{
delete m_d;
}
void KisNodeManager::setView(QPointerimageView)
{
m_d->maskManager.setView(imageView);
m_d->layerManager.setView(imageView);
if (m_d->imageView) {
KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController());
Q_ASSERT(shapeController);
shapeController->disconnect(SIGNAL(sigActivateNode(KisNodeSP)), this);
m_d->imageView->image()->disconnect(this);
}
m_d->imageView = imageView;
if (m_d->imageView) {
KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController());
Q_ASSERT(shapeController);
connect(shapeController, SIGNAL(sigActivateNode(KisNodeSP)), SLOT(slotNonUiActivatedNode(KisNodeSP)));
connect(m_d->imageView->image(), SIGNAL(sigIsolatedModeChanged()),this, SLOT(slotUpdateIsolateModeAction()));
connect(m_d->imageView->image(), SIGNAL(sigRequestNodeReselection(KisNodeSP, const KisNodeList&)),this, SLOT(slotImageRequestNodeReselection(KisNodeSP, const KisNodeList&)));
m_d->imageView->resourceProvider()->slotNodeActivated(m_d->imageView->currentNode());
}
}
#define NEW_LAYER_ACTION(id, layerType) \
{ \
action = actionManager->createAction(id); \
m_d->nodeCreationSignalMapper.setMapping(action, layerType); \
connect(action, SIGNAL(triggered()), \
&m_d->nodeCreationSignalMapper, SLOT(map())); \
}
#define CONVERT_NODE_ACTION_2(id, layerType, exclude) \
{ \
action = actionManager->createAction(id); \
action->setExcludedNodeTypes(QStringList(exclude)); \
actionManager->addAction(id, action); \
m_d->nodeConversionSignalMapper.setMapping(action, layerType); \
connect(action, SIGNAL(triggered()), \
&m_d->nodeConversionSignalMapper, SLOT(map())); \
}
#define CONVERT_NODE_ACTION(id, layerType) \
CONVERT_NODE_ACTION_2(id, layerType, layerType)
void KisNodeManager::setup(KActionCollection * actionCollection, KisActionManager* actionManager)
{
m_d->layerManager.setup(actionManager);
m_d->maskManager.setup(actionCollection, actionManager);
KisAction * action = actionManager->createAction("mirrorNodeX");
connect(action, SIGNAL(triggered()), this, SLOT(mirrorNodeX()));
action = actionManager->createAction("mirrorNodeY");
connect(action, SIGNAL(triggered()), this, SLOT(mirrorNodeY()));
action = actionManager->createAction("activateNextLayer");
connect(action, SIGNAL(triggered()), this, SLOT(activateNextNode()));
action = actionManager->createAction("activatePreviousLayer");
connect(action, SIGNAL(triggered()), this, SLOT(activatePreviousNode()));
action = actionManager->createAction("switchToPreviouslyActiveNode");
connect(action, SIGNAL(triggered()), this, SLOT(switchToPreviouslyActiveNode()));
action = actionManager->createAction("save_node_as_image");
connect(action, SIGNAL(triggered()), this, SLOT(saveNodeAsImage()));
action = actionManager->createAction("duplicatelayer");
connect(action, SIGNAL(triggered()), this, SLOT(duplicateActiveNode()));
action = actionManager->createAction("copy_layer_clipboard");
connect(action, SIGNAL(triggered()), this, SLOT(copyLayersToClipboard()));
action = actionManager->createAction("cut_layer_clipboard");
connect(action, SIGNAL(triggered()), this, SLOT(cutLayersToClipboard()));
action = actionManager->createAction("paste_layer_from_clipboard");
connect(action, SIGNAL(triggered()), this, SLOT(pasteLayersFromClipboard()));
action = actionManager->createAction("create_quick_group");
connect(action, SIGNAL(triggered()), this, SLOT(createQuickGroup()));
action = actionManager->createAction("create_quick_clipping_group");
connect(action, SIGNAL(triggered()), this, SLOT(createQuickClippingGroup()));
action = actionManager->createAction("quick_ungroup");
connect(action, SIGNAL(triggered()), this, SLOT(quickUngroup()));
action = actionManager->createAction("select_all_layers");
connect(action, SIGNAL(triggered()), this, SLOT(selectAllNodes()));
action = actionManager->createAction("select_visible_layers");
connect(action, SIGNAL(triggered()), this, SLOT(selectVisibleNodes()));
action = actionManager->createAction("select_locked_layers");
connect(action, SIGNAL(triggered()), this, SLOT(selectLockedNodes()));
action = actionManager->createAction("select_invisible_layers");
connect(action, SIGNAL(triggered()), this, SLOT(selectInvisibleNodes()));
action = actionManager->createAction("select_unlocked_layers");
connect(action, SIGNAL(triggered()), this, SLOT(selectUnlockedNodes()));
action = actionManager->createAction("new_from_visible");
connect(action, SIGNAL(triggered()), this, SLOT(createFromVisible()));
NEW_LAYER_ACTION("add_new_paint_layer", "KisPaintLayer");
NEW_LAYER_ACTION("add_new_group_layer", "KisGroupLayer");
NEW_LAYER_ACTION("add_new_clone_layer", "KisCloneLayer");
NEW_LAYER_ACTION("add_new_shape_layer", "KisShapeLayer");
NEW_LAYER_ACTION("add_new_adjustment_layer", "KisAdjustmentLayer");
NEW_LAYER_ACTION("add_new_fill_layer", "KisGeneratorLayer");
NEW_LAYER_ACTION("add_new_file_layer", "KisFileLayer");
NEW_LAYER_ACTION("add_new_transparency_mask", "KisTransparencyMask");
NEW_LAYER_ACTION("add_new_filter_mask", "KisFilterMask");
NEW_LAYER_ACTION("add_new_colorize_mask", "KisColorizeMask");
NEW_LAYER_ACTION("add_new_transform_mask", "KisTransformMask");
NEW_LAYER_ACTION("add_new_selection_mask", "KisSelectionMask");
connect(&m_d->nodeCreationSignalMapper, SIGNAL(mapped(const QString &)),
this, SLOT(createNode(const QString &)));
CONVERT_NODE_ACTION("convert_to_paint_layer", "KisPaintLayer");
CONVERT_NODE_ACTION_2("convert_to_selection_mask", "KisSelectionMask", QStringList() << "KisSelectionMask" << "KisColorizeMask");
CONVERT_NODE_ACTION_2("convert_to_filter_mask", "KisFilterMask", QStringList() << "KisFilterMask" << "KisColorizeMask");
CONVERT_NODE_ACTION_2("convert_to_transparency_mask", "KisTransparencyMask", QStringList() << "KisTransparencyMask" << "KisColorizeMask");
CONVERT_NODE_ACTION("convert_to_animated", "animated");
connect(&m_d->nodeConversionSignalMapper, SIGNAL(mapped(const QString &)),
this, SLOT(convertNode(const QString &)));
action = actionManager->createAction("isolate_layer");
connect(action, SIGNAL(triggered(bool)), this, SLOT(toggleIsolateMode(bool)));
action = actionManager->createAction("split_alpha_into_mask");
connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaIntoMask()));
action = actionManager->createAction("split_alpha_write");
connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaWrite()));
// HINT: we can save even when the nodes are not editable
action = actionManager->createAction("split_alpha_save_merged");
connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaSaveMerged()));
connect(this, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotUpdateIsolateModeAction()));
connect(this, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotTryFinishIsolatedMode()));
}
void KisNodeManager::updateGUI()
{
// enable/disable all relevant actions
m_d->layerManager.updateGUI();
m_d->maskManager.updateGUI();
}
KisNodeSP KisNodeManager::activeNode()
{
if (m_d->imageView) {
return m_d->imageView->currentNode();
}
return 0;
}
KisLayerSP KisNodeManager::activeLayer()
{
return m_d->layerManager.activeLayer();
}
const KoColorSpace* KisNodeManager::activeColorSpace()
{
if (m_d->maskManager.activeDevice()) {
return m_d->maskManager.activeDevice()->colorSpace();
} else {
Q_ASSERT(m_d->layerManager.activeLayer());
if (m_d->layerManager.activeLayer()->parentLayer())
return m_d->layerManager.activeLayer()->parentLayer()->colorSpace();
else
return m_d->view->image()->colorSpace();
}
}
void KisNodeManager::moveNodeAt(KisNodeSP node, KisNodeSP parent, int index)
{
if (parent->allowAsChild(node)) {
if (node->inherits("KisSelectionMask") && parent->inherits("KisLayer")) {
KisSelectionMask *m = dynamic_cast(node.data());
KisLayer *l = qobject_cast(parent.data());
KisSelectionMaskSP selMask = l->selectionMask();
if (m && m->active() && l && l->selectionMask())
selMask->setActive(false);
}
m_d->commandsAdapter.moveNode(node, parent, index);
}
}
void KisNodeManager::moveNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis)
{
KUndo2MagicString actionName = kundo2_i18n("Move Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->moveNode(nodes, parent, aboveThis);
}
void KisNodeManager::copyNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis)
{
KUndo2MagicString actionName = kundo2_i18n("Copy Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->copyNode(nodes, parent, aboveThis);
}
void KisNodeManager::addNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis)
{
KUndo2MagicString actionName = kundo2_i18n("Add Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->addNode(nodes, parent, aboveThis);
}
void KisNodeManager::toggleIsolateActiveNode()
{
KisImageWSP image = m_d->view->image();
KisNodeSP activeNode = this->activeNode();
KIS_ASSERT_RECOVER_RETURN(activeNode);
if (activeNode == image->isolatedModeRoot()) {
toggleIsolateMode(false);
} else {
toggleIsolateMode(true);
}
}
void KisNodeManager::toggleIsolateMode(bool checked)
{
KisImageWSP image = m_d->view->image();
if (checked) {
KisNodeSP activeNode = this->activeNode();
// Transform and colorize masks don't have pixel data...
if (activeNode->inherits("KisTransformMask") ||
activeNode->inherits("KisColorizeMask")) return;
KIS_ASSERT_RECOVER_RETURN(activeNode);
if (!image->startIsolatedMode(activeNode)) {
KisAction *action = m_d->view->actionManager()->actionByName("isolate_layer");
action->setChecked(false);
}
} else {
image->stopIsolatedMode();
}
}
void KisNodeManager::slotUpdateIsolateModeAction()
{
KisAction *action = m_d->view->actionManager()->actionByName("isolate_layer");
Q_ASSERT(action);
KisNodeSP activeNode = this->activeNode();
KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot();
action->setChecked(isolatedRootNode && isolatedRootNode == activeNode);
}
void KisNodeManager::slotTryFinishIsolatedMode()
{
KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot();
if (!isolatedRootNode) return;
this->toggleIsolateMode(true);
}
void KisNodeManager::createNode(const QString & nodeType, bool quiet, KisPaintDeviceSP copyFrom)
{
KisNodeSP activeNode = this->activeNode();
if (!activeNode) {
activeNode = m_d->view->image()->root();
}
KIS_ASSERT_RECOVER_RETURN(activeNode);
if (activeNode->systemLocked()) {
return;
}
// XXX: make factories for this kind of stuff,
// with a registry
if (nodeType == "KisPaintLayer") {
m_d->layerManager.addLayer(activeNode);
} else if (nodeType == "KisGroupLayer") {
m_d->layerManager.addGroupLayer(activeNode);
} else if (nodeType == "KisAdjustmentLayer") {
m_d->layerManager.addAdjustmentLayer(activeNode);
} else if (nodeType == "KisGeneratorLayer") {
m_d->layerManager.addGeneratorLayer(activeNode);
} else if (nodeType == "KisShapeLayer") {
m_d->layerManager.addShapeLayer(activeNode);
} else if (nodeType == "KisCloneLayer") {
m_d->layerManager.addCloneLayer(activeNode);
} else if (nodeType == "KisTransparencyMask") {
m_d->maskManager.createTransparencyMask(activeNode, copyFrom, false);
} else if (nodeType == "KisFilterMask") {
m_d->maskManager.createFilterMask(activeNode, copyFrom, quiet, false);
} else if (nodeType == "KisColorizeMask") {
m_d->maskManager.createColorizeMask(activeNode);
} else if (nodeType == "KisTransformMask") {
m_d->maskManager.createTransformMask(activeNode);
} else if (nodeType == "KisSelectionMask") {
m_d->maskManager.createSelectionMask(activeNode, copyFrom, false);
} else if (nodeType == "KisFileLayer") {
m_d->layerManager.addFileLayer(activeNode);
}
-
}
void KisNodeManager::createFromVisible()
{
KisLayerUtils::newLayerFromVisible(m_d->view->image(), m_d->view->image()->root()->lastChild());
}
KisLayerSP KisNodeManager::createPaintLayer()
{
KisNodeSP activeNode = this->activeNode();
if (!activeNode) {
activeNode = m_d->view->image()->root();
}
return m_d->layerManager.addLayer(activeNode);
}
void KisNodeManager::convertNode(const QString &nodeType)
{
KisNodeSP activeNode = this->activeNode();
if (!activeNode) return;
if (nodeType == "KisPaintLayer") {
m_d->layerManager.convertNodeToPaintLayer(activeNode);
} else if (nodeType == "KisSelectionMask" ||
nodeType == "KisFilterMask" ||
nodeType == "KisTransparencyMask") {
KisPaintDeviceSP copyFrom = activeNode->paintDevice() ?
activeNode->paintDevice() : activeNode->projection();
m_d->commandsAdapter.beginMacro(kundo2_i18n("Convert to a Selection Mask"));
if (nodeType == "KisSelectionMask") {
m_d->maskManager.createSelectionMask(activeNode, copyFrom, true);
} else if (nodeType == "KisFilterMask") {
m_d->maskManager.createFilterMask(activeNode, copyFrom, false, true);
} else if (nodeType == "KisTransparencyMask") {
m_d->maskManager.createTransparencyMask(activeNode, copyFrom, true);
}
m_d->commandsAdapter.removeNode(activeNode);
m_d->commandsAdapter.endMacro();
} else {
warnKrita << "Unsupported node conversion type:" << nodeType;
}
}
void KisNodeManager::slotSomethingActivatedNodeImpl(KisNodeSP node)
{
KIS_ASSERT_RECOVER_RETURN(node != activeNode());
if (m_d->activateNodeImpl(node)) {
emit sigUiNeedChangeActiveNode(node);
emit sigNodeActivated(node);
nodesUpdated();
if (node) {
bool toggled = m_d->view->actionCollection()->action("view_show_canvas_only")->isChecked();
if (toggled) {
m_d->view->showFloatingMessage( activeLayer()->name(), QIcon(), 1600, KisFloatingMessage::Medium, Qt::TextSingleLine);
}
}
}
}
void KisNodeManager::slotNonUiActivatedNode(KisNodeSP node)
{
if (node == activeNode()) return;
slotSomethingActivatedNodeImpl(node);
if (node) {
bool toggled = m_d->view->actionCollection()->action("view_show_canvas_only")->isChecked();
if (toggled) {
m_d->view->showFloatingMessage( activeLayer()->name(), QIcon(), 1600, KisFloatingMessage::Medium, Qt::TextSingleLine);
}
}
}
void KisNodeManager::slotUiActivatedNode(KisNodeSP node)
{
if (node == activeNode()) return;
slotSomethingActivatedNodeImpl(node);
if (node) {
QStringList vectorTools = QStringList()
<< "InteractionTool"
<< "KarbonPatternTool"
<< "KarbonGradientTool"
<< "KarbonCalligraphyTool"
<< "CreateShapesTool"
<< "PathTool";
QStringList pixelTools = QStringList()
<< "KritaShape/KisToolBrush"
<< "KritaShape/KisToolDyna"
<< "KritaShape/KisToolMultiBrush"
<< "KritaFill/KisToolFill"
<< "KritaFill/KisToolGradient";
if (node->inherits("KisShapeLayer")) {
if (pixelTools.contains(KoToolManager::instance()->activeToolId())) {
KoToolManager::instance()->switchToolRequested("InteractionTool");
}
}
else {
if (vectorTools.contains(KoToolManager::instance()->activeToolId())) {
KoToolManager::instance()->switchToolRequested("KritaShape/KisToolBrush");
}
}
}
}
void KisNodeManager::nodesUpdated()
{
KisNodeSP node = activeNode();
if (!node) return;
m_d->layerManager.layersUpdated();
m_d->maskManager.masksUpdated();
m_d->view->updateGUI();
m_d->view->selectionManager()->selectionChanged();
}
KisPaintDeviceSP KisNodeManager::activePaintDevice()
{
return m_d->maskManager.activeMask() ?
m_d->maskManager.activeDevice() :
m_d->layerManager.activeDevice();
}
void KisNodeManager::nodeProperties(KisNodeSP node)
{
if (selectedNodes().size() > 1 || node->inherits("KisLayer")) {
m_d->layerManager.layerProperties();
} else if (node->inherits("KisMask")) {
m_d->maskManager.maskProperties();
}
}
qint32 KisNodeManager::convertOpacityToInt(qreal opacity)
{
/**
* Scales opacity from the range 0...100
* to the integer range 0...255
*/
return qMin(255, int(opacity * 2.55 + 0.5));
}
void KisNodeManager::setNodeOpacity(KisNodeSP node, qint32 opacity,
bool finalChange)
{
if (!node) return;
if (node->opacity() == opacity) return;
if (!finalChange) {
node->setOpacity(opacity);
node->setDirty();
} else {
m_d->commandsAdapter.setOpacity(node, opacity);
}
}
void KisNodeManager::setNodeCompositeOp(KisNodeSP node,
const KoCompositeOp* compositeOp)
{
if (!node) return;
if (node->compositeOp() == compositeOp) return;
m_d->commandsAdapter.setCompositeOp(node, compositeOp);
}
void KisNodeManager::slotImageRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes)
{
if (activeNode) {
slotNonUiActivatedNode(activeNode);
}
if (!selectedNodes.isEmpty()) {
slotSetSelectedNodes(selectedNodes);
}
}
void KisNodeManager::slotSetSelectedNodes(const KisNodeList &nodes)
{
m_d->selectedNodes = nodes;
emit sigUiNeedChangeSelectedNodes(nodes);
}
KisNodeList KisNodeManager::selectedNodes()
{
return m_d->selectedNodes;
}
KisNodeSelectionAdapter* KisNodeManager::nodeSelectionAdapter() const
{
return m_d->nodeSelectionAdapter.data();
}
KisNodeInsertionAdapter* KisNodeManager::nodeInsertionAdapter() const
{
return m_d->nodeInsertionAdapter.data();
}
void KisNodeManager::nodeOpacityChanged(qreal opacity, bool finalChange)
{
KisNodeSP node = activeNode();
setNodeOpacity(node, convertOpacityToInt(opacity), finalChange);
}
void KisNodeManager::nodeCompositeOpChanged(const KoCompositeOp* op)
{
KisNodeSP node = activeNode();
setNodeCompositeOp(node, op);
}
void KisNodeManager::duplicateActiveNode()
{
KUndo2MagicString actionName = kundo2_i18n("Duplicate Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->duplicateNode(selectedNodes());
}
KisNodeJugglerCompressed* KisNodeManager::Private::lazyGetJuggler(const KUndo2MagicString &actionName)
{
KisImageWSP image = view->image();
if (!nodeJuggler ||
(nodeJuggler &&
!nodeJuggler->canMergeAction(actionName))) {
nodeJuggler = new KisNodeJugglerCompressed(actionName, image, q, 750);
nodeJuggler->setAutoDelete(true);
}
return nodeJuggler;
}
void KisNodeManager::raiseNode()
{
KUndo2MagicString actionName = kundo2_i18n("Raise Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->raiseNode(selectedNodes());
}
void KisNodeManager::lowerNode()
{
KUndo2MagicString actionName = kundo2_i18n("Lower Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->lowerNode(selectedNodes());
}
void KisNodeManager::removeSingleNode(KisNodeSP node)
{
if (!node || !node->parent()) {
return;
}
KisNodeList nodes;
nodes << node;
removeSelectedNodes(nodes);
}
void KisNodeManager::removeSelectedNodes(KisNodeList nodes)
{
KUndo2MagicString actionName = kundo2_i18n("Remove Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->removeNode(nodes);
}
void KisNodeManager::removeNode()
{
removeSelectedNodes(selectedNodes());
}
void KisNodeManager::mirrorNodeX()
{
KisNodeSP node = activeNode();
KUndo2MagicString commandName;
if (node->inherits("KisLayer")) {
commandName = kundo2_i18n("Mirror Layer X");
} else if (node->inherits("KisMask")) {
commandName = kundo2_i18n("Mirror Mask X");
}
mirrorNode(node, commandName, Qt::Horizontal);
}
void KisNodeManager::mirrorNodeY()
{
KisNodeSP node = activeNode();
KUndo2MagicString commandName;
if (node->inherits("KisLayer")) {
commandName = kundo2_i18n("Mirror Layer Y");
} else if (node->inherits("KisMask")) {
commandName = kundo2_i18n("Mirror Mask Y");
}
mirrorNode(node, commandName, Qt::Vertical);
}
inline bool checkForGlobalSelection(KisNodeSP node) {
return dynamic_cast(node.data()) && node->parent() && !node->parent()->parent();
}
void KisNodeManager::activateNextNode()
{
KisNodeSP activeNode = this->activeNode();
if (!activeNode) return;
KisNodeSP node = activeNode->nextSibling();
while (node && node->childCount() > 0) {
node = node->firstChild();
}
if (!node && activeNode->parent() && activeNode->parent()->parent()) {
node = activeNode->parent();
}
while(node && checkForGlobalSelection(node)) {
node = node->nextSibling();
}
if (node) {
slotNonUiActivatedNode(node);
}
}
void KisNodeManager::activatePreviousNode()
{
KisNodeSP activeNode = this->activeNode();
if (!activeNode) return;
KisNodeSP node;
if (activeNode->childCount() > 0) {
node = activeNode->lastChild();
}
else {
node = activeNode->prevSibling();
}
while (!node && activeNode->parent()) {
node = activeNode->parent()->prevSibling();
activeNode = activeNode->parent();
}
while(node && checkForGlobalSelection(node)) {
node = node->prevSibling();
}
if (node) {
slotNonUiActivatedNode(node);
}
}
void KisNodeManager::switchToPreviouslyActiveNode()
{
if (m_d->previouslyActiveNode && m_d->previouslyActiveNode->parent()) {
slotNonUiActivatedNode(m_d->previouslyActiveNode);
}
}
void KisNodeManager::mergeLayer()
{
m_d->layerManager.mergeLayer();
}
void KisNodeManager::rotate(double radians)
{
if(!m_d->view->image()) return;
KisNodeSP node = activeNode();
if (!node) return;
if (!m_d->view->blockUntilOperationsFinished(m_d->view->image())) return;
m_d->view->image()->rotateNode(node, radians);
}
void KisNodeManager::rotate180()
{
rotate(M_PI);
}
void KisNodeManager::rotateLeft90()
{
rotate(-M_PI / 2);
}
void KisNodeManager::rotateRight90()
{
rotate(M_PI / 2);
}
void KisNodeManager::shear(double angleX, double angleY)
{
if (!m_d->view->image()) return;
KisNodeSP node = activeNode();
if (!node) return;
if(!m_d->view->blockUntilOperationsFinished(m_d->view->image())) return;
m_d->view->image()->shearNode(node, angleX, angleY);
}
void KisNodeManager::scale(double sx, double sy, KisFilterStrategy *filterStrategy)
{
KisNodeSP node = activeNode();
KIS_ASSERT_RECOVER_RETURN(node);
m_d->view->image()->scaleNode(node, sx, sy, filterStrategy);
nodesUpdated();
}
void KisNodeManager::mirrorNode(KisNodeSP node, const KUndo2MagicString& actionName, Qt::Orientation orientation)
{
KisImageSignalVector emitSignals;
emitSignals << ModifiedSignal;
KisProcessingApplicator applicator(m_d->view->image(), node,
KisProcessingApplicator::RECURSIVE,
emitSignals, actionName);
KisProcessingVisitorSP visitor =
new KisMirrorProcessingVisitor(m_d->view->image()->bounds(), orientation);
applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
applicator.end();
nodesUpdated();
}
void KisNodeManager::Private::saveDeviceAsImage(KisPaintDeviceSP device,
const QString &defaultName,
const QRect &bounds,
qreal xRes,
qreal yRes,
quint8 opacity)
{
KoFileDialog dialog(view->mainWindow(), KoFileDialog::SaveFile, "savenodeasimage");
dialog.setCaption(i18n("Export \"%1\"", defaultName));
dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation));
dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Export));
QString filename = dialog.filename();
if (filename.isEmpty()) return;
QUrl url = QUrl::fromLocalFile(filename);
if (url.isEmpty()) return;
QString mimefilter = KisMimeDatabase::mimeTypeForFile(filename);;
QScopedPointer doc(KisPart::instance()->createDocument());
KisImageSP dst = new KisImage(doc->createUndoStore(),
bounds.width(),
bounds.height(),
device->compositionSourceColorSpace(),
defaultName);
dst->setResolution(xRes, yRes);
doc->setCurrentImage(dst);
KisPaintLayer* paintLayer = new KisPaintLayer(dst, "paint device", opacity);
paintLayer->paintDevice()->makeCloneFrom(device, bounds);
dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0));
dst->initialRefreshGraph();
doc->setOutputMimeType(mimefilter.toLatin1());
doc->exportDocument(url);
}
void KisNodeManager::saveNodeAsImage()
{
KisNodeSP node = activeNode();
if (!node) {
warnKrita << "BUG: Save Node As Image was called without any node selected";
return;
}
KisImageWSP image = m_d->view->image();
QRect saveRect = image->bounds() | node->exactBounds();
KisPaintDeviceSP device = node->paintDevice();
if (!device) {
device = node->projection();
}
m_d->saveDeviceAsImage(device, node->name(),
saveRect,
image->xRes(), image->yRes(),
node->opacity());
}
void KisNodeManager::slotSplitAlphaIntoMask()
{
KisNodeSP node = activeNode();
// guaranteed by KisActionManager
KIS_ASSERT_RECOVER_RETURN(node->hasEditablePaintDevice());
KisPaintDeviceSP srcDevice = node->paintDevice();
const KoColorSpace *srcCS = srcDevice->colorSpace();
const QRect processRect =
srcDevice->exactBounds() |
srcDevice->defaultBounds()->bounds();
KisPaintDeviceSP selectionDevice =
new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8());
m_d->commandsAdapter.beginMacro(kundo2_i18n("Split Alpha into a Mask"));
KisTransaction transaction(kundo2_noi18n("__split_alpha_channel__"), srcDevice);
KisSequentialIterator srcIt(srcDevice, processRect);
KisSequentialIterator dstIt(selectionDevice, processRect);
do {
quint8 *srcPtr = srcIt.rawData();
quint8 *alpha8Ptr = dstIt.rawData();
*alpha8Ptr = srcCS->opacityU8(srcPtr);
srcCS->setOpacity(srcPtr, OPACITY_OPAQUE_U8, 1);
} while (srcIt.nextPixel() && dstIt.nextPixel());
m_d->commandsAdapter.addExtraCommand(transaction.endAndTake());
createNode("KisTransparencyMask", false, selectionDevice);
m_d->commandsAdapter.endMacro();
}
void KisNodeManager::Private::mergeTransparencyMaskAsAlpha(bool writeToLayers)
{
KisNodeSP node = q->activeNode();
KisNodeSP parentNode = node->parent();
// guaranteed by KisActionManager
KIS_ASSERT_RECOVER_RETURN(node->inherits("KisTransparencyMask"));
if (writeToLayers && !parentNode->hasEditablePaintDevice()) {
QMessageBox::information(view->mainWindow(),
i18nc("@title:window", "Layer %1 is not editable").arg(parentNode->name()),
i18n("Cannot write alpha channel of "
"the parent layer \"%1\".\n"
"The operation will be cancelled.").arg(parentNode->name()));
return;
}
KisPaintDeviceSP dstDevice;
if (writeToLayers) {
KIS_ASSERT_RECOVER_RETURN(parentNode->paintDevice());
dstDevice = parentNode->paintDevice();
} else {
KisPaintDeviceSP copyDevice = parentNode->paintDevice();
if (!copyDevice) {
copyDevice = parentNode->original();
}
dstDevice = new KisPaintDevice(*copyDevice);
}
const KoColorSpace *dstCS = dstDevice->colorSpace();
KisPaintDeviceSP selectionDevice = node->paintDevice();
KIS_ASSERT_RECOVER_RETURN(selectionDevice->colorSpace()->pixelSize() == 1);
const QRect processRect =
selectionDevice->exactBounds() |
dstDevice->exactBounds() |
selectionDevice->defaultBounds()->bounds();
QScopedPointer transaction;
if (writeToLayers) {
commandsAdapter.beginMacro(kundo2_i18n("Write Alpha into a Layer"));
transaction.reset(new KisTransaction(kundo2_noi18n("__write_alpha_channel__"), dstDevice));
}
KisSequentialIterator srcIt(selectionDevice, processRect);
KisSequentialIterator dstIt(dstDevice, processRect);
do {
quint8 *alpha8Ptr = srcIt.rawData();
quint8 *dstPtr = dstIt.rawData();
dstCS->setOpacity(dstPtr, *alpha8Ptr, 1);
} while (srcIt.nextPixel() && dstIt.nextPixel());
if (writeToLayers) {
commandsAdapter.addExtraCommand(transaction->endAndTake());
commandsAdapter.removeNode(node);
commandsAdapter.endMacro();
} else {
KisImageWSP image = view->image();
QRect saveRect = image->bounds();
saveDeviceAsImage(dstDevice, parentNode->name(),
saveRect,
image->xRes(), image->yRes(),
OPACITY_OPAQUE_U8);
}
}
void KisNodeManager::slotSplitAlphaWrite()
{
m_d->mergeTransparencyMaskAsAlpha(true);
}
void KisNodeManager::slotSplitAlphaSaveMerged()
{
m_d->mergeTransparencyMaskAsAlpha(false);
}
void KisNodeManager::cutLayersToClipboard()
{
KisNodeList nodes = this->selectedNodes();
KisNodeSP root = m_d->view->image()->root();
if (nodes.isEmpty()) return;
KisClipboard::instance()->setLayers(nodes, root, false);
KUndo2MagicString actionName = kundo2_i18n("Cut Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->removeNode(nodes);
}
void KisNodeManager::copyLayersToClipboard()
{
KisNodeList nodes = this->selectedNodes();
KisNodeSP root = m_d->view->image()->root();
KisClipboard::instance()->setLayers(nodes, root, true);
}
void KisNodeManager::pasteLayersFromClipboard()
{
const QMimeData *data = KisClipboard::instance()->layersMimeData();
if (!data) return;
KisNodeSP activeNode = this->activeNode();
KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController());
Q_ASSERT(shapeController);
KisDummiesFacadeBase *dummiesFacade = dynamic_cast(m_d->imageView->document()->shapeController());
Q_ASSERT(dummiesFacade);
const bool copyNode = false;
KisImageSP image = m_d->view->image();
KisNodeDummy *parentDummy = dummiesFacade->dummyForNode(activeNode);
KisNodeDummy *aboveThisDummy = parentDummy ? parentDummy->lastChild() : 0;
KisMimeData::insertMimeLayers(data,
image,
shapeController,
parentDummy,
aboveThisDummy,
copyNode,
nodeInsertionAdapter());
}
void KisNodeManager::createQuickGroupImpl(KisNodeJugglerCompressed *juggler,
const QString &overrideGroupName,
KisNodeSP *newGroup,
KisNodeSP *newLastChild)
{
KisNodeSP active = activeNode();
if (!active) return;
KisImageSP image = m_d->view->image();
QString groupName = !overrideGroupName.isEmpty() ? overrideGroupName : image->nextLayerName();
KisGroupLayerSP group = new KisGroupLayer(image.data(), groupName, OPACITY_OPAQUE_U8);
KisNodeList nodes1;
nodes1 << group;
KisNodeList nodes2;
nodes2 = KisLayerUtils::sortMergableNodes(image->root(), selectedNodes());
KisLayerUtils::filterMergableNodes(nodes2);
if (KisLayerUtils::checkIsChildOf(active, nodes2)) {
active = nodes2.first();
}
KisNodeSP parent = active->parent();
KisNodeSP aboveThis = active;
juggler->addNode(nodes1, parent, aboveThis);
juggler->moveNode(nodes2, group, 0);
*newGroup = group;
*newLastChild = nodes2.last();
}
void KisNodeManager::createQuickGroup()
{
KUndo2MagicString actionName = kundo2_i18n("Quick Group");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
KisNodeSP parent;
KisNodeSP above;
createQuickGroupImpl(juggler, "", &parent, &above);
}
void KisNodeManager::createQuickClippingGroup()
{
KUndo2MagicString actionName = kundo2_i18n("Quick Clipping Group");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
KisNodeSP parent;
KisNodeSP above;
KisImageSP image = m_d->view->image();
createQuickGroupImpl(juggler, image->nextLayerName(i18nc("default name for a clipping group layer", "Clipping Group")), &parent, &above);
KisPaintLayerSP maskLayer = new KisPaintLayer(image.data(), i18nc("default name for quick clip group mask layer", "Mask Layer"), OPACITY_OPAQUE_U8, image->colorSpace());
maskLayer->disableAlphaChannel(true);
juggler->addNode(KisNodeList() << maskLayer, parent, above);
}
void KisNodeManager::quickUngroup()
{
KisNodeSP active = activeNode();
if (!active) return;
KisNodeSP parent = active->parent();
KisNodeSP aboveThis = active;
KUndo2MagicString actionName = kundo2_i18n("Quick Ungroup");
if (parent && dynamic_cast(active.data())) {
KisNodeList nodes = active->childNodes(QStringList(), KoProperties());
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->moveNode(nodes, parent, active);
juggler->removeNode(KisNodeList() << active);
} else if (parent && parent->parent()) {
KisNodeSP grandParent = parent->parent();
KisNodeList allChildNodes = parent->childNodes(QStringList(), KoProperties());
KisNodeList allSelectedNodes = selectedNodes();
const bool removeParent = KritaUtils::compareListsUnordered(allChildNodes, allSelectedNodes);
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->moveNode(allSelectedNodes, grandParent, parent);
if (removeParent) {
juggler->removeNode(KisNodeList() << parent);
}
}
}
void KisNodeManager::selectLayersImpl(const KoProperties &props, const KoProperties &invertedProps)
{
KisImageSP image = m_d->view->image();
KisNodeList nodes = KisLayerUtils::findNodesWithProps(image->root(), props, true);
KisNodeList selectedNodes = this->selectedNodes();
if (KritaUtils::compareListsUnordered(nodes, selectedNodes)) {
nodes = KisLayerUtils::findNodesWithProps(image->root(), invertedProps, true);
}
if (!nodes.isEmpty()) {
slotImageRequestNodeReselection(nodes.last(), nodes);
}
}
void KisNodeManager::selectAllNodes()
{
KoProperties props;
selectLayersImpl(props, props);
}
void KisNodeManager::selectVisibleNodes()
{
KoProperties props;
props.setProperty("visible", true);
KoProperties invertedProps;
invertedProps.setProperty("visible", false);
selectLayersImpl(props, invertedProps);
}
void KisNodeManager::selectLockedNodes()
{
KoProperties props;
props.setProperty("locked", true);
KoProperties invertedProps;
invertedProps.setProperty("locked", false);
selectLayersImpl(props, invertedProps);
}
void KisNodeManager::selectInvisibleNodes()
{
KoProperties props;
props.setProperty("visible", false);
KoProperties invertedProps;
invertedProps.setProperty("visible", true);
selectLayersImpl(props, invertedProps);
}
void KisNodeManager::selectUnlockedNodes()
{
KoProperties props;
props.setProperty("locked", false);
KoProperties invertedProps;
invertedProps.setProperty("locked", true);
selectLayersImpl(props, invertedProps);
}
diff --git a/libs/ui/widgets/KisVisualColorSelector.cpp b/libs/ui/widgets/KisVisualColorSelector.cpp
index 70bc2ae838..fd9de46943 100644
--- a/libs/ui/widgets/KisVisualColorSelector.cpp
+++ b/libs/ui/widgets/KisVisualColorSelector.cpp
@@ -1,442 +1,441 @@
/*
* Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016
*
* This program is free software; you can 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 "KisVisualColorSelector.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "KoColorConversions.h"
#include "KoColorDisplayRendererInterface.h"
#include "KoChannelInfo.h"
#include
#include
#include "kis_signal_compressor.h"
#include "kis_debug.h"
#include "KisVisualColorSelectorShape.h"
#include "KisVisualRectangleSelectorShape.h"
#include "KisVisualTriangleSelectorShape.h"
#include "KisVisualEllipticalSelectorShape.h"
struct KisVisualColorSelector::Private
{
KoColor currentcolor;
const KoColorSpace *currentCS {0};
QList widgetlist;
bool updateSelf {false};
bool updateLonesome {false}; // for modal dialogs.
bool circular {false};
const KoColorDisplayRendererInterface *displayRenderer {0};
KisColorSelectorConfiguration acs_config;
KisSignalCompressor *updateTimer {0};
};
KisVisualColorSelector::KisVisualColorSelector(QWidget *parent)
: KisColorSelectorInterface(parent)
, m_d(new Private)
{
this->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
QVBoxLayout *layout = new QVBoxLayout;
this->setLayout(layout);
KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
m_d->acs_config = KisColorSelectorConfiguration::fromString(cfg.readEntry("colorSelectorConfiguration", KisColorSelectorConfiguration().toString()));
m_d->updateTimer = new KisSignalCompressor(100 /* ms */, KisSignalCompressor::POSTPONE);
connect(m_d->updateTimer, SIGNAL(timeout()), SLOT(slotRebuildSelectors()), Qt::UniqueConnection);
}
KisVisualColorSelector::~KisVisualColorSelector()
{
delete m_d->updateTimer;
}
void KisVisualColorSelector::slotSetColor(const KoColor &c)
{
if (m_d->updateSelf == false) {
m_d->currentcolor = c;
if (m_d->currentCS != c.colorSpace()) {
slotsetColorSpace(c.colorSpace());
}
}
updateSelectorElements(QObject::sender());
}
void KisVisualColorSelector::slotsetColorSpace(const KoColorSpace *cs)
{
if (m_d->currentCS != cs) {
m_d->currentCS = cs;
slotRebuildSelectors();
}
}
void KisVisualColorSelector::setConfig(bool forceCircular, bool forceSelfUpdate)
{
m_d->circular = forceCircular;
m_d->updateLonesome = forceSelfUpdate;
}
KoColor KisVisualColorSelector::getCurrentColor() const
{
return m_d->currentcolor;
}
void KisVisualColorSelector::configurationChanged()
{
if (m_d->updateTimer) {
m_d->updateTimer->start();
}
}
void KisVisualColorSelector::slotRebuildSelectors()
{
KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
m_d->acs_config = KisColorSelectorConfiguration::fromString(cfg.readEntry("colorSelectorConfiguration", KisColorSelectorConfiguration().toString()));
qDeleteAll(children());
m_d->widgetlist.clear();
QLayout *layout = new QHBoxLayout;
//redraw all the widgets.
int sizeValue = qMin(width(), height());
int borderWidth = qMax(sizeValue*0.1, 20.0);
if (m_d->currentCS->colorChannelCount() == 1) {
KisVisualColorSelectorShape *bar;
if (m_d->circular==false) {
bar = new KisVisualRectangleSelectorShape(this, KisVisualColorSelectorShape::onedimensional,KisVisualColorSelectorShape::Channel, m_d->currentCS, 0, 0,m_d->displayRenderer, borderWidth);
bar->setMaximumWidth(width()*0.1);
bar->setMaximumHeight(height());
}
else {
bar = new KisVisualEllipticalSelectorShape(this, KisVisualColorSelectorShape::onedimensional,KisVisualColorSelectorShape::Channel, m_d->currentCS, 0, 0,m_d->displayRenderer, borderWidth, KisVisualEllipticalSelectorShape::borderMirrored);
layout->setMargin(0);
}
connect (bar, SIGNAL(sigNewColor(KoColor)), this, SLOT(updateFromWidgets(KoColor)));
layout->addWidget(bar);
m_d->widgetlist.append(bar);
}
else if (m_d->currentCS->colorChannelCount() == 3) {
QRect newrect(0,0, this->geometry().width(), this->geometry().height());
KisVisualColorSelectorShape::ColorModel modelS = KisVisualColorSelectorShape::HSV;
int channel1 = 0;
int channel2 = 1;
int channel3 = 2;
switch(m_d->acs_config.subTypeParameter)
{
case KisColorSelectorConfiguration::H:
channel1 = 0;
break;
case KisColorSelectorConfiguration::hsyS:
case KisColorSelectorConfiguration::hsiS:
case KisColorSelectorConfiguration::hslS:
case KisColorSelectorConfiguration::hsvS:
channel1 = 1;
break;
case KisColorSelectorConfiguration::V:
case KisColorSelectorConfiguration::L:
case KisColorSelectorConfiguration::I:
case KisColorSelectorConfiguration::Y:
channel1 = 2;
break;
default:
Q_ASSERT_X(false, "", "Invalid acs_config.subTypeParameter");
}
switch(m_d->acs_config.mainTypeParameter)
{
case KisColorSelectorConfiguration::hsySH:
modelS = KisVisualColorSelectorShape::HSY;
channel2 = 0;
channel3 = 1;
break;
case KisColorSelectorConfiguration::hsiSH:
modelS = KisVisualColorSelectorShape::HSI;
channel2 = 0;
channel3 = 1;
break;
case KisColorSelectorConfiguration::hslSH:
modelS = KisVisualColorSelectorShape::HSL;
channel2 = 0;
channel3 = 1;
break;
case KisColorSelectorConfiguration::hsvSH:
modelS = KisVisualColorSelectorShape::HSV;
channel2 = 0;
channel3 = 1;
break;
case KisColorSelectorConfiguration::YH:
modelS = KisVisualColorSelectorShape::HSY;
channel2 = 0;
channel3 = 2;
break;
case KisColorSelectorConfiguration::LH:
modelS = KisVisualColorSelectorShape::HSL;
channel2 = 0;
channel3 = 2;
break;
case KisColorSelectorConfiguration::IH:
modelS = KisVisualColorSelectorShape::HSL;
channel2 = 0;
channel3 = 2;
break;
case KisColorSelectorConfiguration::VH:
modelS = KisVisualColorSelectorShape::HSV;
channel2 = 0;
channel3 = 2;
break;
case KisColorSelectorConfiguration::SY:
modelS = KisVisualColorSelectorShape::HSY;
channel2 = 1;
channel3 = 2;
break;
case KisColorSelectorConfiguration::SI:
modelS = KisVisualColorSelectorShape::HSI;
channel2 = 1;
channel3 = 2;
break;
case KisColorSelectorConfiguration::SL:
modelS = KisVisualColorSelectorShape::HSL;
channel2 = 1;
channel3 = 2;
break;
case KisColorSelectorConfiguration::SV:
case KisColorSelectorConfiguration::SV2:
modelS = KisVisualColorSelectorShape::HSV;
channel2 = 1;
channel3 = 2;
break;
default:
Q_ASSERT_X(false, "", "Invalid acs_config.mainTypeParameter");
}
if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Triangle) {
modelS = KisVisualColorSelectorShape::HSV;
//Triangle only really works in HSV mode.
}
KisVisualColorSelectorShape *bar;
if (m_d->acs_config.subType == KisColorSelectorConfiguration::Ring) {
bar = new KisVisualEllipticalSelectorShape(this,
KisVisualColorSelectorShape::onedimensional,
modelS,
m_d->currentCS, channel1, channel1,
m_d->displayRenderer, borderWidth,KisVisualEllipticalSelectorShape::border);
bar->resize(sizeValue, sizeValue);
}
else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular == false) {
bar = new KisVisualRectangleSelectorShape(this,
KisVisualColorSelectorShape::onedimensional,
modelS,
m_d->currentCS, channel1, channel1,
m_d->displayRenderer, borderWidth);
bar->setMaximumWidth(borderWidth);
bar->setMinimumWidth(borderWidth);
bar->setMinimumHeight(sizeValue);
}
else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular == true) {
bar = new KisVisualEllipticalSelectorShape(this,
KisVisualColorSelectorShape::onedimensional,
modelS,
m_d->currentCS, channel1, channel1,
m_d->displayRenderer, borderWidth, KisVisualEllipticalSelectorShape::borderMirrored);
bar->resize(sizeValue, sizeValue);
} else {
// Accessing bar below would crash since it's not initialized.
// Hopefully this can never happen.
warnUI << "Invalid subType, cannot initialize KisVisualColorSelectorShape";
Q_ASSERT_X(false, "", "Invalid subType, cannot initialize KisVisualColorSelectorShape");
return;
}
bar->setColor(m_d->currentcolor);
m_d->widgetlist.append(bar);
KisVisualColorSelectorShape *block;
if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Triangle) {
block = new KisVisualTriangleSelectorShape(this, KisVisualColorSelectorShape::twodimensional,
modelS,
m_d->currentCS, channel2, channel3,
m_d->displayRenderer);
block->setGeometry(bar->getSpaceForTriangle(newrect));
}
else if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Square) {
block = new KisVisualRectangleSelectorShape(this, KisVisualColorSelectorShape::twodimensional,
modelS,
m_d->currentCS, channel2, channel3,
m_d->displayRenderer);
block->setGeometry(bar->getSpaceForSquare(newrect));
}
else {
block = new KisVisualEllipticalSelectorShape(this, KisVisualColorSelectorShape::twodimensional,
modelS,
m_d->currentCS, channel2, channel3,
m_d->displayRenderer);
block->setGeometry(bar->getSpaceForCircle(newrect));
}
block->setColor(m_d->currentcolor);
connect (bar, SIGNAL(sigNewColor(KoColor)), block, SLOT(setColorFromSibling(KoColor)));
connect (block, SIGNAL(sigNewColor(KoColor)), SLOT(updateFromWidgets(KoColor)));
connect (bar, SIGNAL(sigHSXchange()), SLOT(HSXwrangler()));
connect (block, SIGNAL(sigHSXchange()), SLOT(HSXwrangler()));
m_d->widgetlist.append(block);
}
else if (m_d->currentCS->colorChannelCount() == 4) {
KisVisualRectangleSelectorShape *block = new KisVisualRectangleSelectorShape(this, KisVisualRectangleSelectorShape::twodimensional,KisVisualColorSelectorShape::Channel, m_d->currentCS, 0, 1);
KisVisualRectangleSelectorShape *block2 = new KisVisualRectangleSelectorShape(this, KisVisualRectangleSelectorShape::twodimensional,KisVisualColorSelectorShape::Channel, m_d->currentCS, 2, 3);
block->setMaximumWidth(width()*0.5);
block->setMaximumHeight(height());
block2->setMaximumWidth(width()*0.5);
block2->setMaximumHeight(height());
block->setColor(m_d->currentcolor);
block2->setColor(m_d->currentcolor);
connect (block, SIGNAL(sigNewColor(KoColor)), block2, SLOT(setColorFromSibling(KoColor)));
connect (block2, SIGNAL(sigNewColor(KoColor)), SLOT(updateFromWidgets(KoColor)));
layout->addWidget(block);
layout->addWidget(block2);
m_d->widgetlist.append(block);
m_d->widgetlist.append(block2);
}
- Q_ASSERT(m_d->widgetlist.size() == 2);
this->setLayout(layout);
}
void KisVisualColorSelector::setDisplayRenderer (const KoColorDisplayRendererInterface *displayRenderer) {
m_d->displayRenderer = displayRenderer;
if (m_d->widgetlist.size()>0) {
Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) {
shape->setDisplayRenderer(displayRenderer);
}
}
}
void KisVisualColorSelector::updateSelectorElements(QObject *source)
{
//first lock all elements from sending updates, then update all elements.
Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) {
shape->blockSignals(true);
}
Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) {
if (shape!=source) {
if (m_d->updateSelf) {
shape->setColorFromSibling(m_d->currentcolor);
} else {
shape->setColor(m_d->currentcolor);
}
}
}
Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) {
shape->blockSignals(false);
}
}
void KisVisualColorSelector::updateFromWidgets(KoColor c)
{
m_d->currentcolor = c;
m_d->updateSelf = true;
if (m_d->updateLonesome) {
slotSetColor(c);
Q_EMIT sigNewColor(c);
} else {
Q_EMIT sigNewColor(c);
}
}
void KisVisualColorSelector::leaveEvent(QEvent *)
{
m_d->updateSelf = false;
}
void KisVisualColorSelector::resizeEvent(QResizeEvent *) {
int sizeValue = qMin(width(), height());
int borderWidth = qMax(sizeValue*0.1, 20.0);
QRect newrect(0,0, this->geometry().width(), this->geometry().height());
if (!m_d->currentCS) {
slotsetColorSpace(m_d->currentcolor.colorSpace());
}
if (m_d->currentCS->colorChannelCount()==3) {
if (m_d->acs_config.subType == KisColorSelectorConfiguration::Ring) {
m_d->widgetlist.at(0)->resize(sizeValue,sizeValue);
}
else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular==false) {
m_d->widgetlist.at(0)->setMaximumWidth(borderWidth);
m_d->widgetlist.at(0)->setMinimumWidth(borderWidth);
m_d->widgetlist.at(0)->setMinimumHeight(sizeValue);
m_d->widgetlist.at(0)->setMaximumHeight(sizeValue);
}
else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular==true) {
m_d->widgetlist.at(0)->resize(sizeValue,sizeValue);
}
m_d->widgetlist.at(0)->setBorderWidth(borderWidth);
if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Triangle) {
m_d->widgetlist.at(1)->setGeometry(m_d->widgetlist.at(0)->getSpaceForTriangle(newrect));
}
else if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Square) {
m_d->widgetlist.at(1)->setGeometry(m_d->widgetlist.at(0)->getSpaceForSquare(newrect));
}
else if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Wheel) {
m_d->widgetlist.at(1)->setGeometry(m_d->widgetlist.at(0)->getSpaceForCircle(newrect));
}
}
Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) {
shape->update();
}
}
void KisVisualColorSelector::HSXwrangler()
{
//qDebug() << this << "HSXWrangler";
QVector currentCoordinates = QVector(3);
QVector w1 = m_d->widgetlist.at(0)->getHSX(currentCoordinates, true);
QVector w2 = m_d->widgetlist.at(1)->getHSX(currentCoordinates, true);
QVector ch(3);
ch[0] = m_d->widgetlist.at(0)->getChannels().at(0);
ch[1] = m_d->widgetlist.at(1)->getChannels().at(0);
ch[2] = m_d->widgetlist.at(1)->getChannels().at(1);
currentCoordinates[ch[0]] = w1[ch[0]];
currentCoordinates[ch[1]] = w2[ch[1]];
currentCoordinates[ch[2]] = w2[ch[2]];
m_d->widgetlist.at(0)->setHSX(currentCoordinates, true);
m_d->widgetlist.at(1)->setHSX(currentCoordinates, true);
}
diff --git a/plugins/paintops/defaultpaintops/brush/tests/CMakeLists.txt b/plugins/paintops/defaultpaintops/brush/tests/CMakeLists.txt
index 9bf327df1b..ed22adf8e7 100644
--- a/plugins/paintops/defaultpaintops/brush/tests/CMakeLists.txt
+++ b/plugins/paintops/defaultpaintops/brush/tests/CMakeLists.txt
@@ -1,10 +1,14 @@
set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests )
include(KritaAddBrokenUnitTest)
+
macro_add_unittest_definitions()
+include(ECMAddTests)
+
krita_add_broken_unit_test(kis_brushop_test.cpp ../../../../../sdk/tests/stroke_testing_utils.cpp
TEST_NAME krita-plugins-KisBrushOpTest
LINK_LIBRARIES kritaimage kritaui kritalibpaintop Qt5::Test)
+
diff --git a/plugins/paintops/defaultpaintops/brush/tests/kis_clone_test.h b/plugins/paintops/defaultpaintops/brush/tests/kis_clone_test.h
new file mode 100644
index 0000000000..841435c3b5
--- /dev/null
+++ b/plugins/paintops/defaultpaintops/brush/tests/kis_clone_test.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2016 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.
+ */
+
+#ifndef __KIS_CLONEOP_TEST_H
+#define __KIS_CLONEOP_TEST_H
+
+#include
+
+class KisCloneOpTest : public QObject
+{
+ Q_OBJECT
+private Q_SLOTS:
+ void testClone();
+private:
+ void testProjection();
+
+};
+
+#endif /* __KIS_CLONEOP_TEST_H */
diff --git a/plugins/tools/CMakeLists.txt b/plugins/tools/CMakeLists.txt
index 7d3381a403..3afdb4ebdc 100644
--- a/plugins/tools/CMakeLists.txt
+++ b/plugins/tools/CMakeLists.txt
@@ -1,11 +1,12 @@
add_subdirectory( basictools )
add_subdirectory( defaulttool )
add_subdirectory( selectiontools )
add_subdirectory( tool_crop )
add_subdirectory( tool_polygon )
add_subdirectory( tool_polyline )
add_subdirectory( tool_transform2 )
add_subdirectory( tool_dyna )
add_subdirectory( tool_text )
add_subdirectory( karbonplugins )
add_subdirectory( tool_lazybrush )
+add_subdirectory( tool_smart_patch )
diff --git a/plugins/tools/tool_smart_patch/CMakeLists.txt b/plugins/tools/tool_smart_patch/CMakeLists.txt
new file mode 100644
index 0000000000..36203211c7
--- /dev/null
+++ b/plugins/tools/tool_smart_patch/CMakeLists.txt
@@ -0,0 +1,17 @@
+set(kritatoolSmartPatch_SOURCES
+ tool_smartpatch.cpp
+ kis_tool_smart_patch.cpp
+ kis_tool_smart_patch_options_widget.cpp
+ kis_inpaint.cpp
+ )
+
+ki18n_wrap_ui(kritatoolSmartPatch_SOURCES kis_tool_smart_patch_options_widget.ui)
+
+add_library(kritatoolSmartPatch MODULE ${kritatoolSmartPatch_SOURCES})
+
+generate_export_header(kritatoolSmartPatch BASE_NAME kritatoolSmartPatch)
+
+target_link_libraries(kritatoolSmartPatch kritaui)
+
+install(TARGETS kritatoolSmartPatch DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
+
diff --git a/plugins/tools/tool_smart_patch/kis_inpaint.cpp b/plugins/tools/tool_smart_patch/kis_inpaint.cpp
new file mode 100644
index 0000000000..b4eb470eb9
--- /dev/null
+++ b/plugins/tools/tool_smart_patch/kis_inpaint.cpp
@@ -0,0 +1,1014 @@
+/*
+ * 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 = 0;
+const quint8 MASK_CLEAR = 255;
+
+void patchImage(KisPaintDeviceSP, KisPaintDeviceSP, int radius);
+
+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 assignement 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);
+ }
+
+ virtual ~ImageData()
+ {
+ 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 cacheImageSize(KisPaintDeviceSP imageDev)
+ {
+ imageSize = imageDev->exactBounds();
+ }
+
+ void cacheImage(KisPaintDeviceSP imageDev)
+ {
+ Q_ASSERT(!imageSize.isEmpty() && imageSize.isValid());
+ cs = imageDev->colorSpace();
+ nChannels = cs->channelCount();
+ imageData.Init(imageDev, imageSize);
+ }
+
+
+ void cacheMask(KisPaintDeviceSP maskDev)
+ {
+ Q_ASSERT(!imageSize.isEmpty() && imageSize.isValid());
+ Q_ASSERT(maskDev->colorSpace()->pixelSize() == 1);
+ csMask = maskDev->colorSpace();
+ maskData.Init(maskDev, imageSize);
+
+ //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)
+ {
+ cacheImageSize(_imageDev);
+ cacheImage(_imageDev);
+ cacheMask(_maskDev);
+
+ //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;
+
+ if( colorDepthId == Float16BitsColorDepthID )
+ distance = &distance_impl;
+
+ if( colorDepthId == Float32BitsColorDepthID )
+ distance = &distance_impl;
+
+ if( colorDepthId == Float64BitsColorDepthID )
+ distance = &distance_impl;
+ }
+
+ MaskedImage(KisPaintDeviceSP _imageDev, KisPaintDeviceSP _maskDev)
+ {
+ initialize(_imageDev, _maskDev);
+// DebugDump("Initialize");
+ }
+
+ 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 intial 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 = imSize.width() / nnf.imSize.width();
+ float yscale = 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)
+ {
+ initial = new MaskedImage(dev, devMask);
+ radius = _radius;
+ devCache = dev;
+ }
+ 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)
+{
+ KoColor defaultMaskPixel = maskDev->defaultPixel();
+ maskDev->setDefaultPixel(KoColor(Qt::white, maskDev->colorSpace()));
+ QRect maskRect = maskDev->nonDefaultPixelArea();
+ maskDev->setDefaultPixel(defaultMaskPixel);
+
+ return maskRect;
+}
+
+
+QRect patchImage(KisPaintDeviceSP imageDev, KisPaintDeviceSP maskDev, int patchRadius, int accuracy)
+{
+ QRect maskRect = getMaskBoundingBox(maskDev);
+ QRect imageRect = imageDev->exactBounds();
+
+ float scale = 1 + (accuracy / 25); //higher accuracy means we include more surrouding 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);
+
+ KisPaintDeviceSP tempImageDev = new KisPaintDevice(imageDev->colorSpace());
+ KisPaintDeviceSP tempMaskDev = new KisPaintDevice(maskDev->colorSpace());
+ tempImageDev->makeCloneFrom(imageDev, maskRect);
+ tempMaskDev->makeCloneFrom(maskDev, maskRect);
+
+ if (!maskRect.isEmpty()) {
+ Inpaint inpaint(tempImageDev, tempMaskDev, patchRadius);
+ MaskedImageSP output = inpaint.patch();
+ output->toPaintDevice(imageDev, maskRect);
+ }
+
+ return maskRect;
+}
+
diff --git a/plugins/tools/tool_smart_patch/kis_tool_smart_patch.cpp b/plugins/tools/tool_smart_patch/kis_tool_smart_patch.cpp
new file mode 100644
index 0000000000..d65329bd73
--- /dev/null
+++ b/plugins/tools/tool_smart_patch/kis_tool_smart_patch.cpp
@@ -0,0 +1,236 @@
+/*
+ * 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.
+ */
+
+#include "kis_tool_smart_patch.h"
+
+#include "QApplication"
+
+#include
+#include
+
+#include
+#include "kis_canvas2.h"
+#include "kis_cursor.h"
+#include "kis_config.h"
+#include "kundo2magicstring.h"
+
+#include "KoProperties.h"
+#include "KoColorSpaceRegistry.h"
+#include "KoShapeController.h"
+#include "KoDocumentResourceManager.h"
+#include "kis_node_manager.h"
+#include "kis_cursor.h"
+
+#include "kis_tool_smart_patch_options_widget.h"
+#include "libs/image/kis_paint_device_debug_utils.h"
+
+#include "kis_resources_snapshot.h"
+#include "kis_layer.h"
+#include "kis_transaction.h"
+#include "kis_paint_layer.h"
+
+#include "kis_inpaint_mask.h"
+
+QRect patchImage(KisPaintDeviceSP imageDev, KisPaintDeviceSP maskDev, int radius, int accuracy);
+
+struct KisToolSmartPatch::Private {
+ KisMaskSP mask = nullptr;
+ KisNodeSP maskNode = nullptr;
+ KisNodeSP paintNode = nullptr;
+ KisPaintDeviceSP imageDev = nullptr;
+ KisPaintDeviceSP maskDev = nullptr;
+ KisResourcesSnapshotSP resources = nullptr;
+ KoColor currentFgColor;
+ KisToolSmartPatchOptionsWidget *optionsWidget = nullptr;
+};
+
+
+KisToolSmartPatch::KisToolSmartPatch(KoCanvasBase * canvas)
+ : KisToolFreehand(canvas,
+ KisCursor::load("tool_freehand_cursor.png", 5, 5),
+ kundo2_i18n("Smart Patch Stroke")),
+ m_d(new Private)
+{
+ setObjectName("tool_SmartPatch");
+}
+
+KisToolSmartPatch::~KisToolSmartPatch()
+{
+ m_d->optionsWidget = nullptr;
+}
+
+void KisToolSmartPatch::activate(ToolActivation activation, const QSet &shapes)
+{
+ KisToolFreehand::activate(activation, shapes);
+}
+
+void KisToolSmartPatch::deactivate()
+{
+ KisToolFreehand::deactivate();
+}
+
+void KisToolSmartPatch::resetCursorStyle()
+{
+ KisToolFreehand::resetCursorStyle();
+}
+
+
+bool KisToolSmartPatch::canCreateInpaintMask() const
+{
+ KisNodeSP node = currentNode();
+ return node && node->inherits("KisPaintLayer");
+}
+
+QRect KisToolSmartPatch::inpaintImage(KisPaintDeviceSP maskDev, KisPaintDeviceSP imageDev)
+{
+ int accuracy = 50; //default accuracy - middle value
+ int patchRadius = 4; //default radius, which works well for most cases tested
+
+ if (!m_d.isNull() && m_d->optionsWidget) {
+ accuracy = m_d->optionsWidget->getAccuracy();
+ patchRadius = m_d->optionsWidget->getPatchRadius();
+ }
+ return patchImage(imageDev, maskDev, patchRadius, accuracy);
+}
+
+void KisToolSmartPatch::activatePrimaryAction()
+{
+ KisToolFreehand::activatePrimaryAction();
+}
+
+void KisToolSmartPatch::deactivatePrimaryAction()
+{
+ KisToolFreehand::deactivatePrimaryAction();
+}
+
+void KisToolSmartPatch::createInpaintMask(void)
+{
+ m_d->mask = new KisInpaintMask();
+
+ KisLayerSP parentLayer = qobject_cast(m_d->paintNode.data());
+ m_d->mask->initSelection(parentLayer);
+ image()->addNode(m_d->mask, m_d->paintNode);
+}
+
+void KisToolSmartPatch::deleteInpaintMask(void)
+{
+ KisCanvas2 * kiscanvas = static_cast(canvas());
+ KisViewManager* viewManager = kiscanvas->viewManager();
+ if (! m_d->paintNode.isNull())
+ viewManager->nodeManager()->slotNonUiActivatedNode(m_d->paintNode);
+
+ image()->removeNode(m_d->mask);
+ m_d->mask = nullptr;
+}
+
+void KisToolSmartPatch::beginPrimaryAction(KoPointerEvent *event)
+{
+ m_d->paintNode = currentNode();
+
+ KisCanvas2 * kiscanvas = static_cast(canvas());
+ KisViewManager* viewManager = kiscanvas->viewManager();
+
+ //we can only apply inpaint operation to paint layer
+ if (!m_d->paintNode.isNull() && m_d->paintNode->inherits("KisPaintLayer")) {
+
+
+ if (!m_d->mask.isNull()) {
+ viewManager->nodeManager()->slotNonUiActivatedNode(m_d->mask);
+ } else {
+
+ createInpaintMask();
+ viewManager->nodeManager()->slotNonUiActivatedNode(m_d->mask);
+
+ //Collapse freehand drawing of the mask followed by inpaint operation into a single undo node
+ canvas()->shapeController()->resourceManager()->undoStack()->beginMacro(kundo2_i18n("Smart Patch"));
+
+
+ //User will be drawing on an alpha mask. Show color matching inpaint mask color.
+ m_d->currentFgColor = canvas()->resourceManager()->foregroundColor();
+ canvas()->resourceManager()->setForegroundColor(KoColor(Qt::magenta, image()->colorSpace()));
+ }
+ KisToolFreehand::beginPrimaryAction(event);
+ } else {
+ viewManager->
+ showFloatingMessage(
+ i18n("Select a paint layer to use this tool"),
+ QIcon(), 2000, KisFloatingMessage::Medium, Qt::AlignCenter);
+ }
+}
+
+void KisToolSmartPatch::continuePrimaryAction(KoPointerEvent *event)
+{
+ if (!m_d->mask.isNull())
+ KisToolFreehand::continuePrimaryAction(event);
+}
+
+
+void KisToolSmartPatch::endPrimaryAction(KoPointerEvent *event)
+{
+ if (mode() != KisTool::PAINT_MODE)
+ return;
+
+ if (m_d->mask.isNull())
+ return;
+
+ KisToolFreehand::endPrimaryAction(event);
+
+ //Next line is important. We need to wait for the paint operation to finish otherwise
+ //mask will be incomplete.
+ image()->waitForDone();
+
+ //User drew a mask on the temporary inpaint mask layer. Get this mask to pass to the inpaint algorithm
+ m_d->maskDev = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8());
+
+ if (!m_d->mask.isNull()) {
+ m_d->maskDev->makeCloneFrom(m_d->mask->paintDevice(), m_d->mask->paintDevice()->extent());
+
+ //Once we get the mask we delete the temporary layer on which user painted it
+ deleteInpaintMask();
+
+ image()->waitForDone();
+ m_d->imageDev = currentNode()->paintDevice();
+
+ KisTransaction inpaintTransaction(kundo2_i18n("Inpaint Operation"), m_d->imageDev);
+
+ QApplication::setOverrideCursor(KisCursor::waitCursor());
+
+ //actual inpaint operation. filling in areas masked by user
+ QRect changedRect = inpaintImage(m_d->maskDev, m_d->imageDev);
+ currentNode()->setDirty(changedRect);
+ inpaintTransaction.commit(image()->undoAdapter());
+
+ //Matching endmacro for inpaint operation
+ canvas()->shapeController()->resourceManager()->undoStack()->endMacro();
+
+ QApplication::restoreOverrideCursor();
+
+ canvas()->resourceManager()->setForegroundColor(m_d->currentFgColor);
+ }
+}
+
+QWidget * KisToolSmartPatch::createOptionWidget()
+{
+ KisCanvas2 * kiscanvas = dynamic_cast(canvas());
+
+ m_d->optionsWidget = new KisToolSmartPatchOptionsWidget(kiscanvas->viewManager()->resourceProvider(), 0);
+ m_d->optionsWidget->setObjectName(toolId() + "option widget");
+
+ return m_d->optionsWidget;
+}
+
diff --git a/plugins/tools/tool_smart_patch/kis_tool_smart_patch.h b/plugins/tools/tool_smart_patch/kis_tool_smart_patch.h
new file mode 100644
index 0000000000..8797bb38b9
--- /dev/null
+++ b/plugins/tools/tool_smart_patch/kis_tool_smart_patch.h
@@ -0,0 +1,103 @@
+/*
+ * 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.
+ */
+
+#ifndef KIS_TOOL_SMART_PATCH_H_
+#define KIS_TOOL_SMART_PATCH_H_
+
+#include
+#include "kis_tool_freehand.h"
+
+#include "KoToolFactoryBase.h"
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+class KActionCollection;
+class KoCanvasBase;
+
+class KisToolSmartPatch : public KisToolFreehand
+{
+ Q_OBJECT
+public:
+ KisToolSmartPatch(KoCanvasBase * canvas);
+ virtual ~KisToolSmartPatch();
+
+ QWidget * createOptionWidget();
+
+ void activatePrimaryAction();
+ void deactivatePrimaryAction();
+
+ void beginPrimaryAction(KoPointerEvent *event);
+ void continuePrimaryAction(KoPointerEvent *event);
+ void endPrimaryAction(KoPointerEvent *event);
+
+protected Q_SLOTS:
+ void resetCursorStyle();
+
+public Q_SLOTS:
+ virtual void activate(ToolActivation toolActivation, const QSet &shapes);
+ void deactivate();
+
+Q_SIGNALS:
+
+private:
+ bool canCreateInpaintMask() const;
+ QRect inpaintImage(KisPaintDeviceSP maskDev, KisPaintDeviceSP imageDev);
+
+private:
+ struct Private;
+ const QScopedPointer m_d;
+
+ void createInpaintMask();
+ void deleteInpaintMask();
+};
+
+
+class KisToolSmartPatchFactory : public KoToolFactoryBase
+{
+
+public:
+ KisToolSmartPatchFactory()
+ : KoToolFactoryBase("KritaShape/KisToolSmartPatch")
+ {
+
+ setToolTip(i18n("Smart Patch Tool"));
+
+ setSection(TOOL_TYPE_FILL);
+ setIconName(koIconNameCStr("krita_tool_smart_patch"));
+ setShortcut(QKeySequence(Qt::Key_Shift + Qt::Key_I));
+ setPriority(4);
+ setActivationShapeId(KRITA_TOOL_ACTIVATION_ID);
+ }
+
+ virtual ~KisToolSmartPatchFactory() {}
+
+ virtual KoToolBase * createTool(KoCanvasBase *canvas)
+ {
+ return new KisToolSmartPatch(canvas);
+ }
+
+};
+
+
+#endif // KIS_TOOL_SMART_PATCH_H_
diff --git a/plugins/tools/tool_smart_patch/kis_tool_smart_patch_options_widget.cpp b/plugins/tools/tool_smart_patch/kis_tool_smart_patch_options_widget.cpp
new file mode 100644
index 0000000000..6f768c2762
--- /dev/null
+++ b/plugins/tools/tool_smart_patch/kis_tool_smart_patch_options_widget.cpp
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+
+#include "kis_tool_smart_patch_options_widget.h"
+
+#include "ui_kis_tool_smart_patch_options_widget.h"
+
+#include
+#include "KisPaletteModel.h"
+
+#include "kis_config.h"
+#include
+#include "kis_canvas_resource_provider.h"
+
+
+struct KisToolSmartPatchOptionsWidget::Private {
+ Private()
+ {
+ }
+
+ Ui_KisToolSmartPatchOptionsWidget *ui;
+
+ int getPatchRadius(void)
+ {
+ return ui->patchRadius->value();
+ }
+ int getAccuracy(void)
+ {
+ return ui->accuracySlider->value();
+ }
+};
+
+KisToolSmartPatchOptionsWidget::KisToolSmartPatchOptionsWidget(KisCanvasResourceProvider *provider, QWidget *parent)
+ : QWidget(parent),
+ m_d(new Private)
+{
+ m_d->ui = new Ui_KisToolSmartPatchOptionsWidget();
+ m_d->ui->setupUi(this);
+
+}
+
+KisToolSmartPatchOptionsWidget::~KisToolSmartPatchOptionsWidget()
+{
+}
+
+int KisToolSmartPatchOptionsWidget::getPatchRadius()
+{
+ return m_d->getPatchRadius();
+}
+
+int KisToolSmartPatchOptionsWidget::getAccuracy()
+{
+ return m_d->getAccuracy();
+}
+
diff --git a/plugins/tools/tool_smart_patch/kis_tool_smart_patch_options_widget.h b/plugins/tools/tool_smart_patch/kis_tool_smart_patch_options_widget.h
new file mode 100644
index 0000000000..87289a0fc5
--- /dev/null
+++ b/plugins/tools/tool_smart_patch/kis_tool_smart_patch_options_widget.h
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+#ifndef __KIS_TOOL_SMART_PATCH_OPTIONS_WIDGET_H
+#define __KIS_TOOL_SMART_PATCH_OPTIONS_WIDGET_H
+
+#include
+#include
+#include
+
+#include "kis_types.h"
+
+class KisCanvasResourceProvider;
+class KoColor;
+
+
+class KisToolSmartPatchOptionsWidget : public QWidget
+{
+ Q_OBJECT
+public:
+ KisToolSmartPatchOptionsWidget(KisCanvasResourceProvider *provider, QWidget *parent);
+ ~KisToolSmartPatchOptionsWidget();
+
+ int getPatchRadius(void);
+ int getAccuracy(void);
+
+private:
+ struct Private;
+ const QScopedPointer m_d;
+};
+
+#endif /* __KIS_TOOL_SMART_PATCH_OPTIONS_WIDGET_H */
diff --git a/plugins/tools/tool_smart_patch/kis_tool_smart_patch_options_widget.ui b/plugins/tools/tool_smart_patch/kis_tool_smart_patch_options_widget.ui
new file mode 100644
index 0000000000..42c99afe2f
--- /dev/null
+++ b/plugins/tools/tool_smart_patch/kis_tool_smart_patch_options_widget.ui
@@ -0,0 +1,148 @@
+
+
+ KisToolSmartPatchOptionsWidget
+
+
+
+ 0
+ 0
+ 340
+ 235
+
+
+
+ -
+
+
+
+ 200
+ 200
+
+
+
+ Inpaint Tool
+
+
+
+ true
+
+
+
+ 20
+ 40
+ 281
+ 81
+
+
+
+
+ 200
+ 80
+
+
+
+
+
+ 10
+ 16
+ 260
+ 21
+
+
+
+ 25
+
+
+ 25
+
+
+ 50
+
+
+ Qt::Horizontal
+
+
+ QSlider::TicksBelow
+
+
+ 25
+
+
+
+
+
+ 190
+ 40
+ 71
+ 20
+
+
+
+ Qt::LeftToRight
+
+
+ Accurate
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+
+
+ 20
+ 40
+ 41
+ 17
+
+
+
+ Fast
+
+
+
+
+
+
+ 20
+ 153
+ 181
+ 27
+
+
+
+ Patch Radius
+
+
+ label
+
+
+
+
+
+ 220
+ 153
+ 61
+ 27
+
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ 2
+
+
+ 8
+
+
+ 4
+
+
+
+
+
+
+
+
+
diff --git a/plugins/tools/tool_smart_patch/kritatoolsmartpatch.json b/plugins/tools/tool_smart_patch/kritatoolsmartpatch.json
new file mode 100644
index 0000000000..11401c9ddc
--- /dev/null
+++ b/plugins/tools/tool_smart_patch/kritatoolsmartpatch.json
@@ -0,0 +1,9 @@
+{
+ "Id": "Inpaint Tool",
+ "Type": "Service",
+ "X-KDE-Library": "kritatoolsmartpatch",
+ "X-KDE-ServiceTypes": [
+ "Krita/Tool"
+ ],
+ "X-Krita-Version": "28"
+}
diff --git a/plugins/tools/tool_smart_patch/tool_smartpatch.cpp b/plugins/tools/tool_smart_patch/tool_smartpatch.cpp
new file mode 100644
index 0000000000..9539ce78b6
--- /dev/null
+++ b/plugins/tools/tool_smart_patch/tool_smartpatch.cpp
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+#include "tool_smartpatch.h"
+#include
+
+#include
+#include
+
+#include
+#include
+
+#include "kis_paint_device.h"
+#include "kis_tool_smart_patch.h"
+
+
+K_PLUGIN_FACTORY_WITH_JSON(DefaultToolsFactory, "kritatoolsmartpatch.json", registerPlugin();)
+
+
+ToolSmartPatch::ToolSmartPatch(QObject *parent, const QVariantList &)
+ : QObject(parent)
+{
+ KoToolRegistry::instance()->add(new KisToolSmartPatchFactory());
+}
+
+ToolSmartPatch::~ToolSmartPatch()
+{
+}
+
+#include "tool_smartpatch.moc"
diff --git a/plugins/tools/tool_smart_patch/tool_smartpatch.h b/plugins/tools/tool_smart_patch/tool_smartpatch.h
new file mode 100644
index 0000000000..1f3b7c4db6
--- /dev/null
+++ b/plugins/tools/tool_smart_patch/tool_smartpatch.h
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+#ifndef TOOL_SMARTPATCH_H_
+#define TOOL_SMARTPATCH_H_
+
+#include
+#include
+
+class ToolSmartPatch : public QObject
+{
+ Q_OBJECT
+public:
+ ToolSmartPatch(QObject *parent, const QVariantList &);
+ virtual ~ToolSmartPatch();
+
+};
+
+#endif // TOOL_SMARTPATCH_H_