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 Projectimage/svg+xmlBandage 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 Projectimage/svg+xmlBandage 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_