diff --git a/libs/command/kis_command_ids.h b/libs/command/kis_command_ids.h
index a19705d09d..7ad31146d2 100644
--- a/libs/command/kis_command_ids.h
+++ b/libs/command/kis_command_ids.h
@@ -1,42 +1,43 @@
/*
* Copyright (c) 2016 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_COMMAND_IDS_H
#define KIS_COMMAND_IDS_H
namespace KisCommandUtils {
enum CommandId {
MoveShapeId = 9999,
ResizeShapeId,
TransformShapeId,
ChangeShapeTransparencyId,
ChangeShapeBackgroundId,
ChangeShapeStrokeId,
ChangeShapeMarkersId,
ChangeShapeParameterId,
ChangeEllipseShapeId,
ChangeRectangleShapeId,
ChangePathShapePointId,
ChangePathShapeControlPointId,
- ChangePaletteId
+ ChangePaletteId,
+ TransformToolId
};
}
#endif // KIS_COMMAND_IDS_H
diff --git a/libs/command/kundo2commandextradata.h b/libs/command/kundo2commandextradata.h
index 3524d763ad..21776e07b2 100644
--- a/libs/command/kundo2commandextradata.h
+++ b/libs/command/kundo2commandextradata.h
@@ -1,31 +1,32 @@
/*
* Copyright (c) 2015 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KUNDO2COMMANDEXTRADATA_H
#define __KUNDO2COMMANDEXTRADATA_H
#include "kritacommand_export.h"
class KRITACOMMAND_EXPORT KUndo2CommandExtraData
{
public:
virtual ~KUndo2CommandExtraData();
+ virtual KUndo2CommandExtraData* clone() const = 0;
};
#endif /* __KUNDO2COMMANDEXTRADATA_H */
diff --git a/libs/image/CMakeLists.txt b/libs/image/CMakeLists.txt
index 11b8c2dde4..51d6120af8 100644
--- a/libs/image/CMakeLists.txt
+++ b/libs/image/CMakeLists.txt
@@ -1,377 +1,378 @@
add_subdirectory( tests )
add_subdirectory( tiles3 )
include_directories(
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty
${CMAKE_CURRENT_SOURCE_DIR}/brushengine
${CMAKE_CURRENT_SOURCE_DIR}/commands
${CMAKE_CURRENT_SOURCE_DIR}/commands_new
${CMAKE_CURRENT_SOURCE_DIR}/filter
${CMAKE_CURRENT_SOURCE_DIR}/floodfill
${CMAKE_CURRENT_SOURCE_DIR}/generator
${CMAKE_CURRENT_SOURCE_DIR}/layerstyles
${CMAKE_CURRENT_SOURCE_DIR}/processing
${CMAKE_SOURCE_DIR}/sdk/tests
)
include_directories(SYSTEM
${EIGEN3_INCLUDE_DIR}
)
if(FFTW3_FOUND)
include_directories(${FFTW3_INCLUDE_DIR})
endif()
if(HAVE_VC)
include_directories(SYSTEM ${Vc_INCLUDE_DIR} ${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS})
ko_compile_for_all_implementations(__per_arch_circle_mask_generator_objs kis_brush_mask_applicator_factories.cpp)
else()
set(__per_arch_circle_mask_generator_objs kis_brush_mask_applicator_factories.cpp)
endif()
set(kritaimage_LIB_SRCS
tiles3/kis_tile.cc
tiles3/kis_tile_data.cc
tiles3/kis_tile_data_store.cc
tiles3/kis_tile_data_pooler.cc
tiles3/kis_tiled_data_manager.cc
tiles3/KisTiledExtentManager.cpp
tiles3/kis_memento_manager.cc
tiles3/kis_hline_iterator.cpp
tiles3/kis_vline_iterator.cpp
tiles3/kis_random_accessor.cc
tiles3/swap/kis_abstract_compression.cpp
tiles3/swap/kis_lzf_compression.cpp
tiles3/swap/kis_abstract_tile_compressor.cpp
tiles3/swap/kis_legacy_tile_compressor.cpp
tiles3/swap/kis_tile_compressor_2.cpp
tiles3/swap/kis_chunk_allocator.cpp
tiles3/swap/kis_memory_window.cpp
tiles3/swap/kis_swapped_data_store.cpp
tiles3/swap/kis_tile_data_swapper.cpp
kis_distance_information.cpp
kis_painter.cc
kis_painter_blt_multi_fixed.cpp
kis_marker_painter.cpp
KisPrecisePaintDeviceWrapper.cpp
kis_progress_updater.cpp
brushengine/kis_paint_information.cc
brushengine/kis_random_source.cpp
brushengine/KisPerStrokeRandomSource.cpp
brushengine/kis_stroke_random_source.cpp
brushengine/kis_paintop.cc
brushengine/kis_paintop_factory.cpp
brushengine/kis_paintop_preset.cpp
brushengine/kis_paintop_registry.cc
brushengine/kis_paintop_settings.cpp
brushengine/kis_paintop_settings_update_proxy.cpp
brushengine/kis_paintop_utils.cpp
brushengine/kis_no_size_paintop_settings.cpp
brushengine/kis_locked_properties.cc
brushengine/kis_locked_properties_proxy.cpp
brushengine/kis_locked_properties_server.cpp
brushengine/kis_paintop_config_widget.cpp
brushengine/kis_uniform_paintop_property.cpp
brushengine/kis_combo_based_paintop_property.cpp
brushengine/kis_slider_based_paintop_property.cpp
brushengine/kis_standard_uniform_properties_factory.cpp
brushengine/KisStrokeSpeedMeasurer.cpp
brushengine/KisPaintopSettingsIds.cpp
commands/kis_deselect_global_selection_command.cpp
commands/KisDeselectActiveSelectionCommand.cpp
commands/kis_image_change_layers_command.cpp
commands/kis_image_change_visibility_command.cpp
commands/kis_image_command.cpp
commands/kis_image_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_node_command.cpp
commands/kis_node_compositeop_command.cpp
commands/kis_node_opacity_command.cpp
commands/kis_node_property_list_command.cpp
commands/kis_reselect_global_selection_command.cpp
commands/KisReselectActiveSelectionCommand.cpp
commands/kis_set_global_selection_command.cpp
commands/KisNodeRenameCommand.cpp
commands_new/kis_saved_commands.cpp
commands_new/kis_processing_command.cpp
commands_new/kis_image_resize_command.cpp
commands_new/kis_image_set_resolution_command.cpp
commands_new/kis_node_move_command2.cpp
commands_new/kis_set_layer_style_command.cpp
commands_new/kis_selection_move_command2.cpp
commands_new/kis_update_command.cpp
commands_new/kis_switch_current_time_command.cpp
commands_new/kis_change_projection_color_command.cpp
commands_new/kis_activate_selection_mask_command.cpp
commands_new/kis_transaction_based_command.cpp
+ commands_new/KisHoldUIUpdatesCommand.cpp
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
processing/KisSelectionBasedProcessingHelper.cpp
filter/kis_filter.cc
filter/kis_filter_category_ids.cpp
filter/kis_filter_configuration.cc
filter/kis_color_transformation_configuration.cc
filter/kis_filter_registry.cc
filter/kis_color_transformation_filter.cc
generator/kis_generator.cpp
generator/kis_generator_layer.cpp
generator/kis_generator_registry.cpp
floodfill/kis_fill_interval_map.cpp
floodfill/kis_scanline_fill.cpp
lazybrush/kis_min_cut_worker.cpp
lazybrush/kis_lazy_fill_tools.cpp
lazybrush/kis_multiway_cut.cpp
lazybrush/KisWatershedWorker.cpp
lazybrush/kis_colorize_mask.cpp
lazybrush/kis_colorize_stroke_strategy.cpp
KisDelayedUpdateNodeInterface.cpp
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_node_uuid_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_edge_detection_kernel.cpp
kis_cubic_curve.cpp
kis_default_bounds.cpp
kis_default_bounds_base.cpp
kis_effect_mask.cc
kis_fast_math.cpp
kis_fill_painter.cc
kis_filter_mask.cpp
kis_filter_strategy.cc
kis_transform_mask.cpp
kis_transform_mask_params_interface.cpp
kis_recalculate_transform_mask_job.cpp
kis_recalculate_generator_layer_job.cpp
kis_transform_mask_params_factory_registry.cpp
kis_safe_transform.cpp
kis_gradient_painter.cc
kis_gradient_shape_strategy.cpp
kis_cached_gradient_shape_strategy.cpp
kis_polygonal_gradient_shape_strategy.cpp
kis_iterator_ng.cpp
kis_async_merger.cpp
kis_merge_walker.cc
kis_updater_context.cpp
kis_update_job_item.cpp
kis_stroke_strategy_undo_command_based.cpp
kis_simple_stroke_strategy.cpp
KisRunnableBasedStrokeStrategy.cpp
KisRunnableStrokeJobDataBase.cpp
KisRunnableStrokeJobData.cpp
KisRunnableStrokeJobsInterface.cpp
KisFakeRunnableStrokeJobsExecutor.cpp
kis_stroke_job_strategy.cpp
kis_stroke_strategy.cpp
kis_stroke.cpp
kis_strokes_queue.cpp
KisStrokesQueueMutatedJobInterface.cpp
kis_simple_update_queue.cpp
kis_update_scheduler.cpp
kis_queues_progress_updater.cpp
kis_composite_progress_proxy.cpp
kis_sync_lod_cache_stroke_strategy.cpp
kis_lod_capable_layer_offset.cpp
kis_update_time_monitor.cpp
KisImageConfigNotifier.cpp
kis_group_layer.cc
kis_count_visitor.cpp
kis_histogram.cc
kis_image_interfaces.cpp
kis_image_animation_interface.cpp
kis_time_range.cpp
kis_node_graph_listener.cpp
kis_image.cc
kis_image_signal_router.cpp
KisImageSignals.cpp
kis_image_config.cpp
kis_projection_updates_filter.cpp
kis_suspend_projection_updates_stroke_strategy.cpp
kis_regenerate_frame_stroke_strategy.cpp
kis_switch_time_stroke_strategy.cpp
kis_crop_saved_extra_data.cpp
kis_timed_signal_threshold.cpp
kis_layer.cc
kis_indirect_painting_support.cpp
kis_abstract_projection_plane.cpp
kis_layer_projection_plane.cpp
kis_layer_utils.cpp
kis_mask_projection_plane.cpp
kis_projection_leaf.cpp
KisSafeNodeProjectionStore.cpp
kis_mask.cc
kis_base_mask_generator.cpp
kis_rect_mask_generator.cpp
kis_circle_mask_generator.cpp
kis_gauss_circle_mask_generator.cpp
kis_gauss_rect_mask_generator.cpp
${__per_arch_circle_mask_generator_objs}
kis_curve_circle_mask_generator.cpp
kis_curve_rect_mask_generator.cpp
kis_math_toolbox.cpp
kis_memory_statistics_server.cpp
kis_name_server.cpp
kis_node.cpp
kis_node_facade.cpp
kis_node_progress_proxy.cpp
kis_busy_progress_indicator.cpp
kis_node_visitor.cpp
kis_paint_device.cc
kis_paint_device_debug_utils.cpp
kis_fixed_paint_device.cpp
KisOptimizedByteArray.cpp
kis_paint_layer.cc
kis_perspective_math.cpp
kis_pixel_selection.cpp
kis_processing_information.cpp
kis_properties_configuration.cc
kis_random_accessor_ng.cpp
kis_random_generator.cc
kis_random_sub_accessor.cpp
kis_wrapped_random_accessor.cpp
kis_selection.cc
KisSelectionUpdateCompressor.cpp
kis_selection_mask.cpp
kis_update_outline_job.cpp
kis_update_selection_job.cpp
kis_serializable_configuration.cc
kis_transaction_data.cpp
kis_transform_worker.cc
kis_perspectivetransform_worker.cpp
bsplines/kis_bspline_1d.cpp
bsplines/kis_bspline_2d.cpp
bsplines/kis_nu_bspline_2d.cpp
kis_warptransform_worker.cc
kis_cage_transform_worker.cpp
kis_liquify_transform_worker.cpp
kis_green_coordinates_math.cpp
kis_transparency_mask.cc
kis_undo_adapter.cpp
kis_macro_based_undo_store.cpp
kis_surrogate_undo_adapter.cpp
kis_legacy_undo_adapter.cpp
kis_post_execution_undo_adapter.cpp
kis_processing_visitor.cpp
kis_processing_applicator.cpp
krita_utils.cpp
kis_outline_generator.cpp
kis_layer_composition.cpp
kis_selection_filters.cpp
KisProofingConfiguration.h
KisRecycleProjectionsJob.cpp
kis_keyframe.cpp
kis_keyframe_channel.cpp
kis_keyframe_commands.cpp
kis_scalar_keyframe_channel.cpp
kis_raster_keyframe_channel.cpp
kis_onion_skin_compositor.cpp
kis_onion_skin_cache.cpp
kis_idle_watcher.cpp
kis_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
kis_node_query_path.cc
)
set(einspline_SRCS
3rdparty/einspline/bspline_create.cpp
3rdparty/einspline/bspline_data.cpp
3rdparty/einspline/multi_bspline_create.cpp
3rdparty/einspline/nubasis.cpp
3rdparty/einspline/nubspline_create.cpp
3rdparty/einspline/nugrid.cpp
)
add_library(kritaimage SHARED ${kritaimage_LIB_SRCS} ${einspline_SRCS})
generate_export_header(kritaimage BASE_NAME kritaimage)
target_link_libraries(kritaimage
PUBLIC
kritaversion
kritawidgets
kritaglobal
kritapsd
kritaodf
kritapigment
kritacommand
kritawidgetutils
kritametadata
Qt5::Concurrent
)
target_link_libraries(kritaimage PUBLIC ${Boost_SYSTEM_LIBRARY})
if(NOT HAVE_CXX_ATOMICS_WITHOUT_LIB)
if(NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB)
target_link_libraries(kritaimage PUBLIC atomic)
endif()
endif()
if(OPENEXR_FOUND)
target_link_libraries(kritaimage PUBLIC ${OPENEXR_LIBRARIES})
endif()
if(FFTW3_FOUND)
target_link_libraries(kritaimage PRIVATE ${FFTW3_LIBRARIES})
endif()
if(HAVE_VC)
target_link_libraries(kritaimage PUBLIC ${Vc_LIBRARIES})
endif()
if (NOT GSL_FOUND)
message (WARNING "KRITA WARNING! No GNU Scientific Library was found! Krita's Shaped Gradients might be non-normalized! Please install GSL library.")
else ()
target_link_libraries(kritaimage PRIVATE ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES})
endif ()
target_include_directories(kritaimage
PUBLIC
$
$
$
$
$
)
set_target_properties(kritaimage PROPERTIES
VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
)
install(TARGETS kritaimage ${INSTALL_TARGETS_DEFAULT_ARGS})
diff --git a/libs/image/commands_new/KisHoldUIUpdatesCommand.cpp b/libs/image/commands_new/KisHoldUIUpdatesCommand.cpp
new file mode 100644
index 0000000000..6964e75a18
--- /dev/null
+++ b/libs/image/commands_new/KisHoldUIUpdatesCommand.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2019 Dmitry Kazakov
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "KisHoldUIUpdatesCommand.h"
+
+#include
+#include "kis_image_interfaces.h"
+#include "krita_utils.h"
+#include "kis_paintop_utils.h"
+#include "kis_image_signal_router.h"
+#include "KisRunnableStrokeJobData.h"
+#include "KisRunnableStrokeJobUtils.h"
+#include "KisRunnableStrokeJobsInterface.h"
+
+
+KisHoldUIUpdatesCommand::KisHoldUIUpdatesCommand(KisUpdatesFacade *updatesFacade, State state)
+ : KisCommandUtils::FlipFlopCommand(state),
+ m_updatesFacade(updatesFacade),
+ m_batchUpdateStarted(new bool(false))
+{
+}
+
+void KisHoldUIUpdatesCommand::partA()
+{
+ if (*m_batchUpdateStarted) {
+ m_updatesFacade->notifyBatchUpdateEnded();
+ *m_batchUpdateStarted = false;
+ }
+
+ m_updatesFacade->disableUIUpdates();
+}
+
+void KisHoldUIUpdatesCommand::partB()
+{
+ QVector totalDirtyRects = m_updatesFacade->enableUIUpdates();
+
+ const QRect totalRect =
+ m_updatesFacade->bounds() &
+ std::accumulate(totalDirtyRects.begin(), totalDirtyRects.end(), QRect(), std::bit_or());
+
+ totalDirtyRects =
+ KisPaintOpUtils::splitAndFilterDabRect(totalRect,
+ totalDirtyRects,
+ KritaUtils::optimalPatchSize().width());
+
+ *m_batchUpdateStarted = true;
+ m_updatesFacade->notifyBatchUpdateStarted();
+
+ KisUpdatesFacade *updatesFacade = m_updatesFacade;
+ QSharedPointer batchUpdateStarted = m_batchUpdateStarted;
+
+ QVector jobsData;
+ Q_FOREACH (const QRect &rc, totalDirtyRects) {
+ KritaUtils::addJobConcurrent(jobsData, [updatesFacade, rc] () {
+ updatesFacade->notifyUIUpdateCompleted(rc);
+ });
+ }
+
+ KritaUtils::addJobBarrier(jobsData, [updatesFacade, batchUpdateStarted] () {
+ updatesFacade->notifyBatchUpdateEnded();
+ *batchUpdateStarted = false;
+ });
+
+ runnableJobsInterface()->addRunnableJobs(jobsData);
+}
diff --git a/libs/command/kundo2commandextradata.h b/libs/image/commands_new/KisHoldUIUpdatesCommand.h
similarity index 51%
copy from libs/command/kundo2commandextradata.h
copy to libs/image/commands_new/KisHoldUIUpdatesCommand.h
index 3524d763ad..7f3bec9a43 100644
--- a/libs/command/kundo2commandextradata.h
+++ b/libs/image/commands_new/KisHoldUIUpdatesCommand.h
@@ -1,31 +1,43 @@
/*
- * Copyright (c) 2015 Dmitry Kazakov
+ * Copyright (c) 2019 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#ifndef __KUNDO2COMMANDEXTRADATA_H
-#define __KUNDO2COMMANDEXTRADATA_H
+#ifndef KISHOLDUIUPDATESCOMMAND_H
+#define KISHOLDUIUPDATESCOMMAND_H
-#include "kritacommand_export.h"
+#include "kis_stroke_strategy_undo_command_based.h"
+#include "kis_node.h"
+#include "kis_command_utils.h"
+#include
+class KisUpdatesFacade;
-class KRITACOMMAND_EXPORT KUndo2CommandExtraData
+
+class KRITAIMAGE_EXPORT KisHoldUIUpdatesCommand : public KisCommandUtils::FlipFlopCommand, public KisStrokeStrategyUndoCommandBased::MutatedCommandInterface
{
public:
- virtual ~KUndo2CommandExtraData();
+ KisHoldUIUpdatesCommand(KisUpdatesFacade *updatesFacade, State state);
+
+ void partA() override;
+ void partB() override;
+
+private:
+ KisUpdatesFacade *m_updatesFacade;
+ QSharedPointer m_batchUpdateStarted;
};
-#endif /* __KUNDO2COMMANDEXTRADATA_H */
+#endif // KISHOLDUIUPDATESCOMMAND_H
diff --git a/libs/image/commands_new/kis_saved_commands.cpp b/libs/image/commands_new/kis_saved_commands.cpp
index 97acc31fd0..ddec094bd9 100644
--- a/libs/image/commands_new/kis_saved_commands.cpp
+++ b/libs/image/commands_new/kis_saved_commands.cpp
@@ -1,275 +1,305 @@
/*
* Copyright (c) 2011 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_saved_commands.h"
#include
#include "kis_image_interfaces.h"
#include "kis_stroke_strategy_undo_command_based.h"
KisSavedCommandBase::KisSavedCommandBase(const KUndo2MagicString &name,
KisStrokesFacade *strokesFacade)
: KUndo2Command(name),
m_strokesFacade(strokesFacade),
m_skipOneRedo(true)
{
}
KisSavedCommandBase::~KisSavedCommandBase()
{
}
KisStrokesFacade* KisSavedCommandBase::strokesFacade()
{
return m_strokesFacade;
}
void KisSavedCommandBase::runStroke(bool undo)
{
KisStrokeStrategyUndoCommandBased *strategy =
new KisStrokeStrategyUndoCommandBased(text(), undo, 0);
strategy->setUsedWhileUndoRedo(true);
KisStrokeId id = m_strokesFacade->startStroke(strategy);
addCommands(id, undo);
m_strokesFacade->endStroke(id);
}
void KisSavedCommandBase::undo()
{
runStroke(true);
}
void KisSavedCommandBase::redo()
{
/**
* All the commands are first executed in the stroke and then
* added to the undo stack. It means that the first redo should be
* skipped
*/
if(m_skipOneRedo) {
m_skipOneRedo = false;
return;
}
runStroke(false);
}
KisSavedCommand::KisSavedCommand(KUndo2CommandSP command,
KisStrokesFacade *strokesFacade)
: KisSavedCommandBase(command->text(), strokesFacade),
m_command(command)
{
}
int KisSavedCommand::id() const
{
return m_command->id();
}
bool KisSavedCommand::mergeWith(const KUndo2Command* command)
{
const KisSavedCommand *other =
dynamic_cast(command);
if (other) {
command = other->m_command.data();
}
return m_command->mergeWith(command);
}
void KisSavedCommand::addCommands(KisStrokeId id, bool undo)
{
strokesFacade()->
addJob(id, new KisStrokeStrategyUndoCommandBased::Data(m_command, undo));
}
int KisSavedCommand::timedId()
{
return m_command->timedId();
}
void KisSavedCommand::setTimedID(int timedID)
{
m_command->setTimedID(timedID);
}
bool KisSavedCommand::timedMergeWith(KUndo2Command *other)
{
return m_command->timedMergeWith(other);
}
QVector KisSavedCommand::mergeCommandsVector()
{
return m_command->mergeCommandsVector();
}
void KisSavedCommand::setTime()
{
m_command->setTime();
}
QTime KisSavedCommand::time()
{
return m_command->time();
}
void KisSavedCommand::setEndTime()
{
m_command->setEndTime();
}
QTime KisSavedCommand::endTime()
{
return m_command->endTime();
}
bool KisSavedCommand::isMerged()
{
return m_command->isMerged();
}
struct KisSavedMacroCommand::Private
{
struct SavedCommand {
KUndo2CommandSP command;
KisStrokeJobData::Sequentiality sequentiality;
KisStrokeJobData::Exclusivity exclusivity;
};
QVector commands;
int macroId = -1;
+
+ const KisSavedMacroCommand *overriddenCommand = 0;
+ QVector skipWhenOverride;
};
KisSavedMacroCommand::KisSavedMacroCommand(const KUndo2MagicString &name,
KisStrokesFacade *strokesFacade)
: KisSavedCommandBase(name, strokesFacade),
m_d(new Private())
{
}
KisSavedMacroCommand::~KisSavedMacroCommand()
{
delete m_d;
}
void KisSavedMacroCommand::setMacroId(int value)
{
m_d->macroId = value;
}
int KisSavedMacroCommand::id() const
{
return m_d->macroId;
}
bool KisSavedMacroCommand::mergeWith(const KUndo2Command* command)
{
const KisSavedMacroCommand *other =
dynamic_cast(command);
if (!other || other->id() != id()) return false;
QVector &otherCommands = other->m_d->commands;
+ if (other->m_d->overriddenCommand == this) {
+ m_d->commands.clear();
+
+ Q_FOREACH (Private::SavedCommand cmd, other->m_d->commands) {
+ if (!other->m_d->skipWhenOverride.contains(cmd.command.data())) {
+ m_d->commands.append(cmd);
+ }
+ }
+
+ setExtraData(other->extraData()->clone());
+ return true;
+ }
+
if (m_d->commands.size() != otherCommands.size()) return false;
auto it = m_d->commands.constBegin();
auto end = m_d->commands.constEnd();
auto otherIt = otherCommands.constBegin();
auto otherEnd = otherCommands.constEnd();
bool sameCommands = true;
while (it != end && otherIt != otherEnd) {
if (it->command->id() != otherIt->command->id() ||
it->sequentiality != otherIt->sequentiality ||
it->exclusivity != otherIt->exclusivity) {
sameCommands = false;
break;
}
++it;
++otherIt;
}
if (!sameCommands) return false;
it = m_d->commands.constBegin();
otherIt = otherCommands.constBegin();
while (it != end && otherIt != otherEnd) {
if (it->command->id() != -1) {
bool result = it->command->mergeWith(otherIt->command.data());
KIS_ASSERT_RECOVER(result) { return false; }
}
++it;
++otherIt;
}
return true;
}
void KisSavedMacroCommand::addCommand(KUndo2CommandSP command,
KisStrokeJobData::Sequentiality sequentiality,
KisStrokeJobData::Exclusivity exclusivity)
{
Private::SavedCommand item;
item.command = command;
item.sequentiality = sequentiality;
item.exclusivity = exclusivity;
m_d->commands.append(item);
}
void KisSavedMacroCommand::performCancel(KisStrokeId id, bool strokeUndo)
{
addCommands(id, !strokeUndo);
}
-void KisSavedMacroCommand::addCommands(KisStrokeId id, bool undo)
+void KisSavedMacroCommand::getCommandExecutionJobs(QVector *jobs, bool undo) const
{
QVector::iterator it;
if(!undo) {
for(it = m_d->commands.begin(); it != m_d->commands.end(); it++) {
- strokesFacade()->
- addJob(id, new KisStrokeStrategyUndoCommandBased::
+ *jobs << new KisStrokeStrategyUndoCommandBased::
Data(it->command,
undo,
it->sequentiality,
- it->exclusivity));
+ it->exclusivity);
}
}
else {
for(it = m_d->commands.end(); it != m_d->commands.begin();) {
--it;
- strokesFacade()->
- addJob(id, new KisStrokeStrategyUndoCommandBased::
- Data(it->command,
- undo,
- it->sequentiality,
- it->exclusivity));
+ *jobs << new KisStrokeStrategyUndoCommandBased::
+ Data(it->command,
+ undo,
+ it->sequentiality,
+ it->exclusivity);
}
}
}
+
+void KisSavedMacroCommand::setOverrideInfo(const KisSavedMacroCommand *overriddenCommand, const QVector &skipWhileOverride)
+{
+ m_d->overriddenCommand = overriddenCommand;
+ m_d->skipWhenOverride = skipWhileOverride;
+}
+
+void KisSavedMacroCommand::addCommands(KisStrokeId id, bool undo)
+{
+ QVector jobs;
+ getCommandExecutionJobs(&jobs, undo);
+
+ Q_FOREACH (KisStrokeJobData *job, jobs) {
+ strokesFacade()->addJob(id, job);
+ }
+}
diff --git a/libs/image/commands_new/kis_saved_commands.h b/libs/image/commands_new/kis_saved_commands.h
index f15e7ee290..d1b805c847 100644
--- a/libs/image/commands_new/kis_saved_commands.h
+++ b/libs/image/commands_new/kis_saved_commands.h
@@ -1,101 +1,105 @@
/*
* Copyright (c) 2011 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_SAVED_COMMANDS_H
#define __KIS_SAVED_COMMANDS_H
#include
#include "kis_types.h"
#include "kis_stroke_job_strategy.h"
class KisStrokesFacade;
class KRITAIMAGE_EXPORT KisSavedCommandBase : public KUndo2Command
{
public:
KisSavedCommandBase(const KUndo2MagicString &name, KisStrokesFacade *strokesFacade);
~KisSavedCommandBase() override;
void undo() override;
void redo() override;
protected:
virtual void addCommands(KisStrokeId id, bool undo) = 0;
KisStrokesFacade* strokesFacade();
private:
void runStroke(bool undo);
private:
KisStrokesFacade *m_strokesFacade;
bool m_skipOneRedo;
};
class KRITAIMAGE_EXPORT KisSavedCommand : public KisSavedCommandBase
{
public:
KisSavedCommand(KUndo2CommandSP command, KisStrokesFacade *strokesFacade);
int timedId() override;
void setTimedID(int timedID) override;
int id() const override;
bool mergeWith(const KUndo2Command* command) override;
bool timedMergeWith(KUndo2Command *other) override;
QVector mergeCommandsVector() override;
void setTime() override;
QTime time() override;
void setEndTime() override;
QTime endTime() override;
bool isMerged() override;
protected:
void addCommands(KisStrokeId id, bool undo) override;
private:
KUndo2CommandSP m_command;
};
class KRITAIMAGE_EXPORT KisSavedMacroCommand : public KisSavedCommandBase
{
public:
KisSavedMacroCommand(const KUndo2MagicString &name, KisStrokesFacade *strokesFacade);
~KisSavedMacroCommand() override;
int id() const override;
bool mergeWith(const KUndo2Command* command) override;
void setMacroId(int value);
void addCommand(KUndo2CommandSP command,
KisStrokeJobData::Sequentiality sequentiality = KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::Exclusivity exclusivity = KisStrokeJobData::NORMAL);
void performCancel(KisStrokeId id, bool strokeUndo);
+ void getCommandExecutionJobs(QVector *jobs, bool undo) const;
+
+ void setOverrideInfo(const KisSavedMacroCommand *overriddenCommand, const QVector &skipWhileOverride);
+
protected:
void addCommands(KisStrokeId id, bool undo) override;
private:
struct Private;
Private * const m_d;
};
#endif /* __KIS_SAVED_COMMANDS_H */
diff --git a/libs/image/kis_crop_saved_extra_data.h b/libs/image/kis_crop_saved_extra_data.h
index 4647023525..41381c4550 100644
--- a/libs/image/kis_crop_saved_extra_data.h
+++ b/libs/image/kis_crop_saved_extra_data.h
@@ -1,61 +1,65 @@
/*
* Copyright (c) 2015 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_CROP_SAVED_EXTRA_DATA_H
#define __KIS_CROP_SAVED_EXTRA_DATA_H
#include
#include "kundo2commandextradata.h"
#include "kis_types.h"
#include "kritaimage_export.h"
class KRITAIMAGE_EXPORT KisCropSavedExtraData : public KUndo2CommandExtraData
{
public:
enum Type {
CROP_IMAGE,
RESIZE_IMAGE,
CROP_LAYER
};
public:
KisCropSavedExtraData(Type type, QRect cropRect, KisNodeSP cropNode = 0);
~KisCropSavedExtraData() override;
inline Type type() const {
return m_type;
}
inline QRect cropRect() const {
return m_cropRect;
}
inline KisNodeSP cropNode() const {
return m_cropNode;
}
+ KUndo2CommandExtraData* clone() const override {
+ return new KisCropSavedExtraData(*this);
+ }
+
private:
Type m_type;
QRect m_cropRect;
KisNodeSP m_cropNode;
};
#endif /* __KIS_CROP_SAVED_EXTRA_DATA_H */
diff --git a/libs/image/kis_image.cc b/libs/image/kis_image.cc
index 4d68fc5172..93e204ffe4 100644
--- a/libs/image/kis_image.cc
+++ b/libs/image/kis_image.cc
@@ -1,2029 +1,2049 @@
/*
* Copyright (c) 2002 Patrick Julien
* Copyright (c) 2007 Boudewijn Rempt
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_image.h"
#include // WORDS_BIGENDIAN
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "KoColorSpaceRegistry.h"
#include "KoColor.h"
#include "KoColorProfile.h"
#include
#include "KisProofingConfiguration.h"
#include "kis_adjustment_layer.h"
#include "kis_annotation.h"
#include "kis_change_profile_visitor.h"
#include "kis_colorspace_convert_visitor.h"
#include "kis_count_visitor.h"
#include "kis_filter_strategy.h"
#include "kis_group_layer.h"
#include "commands/kis_image_commands.h"
#include "kis_layer.h"
#include "kis_meta_data_merge_strategy_registry.h"
#include "kis_name_server.h"
#include "kis_paint_layer.h"
#include "kis_projection_leaf.h"
#include "kis_painter.h"
#include "kis_selection.h"
#include "kis_transaction.h"
#include "kis_meta_data_merge_strategy.h"
#include "kis_memory_statistics_server.h"
#include "kis_image_config.h"
#include "kis_update_scheduler.h"
#include "kis_image_signal_router.h"
#include "kis_image_animation_interface.h"
#include "kis_stroke_strategy.h"
#include "kis_simple_stroke_strategy.h"
#include "kis_image_barrier_locker.h"
#include "kis_undo_stores.h"
#include "kis_legacy_undo_adapter.h"
#include "kis_post_execution_undo_adapter.h"
#include "kis_transform_worker.h"
#include "kis_processing_applicator.h"
#include "processing/kis_crop_processing_visitor.h"
#include "processing/kis_crop_selections_processing_visitor.h"
#include "processing/kis_transform_processing_visitor.h"
#include "commands_new/kis_image_resize_command.h"
#include "commands_new/kis_image_set_resolution_command.h"
#include "commands_new/kis_activate_selection_mask_command.h"
#include "kis_composite_progress_proxy.h"
#include "kis_layer_composition.h"
#include "kis_wrapped_rect.h"
#include "kis_crop_saved_extra_data.h"
#include "kis_layer_utils.h"
#include "kis_lod_transform.h"
#include "kis_suspend_projection_updates_stroke_strategy.h"
#include "kis_sync_lod_cache_stroke_strategy.h"
#include "kis_projection_updates_filter.h"
#include "kis_layer_projection_plane.h"
#include "kis_update_time_monitor.h"
#include "tiles3/kis_lockless_stack.h"
#include
#include
#include "kis_time_range.h"
// #define SANITY_CHECKS
#ifdef SANITY_CHECKS
#define SANITY_CHECK_LOCKED(name) \
if (!locked()) warnKrita() << "Locking policy failed:" << name \
<< "has been called without the image" \
"being locked";
#else
#define SANITY_CHECK_LOCKED(name)
#endif
struct KisImageSPStaticRegistrar {
KisImageSPStaticRegistrar() {
qRegisterMetaType("KisImageSP");
}
};
static KisImageSPStaticRegistrar __registrar;
class KisImage::KisImagePrivate
{
public:
KisImagePrivate(KisImage *_q, qint32 w, qint32 h,
const KoColorSpace *c,
KisUndoStore *undo,
KisImageAnimationInterface *_animationInterface)
: q(_q)
, lockedForReadOnly(false)
, width(w)
, height(h)
, colorSpace(c ? c : KoColorSpaceRegistry::instance()->rgb8())
, nserver(1)
, undoStore(undo ? undo : new KisDumbUndoStore())
, legacyUndoAdapter(undoStore.data(), _q)
, postExecutionUndoAdapter(undoStore.data(), _q)
, signalRouter(_q)
, animationInterface(_animationInterface)
, scheduler(_q, _q)
, axesCenter(QPointF(0.5, 0.5))
{
{
KisImageConfig cfg(true);
if (cfg.enableProgressReporting()) {
scheduler.setProgressProxy(&compositeProgressProxy);
}
// Each of these lambdas defines a new factory function.
scheduler.setLod0ToNStrokeStrategyFactory(
[=](bool forgettable) {
return KisLodSyncPair(
new KisSyncLodCacheStrokeStrategy(KisImageWSP(q), forgettable),
KisSyncLodCacheStrokeStrategy::createJobsData(KisImageWSP(q)));
});
scheduler.setSuspendUpdatesStrokeStrategyFactory(
[=]() {
return KisSuspendResumePair(
new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), true),
KisSuspendProjectionUpdatesStrokeStrategy::createSuspendJobsData(KisImageWSP(q)));
});
scheduler.setResumeUpdatesStrokeStrategyFactory(
[=]() {
return KisSuspendResumePair(
new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), false),
KisSuspendProjectionUpdatesStrokeStrategy::createResumeJobsData(KisImageWSP(q)));
});
}
connect(q, SIGNAL(sigImageModified()), KisMemoryStatisticsServer::instance(), SLOT(notifyImageChanged()));
}
~KisImagePrivate() {
/**
* Stop animation interface. It may use the rootLayer.
*/
delete animationInterface;
/**
* First delete the nodes, while strokes
* and undo are still alive
*/
rootLayer.clear();
}
KisImage *q;
quint32 lockCount = 0;
bool lockedForReadOnly;
qint32 width;
qint32 height;
double xres = 1.0;
double yres = 1.0;
const KoColorSpace * colorSpace;
KisProofingConfigurationSP proofingConfig;
KisSelectionSP deselectedGlobalSelection;
KisGroupLayerSP rootLayer; // The layers are contained in here
KisSelectionMaskSP targetOverlaySelectionMask; // the overlay switching stroke will try to switch into this mask
KisSelectionMaskSP overlaySelectionMask;
QList compositions;
KisNodeSP isolatedRootNode;
bool wrapAroundModePermitted = false;
KisNameServer nserver;
QScopedPointer undoStore;
KisLegacyUndoAdapter legacyUndoAdapter;
KisPostExecutionUndoAdapter postExecutionUndoAdapter;
vKisAnnotationSP annotations;
QAtomicInt disableUIUpdateSignals;
KisLocklessStack savedDisabledUIUpdates;
KisProjectionUpdatesFilterSP projectionUpdatesFilter;
KisImageSignalRouter signalRouter;
KisImageAnimationInterface *animationInterface;
KisUpdateScheduler scheduler;
QAtomicInt disableDirtyRequests;
KisCompositeProgressProxy compositeProgressProxy;
bool blockLevelOfDetail = false;
QPointF axesCenter;
bool allowMasksOnRootNode = false;
bool tryCancelCurrentStrokeAsync();
void notifyProjectionUpdatedInPatches(const QRect &rc);
};
KisImage::KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace * colorSpace, const QString& name)
: QObject(0)
, KisShared()
, m_d(new KisImagePrivate(this, width, height,
colorSpace, undoStore,
new KisImageAnimationInterface(this)))
{
// make sure KisImage belongs to the GUI thread
moveToThread(qApp->thread());
connect(this, SIGNAL(sigInternalStopIsolatedModeRequested()), SLOT(stopIsolatedMode()));
setObjectName(name);
setRootLayer(new KisGroupLayer(this, "root", OPACITY_OPAQUE_U8));
}
KisImage::~KisImage()
{
dbgImage << "deleting kisimage" << objectName();
/**
* Request the tools to end currently running strokes
*/
waitForDone();
delete m_d;
disconnect(); // in case Qt gets confused
}
KisImageSP KisImage::fromQImage(const QImage &image, KisUndoStore *undoStore)
{
const KoColorSpace *colorSpace = 0;
switch (image.format()) {
case QImage::Format_Invalid:
case QImage::Format_Mono:
case QImage::Format_MonoLSB:
colorSpace = KoColorSpaceRegistry::instance()->graya8();
break;
case QImage::Format_Indexed8:
case QImage::Format_RGB32:
case QImage::Format_ARGB32:
case QImage::Format_ARGB32_Premultiplied:
colorSpace = KoColorSpaceRegistry::instance()->rgb8();
break;
case QImage::Format_RGB16:
colorSpace = KoColorSpaceRegistry::instance()->rgb16();
break;
case QImage::Format_ARGB8565_Premultiplied:
case QImage::Format_RGB666:
case QImage::Format_ARGB6666_Premultiplied:
case QImage::Format_RGB555:
case QImage::Format_ARGB8555_Premultiplied:
case QImage::Format_RGB888:
case QImage::Format_RGB444:
case QImage::Format_ARGB4444_Premultiplied:
case QImage::Format_RGBX8888:
case QImage::Format_RGBA8888:
case QImage::Format_RGBA8888_Premultiplied:
colorSpace = KoColorSpaceRegistry::instance()->rgb8();
break;
case QImage::Format_BGR30:
case QImage::Format_A2BGR30_Premultiplied:
case QImage::Format_RGB30:
case QImage::Format_A2RGB30_Premultiplied:
colorSpace = KoColorSpaceRegistry::instance()->rgb8();
break;
case QImage::Format_Alpha8:
colorSpace = KoColorSpaceRegistry::instance()->alpha8();
break;
case QImage::Format_Grayscale8:
colorSpace = KoColorSpaceRegistry::instance()->graya8();
break;
#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
case QImage::Format_Grayscale16:
colorSpace = KoColorSpaceRegistry::instance()->graya16();
break;
#endif
#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)
case QImage::Format_RGBX64:
case QImage::Format_RGBA64:
case QImage::Format_RGBA64_Premultiplied:
colorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), 0);
break;
#endif
default:
colorSpace = 0;
}
KisImageSP img = new KisImage(undoStore, image.width(), image.height(), colorSpace, i18n("Imported Image"));
KisPaintLayerSP layer = new KisPaintLayer(img, img->nextLayerName(), 255);
layer->paintDevice()->convertFromQImage(image, 0, 0, 0);
img->addNode(layer.data(), img->rootLayer().data());
return img;
}
KisImage *KisImage::clone(bool exactCopy)
{
return new KisImage(*this, 0, exactCopy);
}
void KisImage::copyFromImage(const KisImage &rhs)
{
copyFromImageImpl(rhs, REPLACE);
}
void KisImage::copyFromImageImpl(const KisImage &rhs, int policy)
{
// make sure we choose exactly one from REPLACE and CONSTRUCT
KIS_ASSERT_RECOVER_RETURN((policy & REPLACE) != (policy & CONSTRUCT));
// only when replacing do we need to emit signals
#define EMIT_IF_NEEDED if (!(policy & REPLACE)) {} else emit
if (policy & REPLACE) { // if we are constructing the image, these are already set
if (m_d->width != rhs.width() || m_d->height != rhs.height()) {
m_d->width = rhs.width();
m_d->height = rhs.height();
emit sigSizeChanged(QPointF(), QPointF());
}
if (m_d->colorSpace != rhs.colorSpace()) {
m_d->colorSpace = rhs.colorSpace();
emit sigColorSpaceChanged(m_d->colorSpace);
}
}
// from KisImage::KisImage(const KisImage &, KisUndoStore *, bool)
setObjectName(rhs.objectName());
if (m_d->xres != rhs.m_d->xres || m_d->yres != rhs.m_d->yres) {
m_d->xres = rhs.m_d->xres;
m_d->yres = rhs.m_d->yres;
EMIT_IF_NEEDED sigResolutionChanged(m_d->xres, m_d->yres);
}
m_d->allowMasksOnRootNode = rhs.m_d->allowMasksOnRootNode;
if (rhs.m_d->proofingConfig) {
KisProofingConfigurationSP proofingConfig(new KisProofingConfiguration(*rhs.m_d->proofingConfig));
if (policy & REPLACE) {
setProofingConfiguration(proofingConfig);
} else {
m_d->proofingConfig = proofingConfig;
}
}
KisNodeSP newRoot = rhs.root()->clone();
newRoot->setGraphListener(this);
newRoot->setImage(this);
m_d->rootLayer = dynamic_cast(newRoot.data());
setRoot(newRoot);
bool exactCopy = policy & EXACT_COPY;
if (exactCopy || rhs.m_d->isolatedRootNode) {
QQueue linearizedNodes;
KisLayerUtils::recursiveApplyNodes(rhs.root(),
[&linearizedNodes](KisNodeSP node) {
linearizedNodes.enqueue(node);
});
KisLayerUtils::recursiveApplyNodes(newRoot,
[&linearizedNodes, exactCopy, &rhs, this](KisNodeSP node) {
KisNodeSP refNode = linearizedNodes.dequeue();
if (exactCopy) {
node->setUuid(refNode->uuid());
}
if (rhs.m_d->isolatedRootNode &&
rhs.m_d->isolatedRootNode == refNode) {
m_d->isolatedRootNode = node;
}
});
}
KisLayerUtils::recursiveApplyNodes(newRoot,
[](KisNodeSP node) {
dbgImage << "Node: " << (void *)node.data();
});
m_d->compositions.clear();
Q_FOREACH (KisLayerCompositionSP comp, rhs.m_d->compositions) {
m_d->compositions << toQShared(new KisLayerComposition(*comp, this));
}
EMIT_IF_NEEDED sigLayersChangedAsync();
m_d->nserver = rhs.m_d->nserver;
vKisAnnotationSP newAnnotations;
Q_FOREACH (KisAnnotationSP annotation, rhs.m_d->annotations) {
newAnnotations << annotation->clone();
}
m_d->annotations = newAnnotations;
KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->projectionUpdatesFilter);
KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableUIUpdateSignals);
KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableDirtyRequests);
m_d->blockLevelOfDetail = rhs.m_d->blockLevelOfDetail;
/**
* The overlay device is not inherited when cloning the image!
*/
if (rhs.m_d->overlaySelectionMask) {
const QRect dirtyRect = rhs.m_d->overlaySelectionMask->extent();
m_d->rootLayer->setDirty(dirtyRect);
}
#undef EMIT_IF_NEEDED
}
KisImage::KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy)
: KisNodeFacade(),
KisNodeGraphListener(),
KisShared(),
m_d(new KisImagePrivate(this,
rhs.width(), rhs.height(),
rhs.colorSpace(),
undoStore ? undoStore : new KisDumbUndoStore(),
new KisImageAnimationInterface(*rhs.animationInterface(), this)))
{
// make sure KisImage belongs to the GUI thread
moveToThread(qApp->thread());
connect(this, SIGNAL(sigInternalStopIsolatedModeRequested()), SLOT(stopIsolatedMode()));
copyFromImageImpl(rhs, CONSTRUCT | (exactCopy ? EXACT_COPY : 0));
}
void KisImage::aboutToAddANode(KisNode *parent, int index)
{
KisNodeGraphListener::aboutToAddANode(parent, index);
SANITY_CHECK_LOCKED("aboutToAddANode");
}
void KisImage::nodeHasBeenAdded(KisNode *parent, int index)
{
KisNodeGraphListener::nodeHasBeenAdded(parent, index);
SANITY_CHECK_LOCKED("nodeHasBeenAdded");
m_d->signalRouter.emitNodeHasBeenAdded(parent, index);
}
void KisImage::aboutToRemoveANode(KisNode *parent, int index)
{
KisNodeSP deletedNode = parent->at(index);
if (!dynamic_cast(deletedNode.data()) &&
deletedNode == m_d->isolatedRootNode) {
emit sigInternalStopIsolatedModeRequested();
}
KisNodeGraphListener::aboutToRemoveANode(parent, index);
SANITY_CHECK_LOCKED("aboutToRemoveANode");
m_d->signalRouter.emitAboutToRemoveANode(parent, index);
}
void KisImage::nodeChanged(KisNode* node)
{
KisNodeGraphListener::nodeChanged(node);
requestStrokeEnd();
m_d->signalRouter.emitNodeChanged(node);
}
void KisImage::invalidateAllFrames()
{
invalidateFrames(KisTimeRange::infinite(0), QRect());
}
void KisImage::setOverlaySelectionMask(KisSelectionMaskSP mask)
{
if (m_d->targetOverlaySelectionMask == mask) return;
m_d->targetOverlaySelectionMask = mask;
struct UpdateOverlaySelectionStroke : public KisSimpleStrokeStrategy {
UpdateOverlaySelectionStroke(KisImageSP image)
: KisSimpleStrokeStrategy("update-overlay-selection-mask", kundo2_noi18n("update-overlay-selection-mask")),
m_image(image)
{
this->enableJob(JOB_INIT, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE);
setClearsRedoOnStart(false);
}
void initStrokeCallback() {
KisSelectionMaskSP oldMask = m_image->m_d->overlaySelectionMask;
KisSelectionMaskSP newMask = m_image->m_d->targetOverlaySelectionMask;
if (oldMask == newMask) return;
KIS_SAFE_ASSERT_RECOVER_RETURN(!newMask || newMask->graphListener() == m_image);
m_image->m_d->overlaySelectionMask = newMask;
if (oldMask || newMask) {
m_image->m_d->rootLayer->notifyChildMaskChanged();
}
if (oldMask) {
m_image->m_d->rootLayer->setDirtyDontResetAnimationCache(oldMask->extent());
}
if (newMask) {
newMask->setDirty();
}
m_image->undoAdapter()->emitSelectionChanged();
}
private:
KisImageSP m_image;
};
KisStrokeId id = startStroke(new UpdateOverlaySelectionStroke(this));
endStroke(id);
}
KisSelectionMaskSP KisImage::overlaySelectionMask() const
{
return m_d->overlaySelectionMask;
}
bool KisImage::hasOverlaySelectionMask() const
{
return m_d->overlaySelectionMask;
}
KisSelectionSP KisImage::globalSelection() const
{
KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask();
if (selectionMask) {
return selectionMask->selection();
} else {
return 0;
}
}
void KisImage::setGlobalSelection(KisSelectionSP globalSelection)
{
KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask();
if (!globalSelection) {
if (selectionMask) {
removeNode(selectionMask);
}
}
else {
if (!selectionMask) {
selectionMask = new KisSelectionMask(this);
selectionMask->initSelection(m_d->rootLayer);
addNode(selectionMask);
// If we do not set the selection now, the setActive call coming next
// can be very, very expensive, depending on the size of the image.
selectionMask->setSelection(globalSelection);
selectionMask->setActive(true);
}
else {
selectionMask->setSelection(globalSelection);
}
KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->rootLayer->childCount() > 0);
KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->rootLayer->selectionMask());
}
m_d->deselectedGlobalSelection = 0;
m_d->legacyUndoAdapter.emitSelectionChanged();
}
void KisImage::deselectGlobalSelection()
{
KisSelectionSP savedSelection = globalSelection();
setGlobalSelection(0);
m_d->deselectedGlobalSelection = savedSelection;
}
bool KisImage::canReselectGlobalSelection()
{
return m_d->deselectedGlobalSelection;
}
void KisImage::reselectGlobalSelection()
{
if(m_d->deselectedGlobalSelection) {
setGlobalSelection(m_d->deselectedGlobalSelection);
}
}
QString KisImage::nextLayerName(const QString &_baseName) const
{
QString baseName = _baseName;
if (m_d->nserver.currentSeed() == 0) {
m_d->nserver.number();
return i18n("background");
}
if (baseName.isEmpty()) {
baseName = i18n("Layer");
}
return QString("%1 %2").arg(baseName).arg(m_d->nserver.number());
}
void KisImage::rollBackLayerName()
{
m_d->nserver.rollback();
}
KisCompositeProgressProxy* KisImage::compositeProgressProxy()
{
return &m_d->compositeProgressProxy;
}
bool KisImage::locked() const
{
return m_d->lockCount != 0;
}
void KisImage::barrierLock(bool readOnly)
{
if (!locked()) {
requestStrokeEnd();
m_d->scheduler.barrierLock();
m_d->lockedForReadOnly = readOnly;
} else {
m_d->lockedForReadOnly &= readOnly;
}
m_d->lockCount++;
}
bool KisImage::tryBarrierLock(bool readOnly)
{
bool result = true;
if (!locked()) {
result = m_d->scheduler.tryBarrierLock();
m_d->lockedForReadOnly = readOnly;
}
if (result) {
m_d->lockCount++;
m_d->lockedForReadOnly &= readOnly;
}
return result;
}
bool KisImage::isIdle(bool allowLocked)
{
return (allowLocked || !locked()) && m_d->scheduler.isIdle();
}
void KisImage::lock()
{
if (!locked()) {
requestStrokeEnd();
m_d->scheduler.lock();
}
m_d->lockCount++;
m_d->lockedForReadOnly = false;
}
void KisImage::unlock()
{
Q_ASSERT(locked());
if (locked()) {
m_d->lockCount--;
if (m_d->lockCount == 0) {
m_d->scheduler.unlock(!m_d->lockedForReadOnly);
}
}
}
void KisImage::blockUpdates()
{
m_d->scheduler.blockUpdates();
}
void KisImage::unblockUpdates()
{
m_d->scheduler.unblockUpdates();
}
void KisImage::setSize(const QSize& size)
{
m_d->width = size.width();
m_d->height = size.height();
}
void KisImage::resizeImageImpl(const QRect& newRect, bool cropLayers)
{
if (newRect == bounds() && !cropLayers) return;
KUndo2MagicString actionName = cropLayers ?
kundo2_i18n("Crop Image") :
kundo2_i18n("Resize Image");
KisImageSignalVector emitSignals;
emitSignals << ComplexSizeChangedSignal(newRect, newRect.size());
emitSignals << ModifiedSignal;
KisCropSavedExtraData *extraData =
new KisCropSavedExtraData(cropLayers ?
KisCropSavedExtraData::CROP_IMAGE :
KisCropSavedExtraData::RESIZE_IMAGE,
newRect);
KisProcessingApplicator applicator(this, m_d->rootLayer,
KisProcessingApplicator::RECURSIVE |
KisProcessingApplicator::NO_UI_UPDATES,
emitSignals, actionName, extraData);
if (cropLayers || !newRect.topLeft().isNull()) {
KisProcessingVisitorSP visitor =
new KisCropProcessingVisitor(newRect, cropLayers, true);
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
}
applicator.applyCommand(new KisImageResizeCommand(this, newRect.size()));
applicator.end();
}
void KisImage::resizeImage(const QRect& newRect)
{
resizeImageImpl(newRect, false);
}
void KisImage::cropImage(const QRect& newRect)
{
resizeImageImpl(newRect, true);
}
void KisImage::cropNode(KisNodeSP node, const QRect& newRect)
{
bool isLayer = qobject_cast(node.data());
KUndo2MagicString actionName = isLayer ?
kundo2_i18n("Crop Layer") :
kundo2_i18n("Crop Mask");
KisImageSignalVector emitSignals;
emitSignals << ModifiedSignal;
KisCropSavedExtraData *extraData =
new KisCropSavedExtraData(KisCropSavedExtraData::CROP_LAYER,
newRect, node);
KisProcessingApplicator applicator(this, node,
KisProcessingApplicator::RECURSIVE,
emitSignals, actionName, extraData);
KisProcessingVisitorSP visitor =
new KisCropProcessingVisitor(newRect, true, false);
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
applicator.end();
}
void KisImage::scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy)
{
bool resolutionChanged = xres != xRes() && yres != yRes();
bool sizeChanged = size != this->size();
if (!resolutionChanged && !sizeChanged) return;
KisImageSignalVector emitSignals;
if (resolutionChanged) emitSignals << ResolutionChangedSignal;
if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), size);
emitSignals << ModifiedSignal;
KUndo2MagicString actionName = sizeChanged ?
kundo2_i18n("Scale Image") :
kundo2_i18n("Change Image Resolution");
KisProcessingApplicator::ProcessingFlags signalFlags =
(resolutionChanged || sizeChanged) ?
KisProcessingApplicator::NO_UI_UPDATES :
KisProcessingApplicator::NONE;
KisProcessingApplicator applicator(this, m_d->rootLayer,
KisProcessingApplicator::RECURSIVE | signalFlags,
emitSignals, actionName);
qreal sx = qreal(size.width()) / this->size().width();
qreal sy = qreal(size.height()) / this->size().height();
QTransform shapesCorrection;
if (resolutionChanged) {
shapesCorrection = QTransform::fromScale(xRes() / xres, yRes() / yres);
}
KisProcessingVisitorSP visitor =
new KisTransformProcessingVisitor(sx, sy,
0, 0,
QPointF(),
0,
0, 0,
filterStrategy,
shapesCorrection);
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
if (resolutionChanged) {
KUndo2Command *parent =
new KisResetShapesCommand(m_d->rootLayer);
new KisImageSetResolutionCommand(this, xres, yres, parent);
applicator.applyCommand(parent);
}
if (sizeChanged) {
applicator.applyCommand(new KisImageResizeCommand(this, size));
}
applicator.end();
}
void KisImage::scaleNode(KisNodeSP node, const QPointF ¢er, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy, KisSelectionSP selection)
{
KUndo2MagicString actionName(kundo2_i18n("Scale Layer"));
KisImageSignalVector emitSignals;
emitSignals << ModifiedSignal;
QPointF offset;
{
KisTransformWorker worker(0,
scaleX, scaleY,
0, 0, 0, 0,
0.0,
0, 0, 0, 0);
QTransform transform = worker.transform();
offset = center - transform.map(center);
}
KisProcessingApplicator applicator(this, node,
KisProcessingApplicator::RECURSIVE,
emitSignals, actionName);
KisTransformProcessingVisitor *visitor =
new KisTransformProcessingVisitor(scaleX, scaleY,
0, 0,
QPointF(),
0,
offset.x(), offset.y(),
filterStrategy);
visitor->setSelection(selection);
if (selection) {
applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
} else {
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
}
applicator.end();
}
void KisImage::rotateImpl(const KUndo2MagicString &actionName,
KisNodeSP rootNode,
double radians,
bool resizeImage,
KisSelectionSP selection)
{
// we can either transform (and resize) the whole image or
// transform a selection, we cannot do both at the same time
KIS_SAFE_ASSERT_RECOVER(!(bool(selection) && resizeImage)) {
selection = 0;
}
const QRect baseBounds =
resizeImage ? bounds() :
selection ? selection->selectedExactRect() :
rootNode->exactBounds();
QPointF offset;
QSize newSize;
{
KisTransformWorker worker(0,
1.0, 1.0,
0, 0, 0, 0,
radians,
0, 0, 0, 0);
QTransform transform = worker.transform();
if (resizeImage) {
QRect newRect = transform.mapRect(baseBounds);
newSize = newRect.size();
offset = -newRect.topLeft();
}
else {
QPointF origin = QRectF(baseBounds).center();
newSize = size();
offset = -(transform.map(origin) - origin);
}
}
bool sizeChanged = resizeImage &&
(newSize.width() != baseBounds.width() ||
newSize.height() != baseBounds.height());
// These signals will be emitted after processing is done
KisImageSignalVector emitSignals;
if (sizeChanged) emitSignals << ComplexSizeChangedSignal(baseBounds, newSize);
emitSignals << ModifiedSignal;
// These flags determine whether updates are transferred to the UI during processing
KisProcessingApplicator::ProcessingFlags signalFlags =
sizeChanged ?
KisProcessingApplicator::NO_UI_UPDATES :
KisProcessingApplicator::NONE;
KisProcessingApplicator applicator(this, rootNode,
KisProcessingApplicator::RECURSIVE | signalFlags,
emitSignals, actionName);
KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bicubic");
KisTransformProcessingVisitor *visitor =
new KisTransformProcessingVisitor(1.0, 1.0, 0.0, 0.0,
QPointF(),
radians,
offset.x(), offset.y(),
filter);
if (selection) {
visitor->setSelection(selection);
}
if (selection) {
applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
} else {
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
}
if (sizeChanged) {
applicator.applyCommand(new KisImageResizeCommand(this, newSize));
}
applicator.end();
}
void KisImage::rotateImage(double radians)
{
rotateImpl(kundo2_i18n("Rotate Image"), root(), radians, true, 0);
}
void KisImage::rotateNode(KisNodeSP node, double radians, KisSelectionSP selection)
{
if (node->inherits("KisMask")) {
rotateImpl(kundo2_i18n("Rotate Mask"), node, radians, false, selection);
} else {
rotateImpl(kundo2_i18n("Rotate Layer"), node, radians, false, selection);
}
}
void KisImage::shearImpl(const KUndo2MagicString &actionName,
KisNodeSP rootNode,
bool resizeImage,
double angleX, double angleY,
KisSelectionSP selection)
{
const QRect baseBounds =
resizeImage ? bounds() :
selection ? selection->selectedExactRect() :
rootNode->exactBounds();
const QPointF origin = QRectF(baseBounds).center();
//angleX, angleY are in degrees
const qreal pi = 3.1415926535897932385;
const qreal deg2rad = pi / 180.0;
qreal tanX = tan(angleX * deg2rad);
qreal tanY = tan(angleY * deg2rad);
QPointF offset;
QSize newSize;
{
KisTransformWorker worker(0,
1.0, 1.0,
tanX, tanY, origin.x(), origin.y(),
0,
0, 0, 0, 0);
QRect newRect = worker.transform().mapRect(baseBounds);
newSize = newRect.size();
if (resizeImage) offset = -newRect.topLeft();
}
if (newSize == baseBounds.size()) return;
KisImageSignalVector emitSignals;
if (resizeImage) emitSignals << ComplexSizeChangedSignal(baseBounds, newSize);
emitSignals << ModifiedSignal;
KisProcessingApplicator::ProcessingFlags signalFlags =
KisProcessingApplicator::RECURSIVE;
if (resizeImage) signalFlags |= KisProcessingApplicator::NO_UI_UPDATES;
KisProcessingApplicator applicator(this, rootNode,
signalFlags,
emitSignals, actionName);
KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bilinear");
KisTransformProcessingVisitor *visitor =
new KisTransformProcessingVisitor(1.0, 1.0,
tanX, tanY, origin,
0,
offset.x(), offset.y(),
filter);
if (selection) {
visitor->setSelection(selection);
}
if (selection) {
applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
} else {
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
}
if (resizeImage) {
applicator.applyCommand(new KisImageResizeCommand(this, newSize));
}
applicator.end();
}
void KisImage::shearNode(KisNodeSP node, double angleX, double angleY, KisSelectionSP selection)
{
if (node->inherits("KisMask")) {
shearImpl(kundo2_i18n("Shear Mask"), node, false,
angleX, angleY, selection);
} else {
shearImpl(kundo2_i18n("Shear Layer"), node, false,
angleX, angleY, selection);
}
}
void KisImage::shear(double angleX, double angleY)
{
shearImpl(kundo2_i18n("Shear Image"), m_d->rootLayer, true,
angleX, angleY, 0);
}
void KisImage::convertImageColorSpace(const KoColorSpace *dstColorSpace,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags)
{
if (!dstColorSpace) return;
const KoColorSpace *srcColorSpace = m_d->colorSpace;
undoAdapter()->beginMacro(kundo2_i18n("Convert Image Color Space"));
undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true));
undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace));
KisColorSpaceConvertVisitor visitor(this, srcColorSpace, dstColorSpace, renderingIntent, conversionFlags);
m_d->rootLayer->accept(visitor);
undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false));
undoAdapter()->endMacro();
setModified();
}
bool KisImage::assignImageProfile(const KoColorProfile *profile)
{
if (!profile) return false;
const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile);
const KoColorSpace *srcCs = colorSpace();
if (!dstCs) return false;
m_d->colorSpace = dstCs;
KisChangeProfileVisitor visitor(srcCs, dstCs);
bool retval = m_d->rootLayer->accept(visitor);
m_d->signalRouter.emitNotification(ProfileChangedSignal);
return retval;
}
void KisImage::convertProjectionColorSpace(const KoColorSpace *dstColorSpace)
{
if (*m_d->colorSpace == *dstColorSpace) return;
undoAdapter()->beginMacro(kundo2_i18n("Convert Projection Color Space"));
undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true));
undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace));
undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false));
undoAdapter()->endMacro();
setModified();
}
void KisImage::setProjectionColorSpace(const KoColorSpace * colorSpace)
{
m_d->colorSpace = colorSpace;
m_d->rootLayer->resetCache();
m_d->signalRouter.emitNotification(ColorSpaceChangedSignal);
}
const KoColorSpace * KisImage::colorSpace() const
{
return m_d->colorSpace;
}
const KoColorProfile * KisImage::profile() const
{
return colorSpace()->profile();
}
double KisImage::xRes() const
{
return m_d->xres;
}
double KisImage::yRes() const
{
return m_d->yres;
}
void KisImage::setResolution(double xres, double yres)
{
m_d->xres = xres;
m_d->yres = yres;
m_d->signalRouter.emitNotification(ResolutionChangedSignal);
}
QPointF KisImage::documentToPixel(const QPointF &documentCoord) const
{
return QPointF(documentCoord.x() * xRes(), documentCoord.y() * yRes());
}
QPoint KisImage::documentToImagePixelFloored(const QPointF &documentCoord) const
{
QPointF pixelCoord = documentToPixel(documentCoord);
return QPoint(qFloor(pixelCoord.x()), qFloor(pixelCoord.y()));
}
QRectF KisImage::documentToPixel(const QRectF &documentRect) const
{
return QRectF(documentToPixel(documentRect.topLeft()), documentToPixel(documentRect.bottomRight()));
}
QPointF KisImage::pixelToDocument(const QPointF &pixelCoord) const
{
return QPointF(pixelCoord.x() / xRes(), pixelCoord.y() / yRes());
}
QPointF KisImage::pixelToDocument(const QPoint &pixelCoord) const
{
return QPointF((pixelCoord.x() + 0.5) / xRes(), (pixelCoord.y() + 0.5) / yRes());
}
QRectF KisImage::pixelToDocument(const QRectF &pixelCoord) const
{
return QRectF(pixelToDocument(pixelCoord.topLeft()), pixelToDocument(pixelCoord.bottomRight()));
}
qint32 KisImage::width() const
{
return m_d->width;
}
qint32 KisImage::height() const
{
return m_d->height;
}
KisGroupLayerSP KisImage::rootLayer() const
{
Q_ASSERT(m_d->rootLayer);
return m_d->rootLayer;
}
KisPaintDeviceSP KisImage::projection() const
{
if (m_d->isolatedRootNode) {
return m_d->isolatedRootNode->projection();
}
Q_ASSERT(m_d->rootLayer);
KisPaintDeviceSP projection = m_d->rootLayer->projection();
Q_ASSERT(projection);
return projection;
}
qint32 KisImage::nlayers() const
{
QStringList list;
list << "KisLayer";
KisCountVisitor visitor(list, KoProperties());
m_d->rootLayer->accept(visitor);
return visitor.count();
}
qint32 KisImage::nHiddenLayers() const
{
QStringList list;
list << "KisLayer";
KoProperties properties;
properties.setProperty("visible", false);
KisCountVisitor visitor(list, properties);
m_d->rootLayer->accept(visitor);
return visitor.count();
}
void KisImage::flatten(KisNodeSP activeNode)
{
KisLayerUtils::flattenImage(this, activeNode);
}
void KisImage::mergeMultipleLayers(QList mergedNodes, KisNodeSP putAfter)
{
if (!KisLayerUtils::tryMergeSelectionMasks(this, mergedNodes, putAfter)) {
KisLayerUtils::mergeMultipleLayers(this, mergedNodes, putAfter);
}
}
void KisImage::mergeDown(KisLayerSP layer, const KisMetaData::MergeStrategy* strategy)
{
KisLayerUtils::mergeDown(this, layer, strategy);
}
void KisImage::flattenLayer(KisLayerSP layer)
{
KisLayerUtils::flattenLayer(this, layer);
}
void KisImage::setModified()
{
m_d->signalRouter.emitNotification(ModifiedSignal);
}
QImage KisImage::convertToQImage(QRect imageRect,
const KoColorProfile * profile)
{
qint32 x;
qint32 y;
qint32 w;
qint32 h;
imageRect.getRect(&x, &y, &w, &h);
return convertToQImage(x, y, w, h, profile);
}
QImage KisImage::convertToQImage(qint32 x,
qint32 y,
qint32 w,
qint32 h,
const KoColorProfile * profile)
{
KisPaintDeviceSP dev = projection();
if (!dev) return QImage();
QImage image = dev->convertToQImage(const_cast(profile), x, y, w, h,
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
return image;
}
QImage KisImage::convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile)
{
if (scaledImageSize.isEmpty()) {
return QImage();
}
KisPaintDeviceSP dev = new KisPaintDevice(colorSpace());
KisPainter gc;
gc.copyAreaOptimized(QPoint(0, 0), projection(), dev, bounds());
gc.end();
double scaleX = qreal(scaledImageSize.width()) / width();
double scaleY = qreal(scaledImageSize.height()) / height();
QPointer updater = new KoDummyUpdater();
KisTransformWorker worker(dev, scaleX, scaleY, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, updater, KisFilterStrategyRegistry::instance()->value("Bicubic"));
worker.run();
delete updater;
return dev->convertToQImage(profile);
}
void KisImage::notifyLayersChanged()
{
m_d->signalRouter.emitNotification(LayersChangedSignal);
}
QRect KisImage::bounds() const
{
return QRect(0, 0, width(), height());
}
QRect KisImage::effectiveLodBounds() const
{
QRect boundRect = bounds();
const int lod = currentLevelOfDetail();
if (lod > 0) {
KisLodTransform t(lod);
boundRect = t.map(boundRect);
}
return boundRect;
}
KisPostExecutionUndoAdapter* KisImage::postExecutionUndoAdapter() const
{
const int lod = currentLevelOfDetail();
return lod > 0 ?
m_d->scheduler.lodNPostExecutionUndoAdapter() :
&m_d->postExecutionUndoAdapter;
}
+const KUndo2Command* KisImage::lastExecutedCommand() const
+{
+ return m_d->undoStore->presentCommand();
+}
+
void KisImage::setUndoStore(KisUndoStore *undoStore)
{
m_d->legacyUndoAdapter.setUndoStore(undoStore);
m_d->postExecutionUndoAdapter.setUndoStore(undoStore);
m_d->undoStore.reset(undoStore);
}
KisUndoStore* KisImage::undoStore()
{
return m_d->undoStore.data();
}
KisUndoAdapter* KisImage::undoAdapter() const
{
return &m_d->legacyUndoAdapter;
}
void KisImage::setDefaultProjectionColor(const KoColor &color)
{
KIS_ASSERT_RECOVER_RETURN(m_d->rootLayer);
m_d->rootLayer->setDefaultProjectionColor(color);
}
KoColor KisImage::defaultProjectionColor() const
{
KIS_ASSERT_RECOVER(m_d->rootLayer) {
return KoColor(Qt::transparent, m_d->colorSpace);
}
return m_d->rootLayer->defaultProjectionColor();
}
void KisImage::setRootLayer(KisGroupLayerSP rootLayer)
{
emit sigInternalStopIsolatedModeRequested();
KoColor defaultProjectionColor(Qt::transparent, m_d->colorSpace);
if (m_d->rootLayer) {
m_d->rootLayer->setGraphListener(0);
m_d->rootLayer->disconnect();
KisPaintDeviceSP original = m_d->rootLayer->original();
defaultProjectionColor = original->defaultPixel();
}
m_d->rootLayer = rootLayer;
m_d->rootLayer->disconnect();
m_d->rootLayer->setGraphListener(this);
m_d->rootLayer->setImage(this);
setRoot(m_d->rootLayer.data());
this->setDefaultProjectionColor(defaultProjectionColor);
}
void KisImage::addAnnotation(KisAnnotationSP annotation)
{
// Find the icc annotation, if there is one
vKisAnnotationSP_it it = m_d->annotations.begin();
while (it != m_d->annotations.end()) {
if ((*it)->type() == annotation->type()) {
*it = annotation;
return;
}
++it;
}
m_d->annotations.push_back(annotation);
}
KisAnnotationSP KisImage::annotation(const QString& type)
{
vKisAnnotationSP_it it = m_d->annotations.begin();
while (it != m_d->annotations.end()) {
if ((*it)->type() == type) {
return *it;
}
++it;
}
return KisAnnotationSP(0);
}
void KisImage::removeAnnotation(const QString& type)
{
vKisAnnotationSP_it it = m_d->annotations.begin();
while (it != m_d->annotations.end()) {
if ((*it)->type() == type) {
m_d->annotations.erase(it);
return;
}
++it;
}
}
vKisAnnotationSP_it KisImage::beginAnnotations()
{
return m_d->annotations.begin();
}
vKisAnnotationSP_it KisImage::endAnnotations()
{
return m_d->annotations.end();
}
void KisImage::notifyAboutToBeDeleted()
{
emit sigAboutToBeDeleted();
}
KisImageSignalRouter* KisImage::signalRouter()
{
return &m_d->signalRouter;
}
void KisImage::waitForDone()
{
requestStrokeEnd();
m_d->scheduler.waitForDone();
}
KisStrokeId KisImage::startStroke(KisStrokeStrategy *strokeStrategy)
{
/**
* Ask open strokes to end gracefully. All the strokes clients
* (including the one calling this method right now) will get
* a notification that they should probably end their strokes.
* However this is purely their choice whether to end a stroke
* or not.
*/
if (strokeStrategy->requestsOtherStrokesToEnd()) {
requestStrokeEnd();
}
/**
* Some of the strokes can cancel their work with undoing all the
* changes they did to the paint devices. The problem is that undo
* stack will know nothing about it. Therefore, just notify it
* explicitly
*/
if (strokeStrategy->clearsRedoOnStart()) {
m_d->undoStore->purgeRedoState();
}
return m_d->scheduler.startStroke(strokeStrategy);
}
void KisImage::KisImagePrivate::notifyProjectionUpdatedInPatches(const QRect &rc)
{
KisImageConfig imageConfig(true);
int patchWidth = imageConfig.updatePatchWidth();
int patchHeight = imageConfig.updatePatchHeight();
for (int y = 0; y < rc.height(); y += patchHeight) {
for (int x = 0; x < rc.width(); x += patchWidth) {
QRect patchRect(x, y, patchWidth, patchHeight);
patchRect &= rc;
QtConcurrent::run(std::bind(&KisImage::notifyProjectionUpdated, q, patchRect));
}
}
}
bool KisImage::startIsolatedMode(KisNodeSP node)
{
struct StartIsolatedModeStroke : public KisSimpleStrokeStrategy {
StartIsolatedModeStroke(KisNodeSP node, KisImageSP image)
: KisSimpleStrokeStrategy("start-isolated-mode", kundo2_noi18n("start-isolated-mode")),
m_node(node),
m_image(image)
{
this->enableJob(JOB_INIT, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
setClearsRedoOnStart(false);
}
void initStrokeCallback() {
// pass-though node don't have any projection prepared, so we should
// explicitly regenerate it before activating isolated mode.
m_node->projectionLeaf()->explicitlyRegeneratePassThroughProjection();
m_image->m_d->isolatedRootNode = m_node;
emit m_image->sigIsolatedModeChanged();
// the GUI uses our thread to do the color space conversion so we
// need to emit this signal in multiple threads
m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds());
m_image->invalidateAllFrames();
}
private:
KisNodeSP m_node;
KisImageSP m_image;
};
KisStrokeId id = startStroke(new StartIsolatedModeStroke(node, this));
endStroke(id);
return true;
}
void KisImage::stopIsolatedMode()
{
if (!m_d->isolatedRootNode) return;
struct StopIsolatedModeStroke : public KisSimpleStrokeStrategy {
StopIsolatedModeStroke(KisImageSP image)
: KisSimpleStrokeStrategy("stop-isolated-mode", kundo2_noi18n("stop-isolated-mode")),
m_image(image)
{
this->enableJob(JOB_INIT);
setClearsRedoOnStart(false);
}
void initStrokeCallback() {
if (!m_image->m_d->isolatedRootNode) return;
//KisNodeSP oldRootNode = m_image->m_d->isolatedRootNode;
m_image->m_d->isolatedRootNode = 0;
emit m_image->sigIsolatedModeChanged();
// the GUI uses our thread to do the color space conversion so we
// need to emit this signal in multiple threads
m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds());
m_image->invalidateAllFrames();
// TODO: Substitute notifyProjectionUpdated() with this code
// when update optimization is implemented
//
// QRect updateRect = bounds() | oldRootNode->extent();
// oldRootNode->setDirty(updateRect);
}
private:
KisImageSP m_image;
};
KisStrokeId id = startStroke(new StopIsolatedModeStroke(this));
endStroke(id);
}
KisNodeSP KisImage::isolatedModeRoot() const
{
return m_d->isolatedRootNode;
}
void KisImage::addJob(KisStrokeId id, KisStrokeJobData *data)
{
KisUpdateTimeMonitor::instance()->reportJobStarted(data);
m_d->scheduler.addJob(id, data);
}
void KisImage::endStroke(KisStrokeId id)
{
m_d->scheduler.endStroke(id);
}
bool KisImage::cancelStroke(KisStrokeId id)
{
return m_d->scheduler.cancelStroke(id);
}
bool KisImage::KisImagePrivate::tryCancelCurrentStrokeAsync()
{
return scheduler.tryCancelCurrentStrokeAsync();
}
void KisImage::requestUndoDuringStroke()
{
emit sigUndoDuringStrokeRequested();
}
void KisImage::requestStrokeCancellation()
{
if (!m_d->tryCancelCurrentStrokeAsync()) {
emit sigStrokeCancellationRequested();
}
}
UndoResult KisImage::tryUndoUnfinishedLod0Stroke()
{
return m_d->scheduler.tryUndoLastStrokeAsync();
}
void KisImage::requestStrokeEnd()
{
emit sigStrokeEndRequested();
emit sigStrokeEndRequestedActiveNodeFiltered();
}
void KisImage::requestStrokeEndActiveNode()
{
emit sigStrokeEndRequested();
}
void KisImage::refreshGraph(KisNodeSP root)
{
refreshGraph(root, bounds(), bounds());
}
void KisImage::refreshGraph(KisNodeSP root, const QRect &rc, const QRect &cropRect)
{
if (!root) root = m_d->rootLayer;
m_d->animationInterface->notifyNodeChanged(root.data(), rc, true);
m_d->scheduler.fullRefresh(root, rc, cropRect);
}
void KisImage::initialRefreshGraph()
{
/**
* NOTE: Tricky part. We set crop rect to null, so the clones
* will not rely on precalculated projections of their sources
*/
refreshGraphAsync(0, bounds(), QRect());
waitForDone();
}
void KisImage::refreshGraphAsync(KisNodeSP root)
{
refreshGraphAsync(root, bounds(), bounds());
}
void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc)
{
refreshGraphAsync(root, rc, bounds());
}
void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect)
{
if (!root) root = m_d->rootLayer;
m_d->animationInterface->notifyNodeChanged(root.data(), rc, true);
m_d->scheduler.fullRefreshAsync(root, rc, cropRect);
}
void KisImage::requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect)
{
KIS_ASSERT_RECOVER_RETURN(pseudoFilthy);
m_d->animationInterface->notifyNodeChanged(pseudoFilthy.data(), rc, false);
m_d->scheduler.updateProjectionNoFilthy(pseudoFilthy, rc, cropRect);
}
void KisImage::addSpontaneousJob(KisSpontaneousJob *spontaneousJob)
{
m_d->scheduler.addSpontaneousJob(spontaneousJob);
}
void KisImage::setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter)
{
// update filters are *not* recursive!
KIS_ASSERT_RECOVER_NOOP(!filter || !m_d->projectionUpdatesFilter);
m_d->projectionUpdatesFilter = filter;
}
KisProjectionUpdatesFilterSP KisImage::projectionUpdatesFilter() const
{
return m_d->projectionUpdatesFilter;
}
void KisImage::disableDirtyRequests()
{
setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP(new KisDropAllProjectionUpdatesFilter()));
}
void KisImage::enableDirtyRequests()
{
setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP());
}
void KisImage::disableUIUpdates()
{
m_d->disableUIUpdateSignals.ref();
}
+void KisImage::notifyBatchUpdateStarted()
+{
+ m_d->signalRouter.emitNotifyBatchUpdateStarted();
+}
+
+void KisImage::notifyBatchUpdateEnded()
+{
+ m_d->signalRouter.emitNotifyBatchUpdateEnded();
+}
+
+void KisImage::notifyUIUpdateCompleted(const QRect &rc)
+{
+ notifyProjectionUpdated(rc);
+}
+
QVector KisImage::enableUIUpdates()
{
m_d->disableUIUpdateSignals.deref();
QRect rect;
QVector postponedUpdates;
while (m_d->savedDisabledUIUpdates.pop(rect)) {
postponedUpdates.append(rect);
}
return postponedUpdates;
}
void KisImage::notifyProjectionUpdated(const QRect &rc)
{
KisUpdateTimeMonitor::instance()->reportUpdateFinished(rc);
if (!m_d->disableUIUpdateSignals) {
int lod = currentLevelOfDetail();
QRect dirtyRect = !lod ? rc : KisLodTransform::upscaledRect(rc, lod);
if (dirtyRect.isEmpty()) return;
emit sigImageUpdated(dirtyRect);
} else {
m_d->savedDisabledUIUpdates.push(rc);
}
}
void KisImage::setWorkingThreadsLimit(int value)
{
m_d->scheduler.setThreadsLimit(value);
}
int KisImage::workingThreadsLimit() const
{
return m_d->scheduler.threadsLimit();
}
void KisImage::notifySelectionChanged()
{
/**
* The selection is calculated asynchromously, so it is not
* handled by disableUIUpdates() and other special signals of
* KisImageSignalRouter
*/
m_d->legacyUndoAdapter.emitSelectionChanged();
/**
* Editing of selection masks doesn't necessary produce a
* setDirty() call, so in the end of the stroke we need to request
* direct update of the UI's cache.
*/
if (m_d->isolatedRootNode &&
dynamic_cast(m_d->isolatedRootNode.data())) {
notifyProjectionUpdated(bounds());
}
}
void KisImage::requestProjectionUpdateImpl(KisNode *node,
const QVector &rects,
const QRect &cropRect)
{
if (rects.isEmpty()) return;
m_d->scheduler.updateProjection(node, rects, cropRect);
}
void KisImage::requestProjectionUpdate(KisNode *node, const QVector &rects, bool resetAnimationCache)
{
if (m_d->projectionUpdatesFilter
&& m_d->projectionUpdatesFilter->filter(this, node, rects, resetAnimationCache)) {
return;
}
if (resetAnimationCache) {
m_d->animationInterface->notifyNodeChanged(node, rects, false);
}
/**
* Here we use 'permitted' instead of 'active' intentively,
* because the updates may come after the actual stroke has been
* finished. And having some more updates for the stroke not
* supporting the wrap-around mode will not make much harm.
*/
if (m_d->wrapAroundModePermitted) {
QVector allSplitRects;
const QRect boundRect = effectiveLodBounds();
Q_FOREACH (const QRect &rc, rects) {
KisWrappedRect splitRect(rc, boundRect);
allSplitRects.append(splitRect);
}
requestProjectionUpdateImpl(node, allSplitRects, boundRect);
} else {
requestProjectionUpdateImpl(node, rects, bounds());
}
KisNodeGraphListener::requestProjectionUpdate(node, rects, resetAnimationCache);
}
void KisImage::invalidateFrames(const KisTimeRange &range, const QRect &rect)
{
m_d->animationInterface->invalidateFrames(range, rect);
}
void KisImage::requestTimeSwitch(int time)
{
m_d->animationInterface->requestTimeSwitchNonGUI(time);
}
KisNode *KisImage::graphOverlayNode() const
{
return m_d->overlaySelectionMask.data();
}
QList KisImage::compositions()
{
return m_d->compositions;
}
void KisImage::addComposition(KisLayerCompositionSP composition)
{
m_d->compositions.append(composition);
}
void KisImage::removeComposition(KisLayerCompositionSP composition)
{
m_d->compositions.removeAll(composition);
}
bool checkMasksNeedConversion(KisNodeSP root, const QRect &bounds)
{
KisSelectionMask *mask = dynamic_cast(root.data());
if (mask &&
(!bounds.contains(mask->paintDevice()->exactBounds()) ||
mask->selection()->hasShapeSelection())) {
return true;
}
KisNodeSP node = root->firstChild();
while (node) {
if (checkMasksNeedConversion(node, bounds)) {
return true;
}
node = node->nextSibling();
}
return false;
}
void KisImage::setWrapAroundModePermitted(bool value)
{
if (m_d->wrapAroundModePermitted != value) {
requestStrokeEnd();
}
m_d->wrapAroundModePermitted = value;
if (m_d->wrapAroundModePermitted &&
checkMasksNeedConversion(root(), bounds())) {
KisProcessingApplicator applicator(this, root(),
KisProcessingApplicator::RECURSIVE,
KisImageSignalVector() << ModifiedSignal,
kundo2_i18n("Crop Selections"));
KisProcessingVisitorSP visitor =
new KisCropSelectionsProcessingVisitor(bounds());
applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
applicator.end();
}
}
bool KisImage::wrapAroundModePermitted() const
{
return m_d->wrapAroundModePermitted;
}
bool KisImage::wrapAroundModeActive() const
{
return m_d->wrapAroundModePermitted &&
m_d->scheduler.wrapAroundModeSupported();
}
void KisImage::setDesiredLevelOfDetail(int lod)
{
if (m_d->blockLevelOfDetail) {
qWarning() << "WARNING: KisImage::setDesiredLevelOfDetail()"
<< "was called while LoD functionality was being blocked!";
return;
}
m_d->scheduler.setDesiredLevelOfDetail(lod);
}
int KisImage::currentLevelOfDetail() const
{
if (m_d->blockLevelOfDetail) {
return 0;
}
return m_d->scheduler.currentLevelOfDetail();
}
void KisImage::setLevelOfDetailBlocked(bool value)
{
KisImageBarrierLockerRaw l(this);
if (value && !m_d->blockLevelOfDetail) {
m_d->scheduler.setDesiredLevelOfDetail(0);
}
m_d->blockLevelOfDetail = value;
}
void KisImage::explicitRegenerateLevelOfDetail()
{
if (!m_d->blockLevelOfDetail) {
m_d->scheduler.explicitRegenerateLevelOfDetail();
}
}
bool KisImage::levelOfDetailBlocked() const
{
return m_d->blockLevelOfDetail;
}
void KisImage::notifyNodeCollpasedChanged()
{
emit sigNodeCollapsedChanged();
}
KisImageAnimationInterface* KisImage::animationInterface() const
{
return m_d->animationInterface;
}
void KisImage::setProofingConfiguration(KisProofingConfigurationSP proofingConfig)
{
m_d->proofingConfig = proofingConfig;
emit sigProofingConfigChanged();
}
KisProofingConfigurationSP KisImage::proofingConfiguration() const
{
if (m_d->proofingConfig) {
return m_d->proofingConfig;
}
return KisProofingConfigurationSP();
}
QPointF KisImage::mirrorAxesCenter() const
{
return m_d->axesCenter;
}
void KisImage::setMirrorAxesCenter(const QPointF &value) const
{
m_d->axesCenter = value;
}
void KisImage::setAllowMasksOnRootNode(bool value)
{
m_d->allowMasksOnRootNode = value;
}
bool KisImage::allowMasksOnRootNode() const
{
return m_d->allowMasksOnRootNode;
}
diff --git a/libs/image/kis_image.h b/libs/image/kis_image.h
index 4844b6cb1d..dea01e86a1 100644
--- a/libs/image/kis_image.h
+++ b/libs/image/kis_image.h
@@ -1,1156 +1,1185 @@
/*
* Copyright (c) 2002 Patrick Julien
* Copyright (c) 2007 Boudewijn Rempt
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_IMAGE_H_
#define KIS_IMAGE_H_
#include
#include
#include
#include
#include
#include
#include
#include "kis_types.h"
#include "kis_shared.h"
#include "kis_node_graph_listener.h"
#include "kis_node_facade.h"
#include "kis_image_interfaces.h"
#include "kis_strokes_queue_undo_result.h"
#include
class KoColorSpace;
class KoColor;
class KisCompositeProgressProxy;
class KisUndoStore;
class KisUndoAdapter;
class KisImageSignalRouter;
class KisPostExecutionUndoAdapter;
class KisFilterStrategy;
class KoColorProfile;
class KisLayerComposition;
class KisSpontaneousJob;
class KisImageAnimationInterface;
class KUndo2MagicString;
class KisProofingConfiguration;
class KisPaintDevice;
namespace KisMetaData
{
class MergeStrategy;
}
/**
* This is the image class, it contains a tree of KisLayer stack and
* meta information about the image. And it also provides some
* functions to manipulate the whole image.
*/
class KRITAIMAGE_EXPORT KisImage : public QObject,
public KisStrokesFacade,
public KisStrokeUndoFacade,
public KisUpdatesFacade,
public KisProjectionUpdateListener,
public KisNodeFacade,
public KisNodeGraphListener,
public KisShared
{
Q_OBJECT
public:
/// @p colorSpace can be null. In that case, it will be initialised to a default color space.
KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace *colorSpace, const QString& name);
~KisImage() override;
static KisImageSP fromQImage(const QImage &image, KisUndoStore *undoStore);
public: // KisNodeGraphListener implementation
void aboutToAddANode(KisNode *parent, int index) override;
void nodeHasBeenAdded(KisNode *parent, int index) override;
void aboutToRemoveANode(KisNode *parent, int index) override;
void nodeChanged(KisNode * node) override;
void invalidateAllFrames() override;
void notifySelectionChanged() override;
void requestProjectionUpdate(KisNode *node, const QVector &rects, bool resetAnimationCache) override;
void invalidateFrames(const KisTimeRange &range, const QRect &rect) override;
void requestTimeSwitch(int time) override;
KisNode* graphOverlayNode() const override;
public: // KisProjectionUpdateListener implementation
void notifyProjectionUpdated(const QRect &rc) override;
public:
/**
* Set the number of threads used by the image's working threads
*/
void setWorkingThreadsLimit(int value);
/**
* Return the number of threads available to the image's working threads
*/
int workingThreadsLimit() const;
/**
* Makes a copy of the image with all the layers. If possible, shallow
* copies of the layers are made.
*
* \p exactCopy shows if the copied image should look *exactly* the same as
* the other one (according to it's .kra xml representation). It means that
* the layers will have the same UUID keys and, therefore, you are not
* expected to use the copied image anywhere except for saving. Don't use
* this option if you plan to work with the copied image later.
*/
KisImage *clone(bool exactCopy = false);
void copyFromImage(const KisImage &rhs);
private:
// must specify exactly one from CONSTRUCT or REPLACE.
enum CopyPolicy {
CONSTRUCT = 1, ///< we are copy-constructing a new KisImage
REPLACE = 2, ///< we are replacing the current KisImage with another
EXACT_COPY = 4, /// we need an exact copy of the original image
};
void copyFromImageImpl(const KisImage &rhs, int policy);
public:
/**
* Render the projection onto a QImage.
*/
QImage convertToQImage(qint32 x1,
qint32 y1,
qint32 width,
qint32 height,
const KoColorProfile * profile);
/**
* Render the projection onto a QImage.
* (this is an overloaded function)
*/
QImage convertToQImage(QRect imageRect,
const KoColorProfile * profile);
/**
* Render a thumbnail of the projection onto a QImage.
*/
QImage convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile);
/**
* [low-level] Lock the image without waiting for all the internal job queues are processed
*
* WARNING: Don't use it unless you really know what you are doing! Use barrierLock() instead!
*
* Waits for all the **currently running** internal jobs to complete and locks the image
* for writing. Please note that this function does **not** wait for all the internal
* queues to process, so there might be some non-finished actions pending. It means that
* you just postpone these actions until you unlock() the image back. Until then, then image
* might easily be frozen in some inconsistent state.
*
* The only sane usage for this function is to lock the image for **emergency**
* processing, when some internal action or scheduler got hung up, and you just want
* to fetch some data from the image without races.
*
* In all other cases, please use barrierLock() instead!
*/
void lock();
/**
* Unlocks the image and starts/resumes all the pending internal jobs. If the image
* has been locked for a non-readOnly access, then all the internal caches of the image
* (e.g. lod-planes) are reset and regeneration jobs are scheduled.
*/
void unlock();
/**
* @return return true if the image is in a locked state, i.e. all the internal
* jobs are blocked from execution by calling wither lock() or barrierLock().
*
* When the image is locked, the user can do some modifications to the image
* contents safely without a perspective having race conditions with internal
* image jobs.
*/
bool locked() const;
/**
* Sets the mask (it must be a part of the node hierarchy already) to be paited on
* the top of all layers. This method does all the locking and syncing for you. It
* is executed asynchronously.
*/
void setOverlaySelectionMask(KisSelectionMaskSP mask);
/**
* \see setOverlaySelectionMask
*/
KisSelectionMaskSP overlaySelectionMask() const;
/**
* \see setOverlaySelectionMask
*/
bool hasOverlaySelectionMask() const;
/**
* @return the global selection object or 0 if there is none. The
* global selection is always read-write.
*/
KisSelectionSP globalSelection() const;
/**
* Retrieve the next automatic layername (XXX: fix to add option to return Mask X)
*/
QString nextLayerName(const QString &baseName = "") const;
/**
* Set the automatic layer name counter one back.
*/
void rollBackLayerName();
/**
* @brief start asynchronous operation on resizing the image
*
* The method will resize the image to fit the new size without
* dropping any pixel data. The GUI will get correct
* notification with old and new sizes, so it adjust canvas origin
* accordingly and avoid jumping of the canvas on screen
*
* @param newRect the rectangle of the image which will be visible
* after operation is completed
*
* Please note that the actual operation starts asynchronously in
* a background, so you cannot expect the image having new size
* right after this call.
*/
void resizeImage(const QRect& newRect);
/**
* @brief start asynchronous operation on cropping the image
*
* The method will **drop** all the image data outside \p newRect
* and resize the image to fit the new size. The GUI will get correct
* notification with old and new sizes, so it adjust canvas origin
* accordingly and avoid jumping of the canvas on screen
*
* @param newRect the rectangle of the image which will be cut-out
*
* Please note that the actual operation starts asynchronously in
* a background, so you cannot expect the image having new size
* right after this call.
*/
void cropImage(const QRect& newRect);
/**
* @brief start asynchronous operation on cropping a subtree of nodes starting at \p node
*
* The method will **drop** all the layer data outside \p newRect. Neither
* image nor a layer will be moved anywhere
*
* @param node node to crop
* @param newRect the rectangle of the layer which will be cut-out
*
* Please note that the actual operation starts asynchronously in
* a background, so you cannot expect the image having new size
* right after this call.
*/
void cropNode(KisNodeSP node, const QRect& newRect);
/**
* @brief start asynchronous operation on scaling the image
* @param size new image size in pixels
* @param xres new image x-resolution pixels-per-pt
* @param yres new image y-resolution pixels-per-pt
* @param filterStrategy filtering strategy
*
* Please note that the actual operation starts asynchronously in
* a background, so you cannot expect the image having new size
* right after this call.
*/
void scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy);
/**
* @brief start asynchronous operation on scaling a subtree of nodes starting at \p node
* @param node node to scale
* @param center the center of the scaling
* @param scaleX x-scale coefficient to be applied to the node
* @param scaleY y-scale coefficient to be applied to the node
* @param filterStrategy filtering strategy
* @param selection the selection we based on
*
* Please note that the actual operation starts asynchronously in
* a background, so you cannot expect the image having new size
* right after this call.
*/
void scaleNode(KisNodeSP node, const QPointF ¢er, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy, KisSelectionSP selection);
/**
* @brief start asynchronous operation on rotating the image
*
* The image is resized to fit the rotated rectangle
*
* @param radians rotation angle in radians
*
* Please note that the actual operation starts asynchronously in
* a background, so you cannot expect the operation being completed
* right after the call
*/
void rotateImage(double radians);
/**
* @brief start asynchronous operation on rotating a subtree of nodes starting at \p node
*
* The image is not resized!
*
* @param node the root of the subtree to rotate
* @param radians rotation angle in radians
* @param selection the selection we based on
*
* Please note that the actual operation starts asynchronously in
* a background, so you cannot expect the operation being completed
* right after the call
*/
void rotateNode(KisNodeSP node, double radians, KisSelectionSP selection);
/**
* @brief start asynchronous operation on shearing the image
*
* The image is resized to fit the sheared polygon
*
* @p angleX, @p angleY are given in degrees.
*
* Please note that the actual operation starts asynchronously in
* a background, so you cannot expect the operation being completed
* right after the call
*/
void shear(double angleX, double angleY);
/**
* @brief start asynchronous operation on shearing a subtree of nodes starting at \p node
*
* The image is not resized!
*
* @param node the root of the subtree to rotate
* @param angleX x-shear given in degrees.
* @param angleY y-shear given in degrees.
* @param selection the selection we based on
*
* Please note that the actual operation starts asynchronously in
* a background, so you cannot expect the operation being completed
* right after the call
*/
void shearNode(KisNodeSP node, double angleX, double angleY, KisSelectionSP selection);
/**
* Convert the image and all its layers to the dstColorSpace
*/
void convertImageColorSpace(const KoColorSpace *dstColorSpace,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags);
/**
* Set the color space of the projection (and the root layer)
* to dstColorSpace. No conversion is done for other layers,
* their colorspace can differ.
* @note No conversion is done, only regeneration, so no rendering
* intent needed
*/
void convertProjectionColorSpace(const KoColorSpace *dstColorSpace);
// Get the profile associated with this image
const KoColorProfile * profile() const;
/**
* Set the profile of the image to the new profile and do the same for
* all layers that have the same colorspace and profile of the image.
* It doesn't do any pixel conversion.
*
* This is essential if you have loaded an image that didn't
* have an embedded profile to which you want to attach the right profile.
*
* This does not create an undo action; only call it when creating or
* loading an image.
*
* @returns false if the profile could not be assigned
*/
bool assignImageProfile(const KoColorProfile *profile);
/**
* Returns the current undo adapter. You can add new commands to the
* undo stack using the adapter. This adapter is used for a backward
* compatibility for old commands created before strokes. It blocks
* all the porcessing at the scheduler, waits until it's finished
* and executes commands exclusively.
*/
KisUndoAdapter* undoAdapter() const;
/**
* This adapter is used by the strokes system. The commands are added
* to it *after* redo() is done (in the scheduler context). They are
* wrapped into a special command and added to the undo stack. redo()
* in not called.
*/
KisPostExecutionUndoAdapter* postExecutionUndoAdapter() const override;
+ /**
+ * Return the lastly executed LoD0 command. It is effectively the same
+ * as to call undoAdapter()->presentCommand();
+ */
+ const KUndo2Command* lastExecutedCommand() const;
+
/**
* Replace current undo store with the new one. The old store
* will be deleted.
* This method is used by KisDocument for dropping all the commands
* during file loading.
*/
void setUndoStore(KisUndoStore *undoStore);
/**
* Return current undo store of the image
*/
KisUndoStore* undoStore();
/**
* Tell the image it's modified; this emits the sigImageModified
* signal. This happens when the image needs to be saved
*/
void setModified();
/**
* The default colorspace of this image: new layers will have this
* colorspace and the projection will have this colorspace.
*/
const KoColorSpace * colorSpace() const;
/**
* X resolution in pixels per pt
*/
double xRes() const;
/**
* Y resolution in pixels per pt
*/
double yRes() const;
/**
* Set the resolution in pixels per pt.
*/
void setResolution(double xres, double yres);
/**
* Convert a document coordinate to a pixel coordinate.
*
* @param documentCoord PostScript Pt coordinate to convert.
*/
QPointF documentToPixel(const QPointF &documentCoord) const;
/**
* Convert a document coordinate to an integer pixel coordinate rounded down.
*
* @param documentCoord PostScript Pt coordinate to convert.
*/
QPoint documentToImagePixelFloored(const QPointF &documentCoord) const;
/**
* Convert a document rectangle to a pixel rectangle.
*
* @param documentRect PostScript Pt rectangle to convert.
*/
QRectF documentToPixel(const QRectF &documentRect) const;
/**
* Convert a pixel coordinate to a document coordinate.
*
* @param pixelCoord pixel coordinate to convert.
*/
QPointF pixelToDocument(const QPointF &pixelCoord) const;
/**
* Convert an integer pixel coordinate to a document coordinate.
* The document coordinate is at the centre of the pixel.
*
* @param pixelCoord pixel coordinate to convert.
*/
QPointF pixelToDocument(const QPoint &pixelCoord) const;
/**
* Convert a document rectangle to an integer pixel rectangle.
*
* @param pixelCoord pixel coordinate to convert.
*/
QRectF pixelToDocument(const QRectF &pixelCoord) const;
/**
* Return the width of the image
*/
qint32 width() const;
/**
* Return the height of the image
*/
qint32 height() const;
/**
* Return the size of the image
*/
QSize size() const {
return QSize(width(), height());
}
/**
* @return the root node of the image node graph
*/
KisGroupLayerSP rootLayer() const;
/**
* Return the projection; that is, the complete, composited
* representation of this image.
*/
KisPaintDeviceSP projection() const;
/**
* Return the number of layers (not other nodes) that are in this
* image.
*/
qint32 nlayers() const;
/**
* Return the number of layers (not other node types) that are in
* this image and that are hidden.
*/
qint32 nHiddenLayers() const;
/**
* Merge all visible layers and discard hidden ones.
*/
void flatten(KisNodeSP activeNode);
/**
* Merge the specified layer with the layer
* below this layer, remove the specified layer.
*/
void mergeDown(KisLayerSP l, const KisMetaData::MergeStrategy* strategy);
/**
* flatten the layer: that is, the projection becomes the layer
* and all subnodes are removed. If this is not a paint layer, it will morph
* into a paint layer.
*/
void flattenLayer(KisLayerSP layer);
/**
* Merges layers in \p mergedLayers and creates a new layer above
* \p putAfter
*/
void mergeMultipleLayers(QList mergedLayers, KisNodeSP putAfter);
/// @return the exact bounds of the image in pixel coordinates.
QRect bounds() const;
/**
* Returns the actual bounds of the image, taking LevelOfDetail
* into account. This value is used as a bounds() value of
* KisDefaultBounds object.
*/
QRect effectiveLodBounds() const;
/// use if the layers have changed _completely_ (eg. when flattening)
void notifyLayersChanged();
/**
* Sets the default color of the root layer projection. All the layers
* will be merged on top of this very color
*/
void setDefaultProjectionColor(const KoColor &color);
/**
* \see setDefaultProjectionColor()
*/
KoColor defaultProjectionColor() const;
void setRootLayer(KisGroupLayerSP rootLayer);
/**
* Add an annotation for this image. This can be anything: Gamma, EXIF, etc.
* Note that the "icc" annotation is reserved for the color strategies.
* If the annotation already exists, overwrite it with this one.
*/
void addAnnotation(KisAnnotationSP annotation);
/** get the annotation with the given type, can return 0 */
KisAnnotationSP annotation(const QString& type);
/** delete the annotation, if the image contains it */
void removeAnnotation(const QString& type);
/**
* Start of an iteration over the annotations of this image (including the ICC Profile)
*/
vKisAnnotationSP_it beginAnnotations();
/** end of an iteration over the annotations of this image */
vKisAnnotationSP_it endAnnotations();
/**
* Called before the image is deleted and sends the sigAboutToBeDeleted signal
*/
void notifyAboutToBeDeleted();
KisImageSignalRouter* signalRouter();
/**
* Returns whether we can reselect current global selection
*
* \see reselectGlobalSelection()
*/
bool canReselectGlobalSelection();
/**
* Returns the layer compositions for the image
*/
QList compositions();
/**
* Adds a new layer composition, will be saved with the image
*/
void addComposition(KisLayerCompositionSP composition);
/**
* Remove the layer compostion
*/
void removeComposition(KisLayerCompositionSP composition);
/**
* Permit or deny the wrap-around mode for all the paint devices
* of the image. Note that permitting the wraparound mode will not
* necessarily activate it right now. To be activated the wrap
* around mode should be 1) permitted; 2) supported by the
* currently running stroke.
*/
void setWrapAroundModePermitted(bool value);
/**
* \return whether the wrap-around mode is permitted for this
* image. If the wrap around mode is permitted and the
* currently running stroke supports it, the mode will be
* activated for all paint devices of the image.
*
* \see setWrapAroundMode
*/
bool wrapAroundModePermitted() const;
/**
* \return whether the wraparound mode is activated for all the
* devices of the image. The mode is activated when both
* factors are true: the user permitted it and the stroke
* supports it
*/
bool wrapAroundModeActive() const;
/**
* \return current level of detail which is used when processing the image.
* Current working zoom = 2 ^ (- currentLevelOfDetail()). Default value is
* null, which means we work on the original image.
*/
int currentLevelOfDetail() const;
/**
* Notify KisImage which level of detail should be used in the
* lod-mode. Setting the mode does not guarantee the LOD to be
* used. It will be activated only when the stokes supports it.
*/
void setDesiredLevelOfDetail(int lod);
/**
* Relative position of the mirror axis center
* 0,0 - topleft corner of the image
* 1,1 - bottomright corner of the image
*/
QPointF mirrorAxesCenter() const;
/**
* Sets the relative position of the axes center
* \see mirrorAxesCenter() for details
*/
void setMirrorAxesCenter(const QPointF &value) const;
/**
* Configure the image to allow masks on the root not (as reported by
* root()->allowAsChild()). By default it is not allowed (because it
* looks weird from GUI point of view)
*/
void setAllowMasksOnRootNode(bool value);
/**
* \see setAllowMasksOnRootNode()
*/
bool allowMasksOnRootNode() const;
public Q_SLOTS:
/**
* Explicitly start regeneration of LoD planes of all the devices
* in the image. This call should be performed when the user is idle,
* just to make the quality of image updates better.
*/
void explicitRegenerateLevelOfDetail();
public:
/**
* Blocks usage of level of detail functionality. After this method
* has been called, no new strokes will use LoD.
*/
void setLevelOfDetailBlocked(bool value);
/**
* \see setLevelOfDetailBlocked()
*/
bool levelOfDetailBlocked() const;
/**
* Notifies that the node collapsed state has changed
*/
void notifyNodeCollpasedChanged();
KisImageAnimationInterface *animationInterface() const;
/**
* @brief setProofingConfiguration, this sets the image's proofing configuration, and signals
* the proofingConfiguration has changed.
* @param proofingConfig - the kis proofing config that will be used instead.
*/
void setProofingConfiguration(KisProofingConfigurationSP proofingConfig);
/**
* @brief proofingConfiguration
* @return the proofing configuration of the image.
*/
KisProofingConfigurationSP proofingConfiguration() const;
public Q_SLOTS:
bool startIsolatedMode(KisNodeSP node);
void stopIsolatedMode();
public:
KisNodeSP isolatedModeRoot() const;
Q_SIGNALS:
/**
* Emitted whenever an action has caused the image to be
* recomposited. Parameter is the rect that has been recomposited.
*/
void sigImageUpdated(const QRect &);
/**
Emitted whenever the image has been modified, so that it
doesn't match with the version saved on disk.
*/
void sigImageModified();
/**
* The signal is emitted when the size of the image is changed.
* \p oldStillPoint and \p newStillPoint give the receiver the
* hint about how the new and old rect of the image correspond to
* each other. They specify the point of the image around which
* the conversion was done. This point will stay still on the
* user's screen. That is the \p newStillPoint of the new image
* will be painted at the same screen position, where \p
* oldStillPoint of the old image was painted.
*
* \param oldStillPoint is a still point represented in *old*
* image coordinates
*
* \param newStillPoint is a still point represented in *new*
* image coordinates
*/
void sigSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint);
void sigProfileChanged(const KoColorProfile * profile);
void sigColorSpaceChanged(const KoColorSpace* cs);
void sigResolutionChanged(double xRes, double yRes);
void sigRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes);
/**
* Inform the model that a node was changed
*/
void sigNodeChanged(KisNodeSP node);
/**
* Inform that the image is going to be deleted
*/
void sigAboutToBeDeleted();
/**
* The signal is emitted right after a node has been connected
* to the graph of the nodes.
*
* WARNING: you must not request any graph-related information
* about the node being run in a not-scheduler thread. If you need
* information about the parent/siblings of the node connect
* with Qt::DirectConnection, get needed information and then
* emit another Qt::AutoConnection signal to pass this information
* to your thread. See details of the implementation
* in KisDummiesfacadeBase.
*/
void sigNodeAddedAsync(KisNodeSP node);
/**
* This signal is emitted right before a node is going to removed
* from the graph of the nodes.
*
* WARNING: you must not request any graph-related information
* about the node being run in a not-scheduler thread.
*
* \see comment in sigNodeAddedAsync()
*/
void sigRemoveNodeAsync(KisNodeSP node);
/**
* Emitted when the root node of the image has changed.
* It happens, e.g. when we flatten the image. When
* this happens the receiver should reload information
* about the image
*/
void sigLayersChangedAsync();
/**
* Emitted when the UI has requested the undo of the last stroke's
* operation. The point is, we cannot deal with the internals of
* the stroke without its creator knowing about it (which most
* probably cause a crash), so we just forward this request from
* the UI to the creator of the stroke.
*
* If your tool supports undoing part of its work, just listen to
* this signal and undo when it comes
*/
void sigUndoDuringStrokeRequested();
/**
* Emitted when the UI has requested the cancellation of
* the stroke. The point is, we cannot cancel the stroke
* without its creator knowing about it (which most probably
* cause a crash), so we just forward this request from the UI
* to the creator of the stroke.
*
* If your tool supports cancelling of its work in the middle
* of operation, just listen to this signal and cancel
* the stroke when it comes
*/
void sigStrokeCancellationRequested();
/**
* Emitted when the image decides that the stroke should better
* be ended. The point is, we cannot just end the stroke
* without its creator knowing about it (which most probably
* cause a crash), so we just forward this request from the UI
* to the creator of the stroke.
*
* If your tool supports long strokes that may involve multiple
* mouse actions in one stroke, just listen to this signal and
* end the stroke when it comes.
*/
void sigStrokeEndRequested();
/**
* Same as sigStrokeEndRequested() but is not emitted when the active node
* is changed.
*/
void sigStrokeEndRequestedActiveNodeFiltered();
/**
* Emitted when the isolated mode status has changed.
*
* Can be used by the receivers to catch a fact of forcefully
* stopping the isolated mode by the image when some complex
* action was requested
*/
void sigIsolatedModeChanged();
/**
* Emitted when one or more nodes changed the collapsed state
*
*/
void sigNodeCollapsedChanged();
/**
* Emitted when the proofing configuration of the image is being changed.
*
*/
void sigProofingConfigChanged();
/**
* Internal signal for asynchronously requesting isolated mode to stop. Don't use it
* outside KisImage, use sigIsolatedModeChanged() instead.
*/
void sigInternalStopIsolatedModeRequested();
public Q_SLOTS:
KisCompositeProgressProxy* compositeProgressProxy();
bool isIdle(bool allowLocked = false);
/**
* @brief Wait until all the queued background jobs are completed and lock the image.
*
* KisImage object has a local scheduler that executes long-running image
* rendering/modifying jobs (we call them "strokes") in a background. Basically,
* one should either access the image from the scope of such jobs (strokes) or
* just lock the image before using.
*
* Calling barrierLock() will wait until all the queued operations are finished
* and lock the image, so you can start accessing it in a safe way.
*
* @p readOnly tells the image if the caller is going to modify the image during
* holding the lock. Locking with non-readOnly access will reset all
* the internal caches of the image (lod-planes) when the lock status
* will be lifted.
*/
void barrierLock(bool readOnly = false);
/**
* @brief Tries to lock the image without waiting for the jobs to finish
*
* Same as barrierLock(), but doesn't block execution of the calling thread
* until all the background jobs are finished. Instead, in case of presence of
* unfinished jobs in the queue, it just returns false
*
* @return whether the lock has been acquired
* @see barrierLock
*/
bool tryBarrierLock(bool readOnly = false);
/**
* Wait for all the internal image jobs to complete and return without locking
* the image. This function is handly for tests or other synchronous actions,
* when one needs to wait for the result of his actions.
*/
void waitForDone();
KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy) override;
void addJob(KisStrokeId id, KisStrokeJobData *data) override;
void endStroke(KisStrokeId id) override;
bool cancelStroke(KisStrokeId id) override;
/**
* @brief blockUpdates block updating the image projection
*/
void blockUpdates() override;
/**
* @brief unblockUpdates unblock updating the image project. This
* only restarts the scheduler and does not schedule a full refresh.
*/
void unblockUpdates() override;
/**
* Disables notification of the UI about the changes in the image.
* This feature is used by KisProcessingApplicator. It is needed
* when we change the size of the image. In this case, the whole
* image will be reloaded into UI by sigSizeChanged(), so there is
* no need to inform the UI about individual dirty rects.
*
* The last call to enableUIUpdates() will return the list of udpates
* that were requested while they were blocked.
*/
void disableUIUpdates() override;
+ /**
+ * Notify GUI about a bunch of updates planned. GUI is expected to wait
+ * until all the updates are completed, and render them on screen only
+ * in the very and of the batch.
+ */
+ void notifyBatchUpdateStarted() override;
+
+ /**
+ * Notify GUI that batch update has been completed. Now GUI can start
+ * showing all of them on screen.
+ */
+ void notifyBatchUpdateEnded() override;
+
+ /**
+ * Notify GUI that rect \p rc is now prepared in the image and
+ * GUI can read data from it.
+ *
+ * WARNING: GUI will read the data right in the handler of this
+ * signal, so exclusive access to the area must be guaranteed
+ * by the caller.
+ */
+ void notifyUIUpdateCompleted(const QRect &rc) override;
+
/**
* \see disableUIUpdates
*/
QVector enableUIUpdates() override;
/**
* Disables the processing of all the setDirty() requests that
* come to the image. The incoming requests are effectively
* *dropped*.
*
* This feature is used by KisProcessingApplicator. For many cases
* it provides its own updates interface, which recalculates the
* whole subtree of nodes. But while we change any particular
* node, it can ask for an update itself. This method is a way of
* blocking such intermediate (and excessive) requests.
*
* NOTE: this is a convenience function for setProjectionUpdatesFilter()
* that installs a predefined filter that eats everything. Please
* note that these calls are *not* recursive
*/
void disableDirtyRequests() override;
/**
* \see disableDirtyRequests()
*/
void enableDirtyRequests() override;
/**
* Installs a filter object that will filter all the incoming projection update
* requests. If the filter return true, the incoming update is dropped.
*
* NOTE: you cannot set filters recursively!
*/
void setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter) override;
/**
* \see setProjectionUpdatesFilter()
*/
KisProjectionUpdatesFilterSP projectionUpdatesFilter() const override;
void refreshGraphAsync(KisNodeSP root = KisNodeSP()) override;
void refreshGraphAsync(KisNodeSP root, const QRect &rc) override;
void refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect) override;
/**
* Triggers synchronous recomposition of the projection
*/
void refreshGraph(KisNodeSP root = KisNodeSP());
void refreshGraph(KisNodeSP root, const QRect& rc, const QRect &cropRect);
void initialRefreshGraph();
/**
* Initiate a stack regeneration skipping the recalculation of the
* filthy node's projection.
*
* Works exactly as pseudoFilthy->setDirty() with the only
* exception that pseudoFilthy::updateProjection() will not be
* called. That is used by KisRecalculateTransformMaskJob to avoid
* cyclic dependencies.
*/
void requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect);
/**
* Adds a spontaneous job to the updates queue.
*
* A spontaneous job may do some trivial tasks in the background,
* like updating the outline of selection or purging unused tiles
* from the existing paint devices.
*/
void addSpontaneousJob(KisSpontaneousJob *spontaneousJob);
/**
* This method is called by the UI (*not* by the creator of the
* stroke) when it thinks the current stroke should undo its last
* action, for example, when the user presses Ctrl+Z while some
* stroke is active.
*
* If the creator of the stroke supports undoing of intermediate
* actions, it will be notified about this request and can undo
* its last action.
*/
void requestUndoDuringStroke();
/**
* This method is called by the UI (*not* by the creator of the
* stroke) when it thinks current stroke should be cancelled. If
* there is a running stroke that has already been detached from
* its creator (ended or cancelled), it will be forcefully
* cancelled and reverted. If there is an open stroke present, and
* if its creator supports cancelling, it will be notified about
* the request and the stroke will be cancelled
*/
void requestStrokeCancellation();
/**
* This method requests the last stroke executed on the image to become undone.
* If the stroke is not ended, or if all the Lod0 strokes are completed, the method
* returns UNDO_FAIL. If the last Lod0 is going to be finished soon, then UNDO_WAIT
* is returned and the caller should just wait for its completion and call global undo
* instead. UNDO_OK means one unfinished stroke has been undone.
*/
UndoResult tryUndoUnfinishedLod0Stroke();
/**
* This method is called when image or some other part of Krita
* (*not* the creator of the stroke) decides that the stroke
* should be ended. If the creator of the stroke supports it, it
* will be notified and the stroke will be cancelled
*/
void requestStrokeEnd();
/**
* Same as requestStrokeEnd() but is called by view manager when
* the current node is changed. Use to distinguish
* sigStrokeEndRequested() and
* sigStrokeEndRequestedActiveNodeFiltered() which are used by
* KisNodeJugglerCompressed
*/
void requestStrokeEndActiveNode();
private:
KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy);
KisImage& operator=(const KisImage& rhs);
void emitSizeChanged();
void resizeImageImpl(const QRect& newRect, bool cropLayers);
void rotateImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, double radians,
bool resizeImage, KisSelectionSP selection);
void shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode,
bool resizeImage, double angleX, double angleY, KisSelectionSP selection);
void safeRemoveTwoNodes(KisNodeSP node1, KisNodeSP node2);
void refreshHiddenArea(KisNodeSP rootNode, const QRect &preparedArea);
void requestProjectionUpdateImpl(KisNode *node,
const QVector &rects,
const QRect &cropRect);
friend class KisImageResizeCommand;
void setSize(const QSize& size);
friend class KisImageSetProjectionColorSpaceCommand;
void setProjectionColorSpace(const KoColorSpace * colorSpace);
friend class KisDeselectGlobalSelectionCommand;
friend class KisReselectGlobalSelectionCommand;
friend class KisSetGlobalSelectionCommand;
friend class KisImageTest;
friend class Document; // For libkis
/**
* Replaces the current global selection with globalSelection. If
* \p globalSelection is empty, removes the selection object, so that
* \ref globalSelection() will return 0 after that.
*/
void setGlobalSelection(KisSelectionSP globalSelection);
/**
* Deselects current global selection.
* \ref globalSelection() will return 0 after that.
*/
void deselectGlobalSelection();
/**
* Reselects current deselected selection
*
* \see deselectGlobalSelection()
*/
void reselectGlobalSelection();
private:
class KisImagePrivate;
KisImagePrivate * m_d;
};
#endif // KIS_IMAGE_H_
diff --git a/libs/image/kis_image_interfaces.h b/libs/image/kis_image_interfaces.h
index 79c59d9819..4dc3387ca6 100644
--- a/libs/image/kis_image_interfaces.h
+++ b/libs/image/kis_image_interfaces.h
@@ -1,77 +1,84 @@
/*
* Copyright (c) 2011 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_IMAGE_INTERFACES_H
#define __KIS_IMAGE_INTERFACES_H
#include "kis_types.h"
#include
class QRect;
class KisStrokeStrategy;
class KisStrokeJobData;
class KisPostExecutionUndoAdapter;
class KRITAIMAGE_EXPORT KisStrokesFacade
{
public:
virtual ~KisStrokesFacade();
virtual KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy) = 0;
virtual void addJob(KisStrokeId id, KisStrokeJobData *data) = 0;
virtual void endStroke(KisStrokeId id) = 0;
virtual bool cancelStroke(KisStrokeId id) = 0;
};
class KRITAIMAGE_EXPORT KisUpdatesFacade
{
public:
virtual ~KisUpdatesFacade();
virtual void blockUpdates() = 0;
virtual void unblockUpdates() = 0;
virtual void disableUIUpdates() = 0;
virtual QVector enableUIUpdates() = 0;
+ virtual void notifyBatchUpdateStarted() = 0;
+ virtual void notifyBatchUpdateEnded() = 0;
+ virtual void notifyUIUpdateCompleted(const QRect &rc) = 0;
+
+ virtual QRect bounds() const = 0;
+
virtual void disableDirtyRequests() = 0;
virtual void enableDirtyRequests() = 0;
virtual void refreshGraphAsync(KisNodeSP root) = 0;
virtual void refreshGraphAsync(KisNodeSP root, const QRect &rc) = 0;
virtual void refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect) = 0;
virtual void setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter) = 0;
virtual KisProjectionUpdatesFilterSP projectionUpdatesFilter() const = 0;
};
class KRITAIMAGE_EXPORT KisProjectionUpdateListener
{
public:
virtual ~KisProjectionUpdateListener();
virtual void notifyProjectionUpdated(const QRect &rc) = 0;
};
class KRITAIMAGE_EXPORT KisStrokeUndoFacade
{
public:
virtual ~KisStrokeUndoFacade();
virtual KisPostExecutionUndoAdapter* postExecutionUndoAdapter() const = 0;
+ virtual const KUndo2Command* lastExecutedCommand() const = 0;
};
#endif /* __KIS_IMAGE_INTERFACES_H */
diff --git a/libs/image/kis_stroke_strategy_undo_command_based.cpp b/libs/image/kis_stroke_strategy_undo_command_based.cpp
index 0b0654fad9..37560264ee 100644
--- a/libs/image/kis_stroke_strategy_undo_command_based.cpp
+++ b/libs/image/kis_stroke_strategy_undo_command_based.cpp
@@ -1,170 +1,185 @@
/*
* Copyright (c) 2011 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_stroke_strategy_undo_command_based.h"
#include
#include "kis_image_interfaces.h"
#include "kis_post_execution_undo_adapter.h"
#include "commands_new/kis_saved_commands.h"
KisStrokeStrategyUndoCommandBased::
KisStrokeStrategyUndoCommandBased(const KUndo2MagicString &name,
bool undo,
KisStrokeUndoFacade *undoFacade,
KUndo2CommandSP initCommand,
KUndo2CommandSP finishCommand)
- : KisSimpleStrokeStrategy("STROKE_UNDO_COMMAND_BASED", name),
+ : KisRunnableBasedStrokeStrategy("STROKE_UNDO_COMMAND_BASED", name),
m_undo(undo),
m_initCommand(initCommand),
m_finishCommand(finishCommand),
m_undoFacade(undoFacade),
m_macroId(-1),
m_macroCommand(0)
{
enableJob(KisSimpleStrokeStrategy::JOB_INIT);
enableJob(KisSimpleStrokeStrategy::JOB_FINISH);
enableJob(KisSimpleStrokeStrategy::JOB_CANCEL);
enableJob(KisSimpleStrokeStrategy::JOB_DOSTROKE);
}
KisStrokeStrategyUndoCommandBased::
KisStrokeStrategyUndoCommandBased(const KisStrokeStrategyUndoCommandBased &rhs)
- : KisSimpleStrokeStrategy(rhs),
+ : KisRunnableBasedStrokeStrategy(rhs),
m_undo(false),
m_initCommand(rhs.m_initCommand),
m_finishCommand(rhs.m_finishCommand),
m_undoFacade(rhs.m_undoFacade),
m_macroCommand(0)
{
KIS_ASSERT_RECOVER_NOOP(!rhs.m_macroCommand &&
!rhs.m_undo &&
"After the stroke has been started, no copying must happen");
}
void KisStrokeStrategyUndoCommandBased::setUsedWhileUndoRedo(bool value)
{
setClearsRedoOnStart(!value);
}
void KisStrokeStrategyUndoCommandBased::executeCommand(KUndo2CommandSP command, bool undo)
{
if(!command) return;
+ if (MutatedCommandInterface *mutatedCommand = dynamic_cast(command.data())) {
+ mutatedCommand->setRunnableJobsInterface(this->runnableJobsInterface());
+ }
+
if(undo) {
command->undo();
} else {
command->redo();
}
}
void KisStrokeStrategyUndoCommandBased::initStrokeCallback()
{
if(m_undoFacade) {
m_macroCommand = m_undoFacade->postExecutionUndoAdapter()->createMacro(name());
}
executeCommand(m_initCommand, m_undo);
notifyCommandDone(m_initCommand,
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::NORMAL);
}
void KisStrokeStrategyUndoCommandBased::finishStrokeCallback()
{
executeCommand(m_finishCommand, m_undo);
notifyCommandDone(m_finishCommand,
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::NORMAL);
QMutexLocker locker(&m_mutex);
if(m_macroCommand) {
Q_ASSERT(m_undoFacade);
postProcessToplevelCommand(m_macroCommand);
m_undoFacade->postExecutionUndoAdapter()->addMacro(m_macroCommand);
m_macroCommand = 0;
}
}
void KisStrokeStrategyUndoCommandBased::cancelStrokeCallback()
{
QMutexLocker locker(&m_mutex);
if(m_macroCommand) {
m_macroCommand->performCancel(cancelStrokeId(), m_undo);
delete m_macroCommand;
m_macroCommand = 0;
}
}
void KisStrokeStrategyUndoCommandBased::doStrokeCallback(KisStrokeJobData *data)
{
Data *d = dynamic_cast(data);
- executeCommand(d->command, d->undo);
- notifyCommandDone(d->command, d->sequentiality(), d->exclusivity());
+
+ if (d) {
+ executeCommand(d->command, d->undo);
+ notifyCommandDone(d->command, d->sequentiality(), d->exclusivity());
+ } else {
+ KisRunnableBasedStrokeStrategy::doStrokeCallback(data);
+ }
+
}
void KisStrokeStrategyUndoCommandBased::runAndSaveCommand(KUndo2CommandSP command,
KisStrokeJobData::Sequentiality sequentiality,
KisStrokeJobData::Exclusivity exclusivity)
{
if (!command) return;
- command->redo();
+ executeCommand(command, false);
notifyCommandDone(command, sequentiality, exclusivity);
}
void KisStrokeStrategyUndoCommandBased::notifyCommandDone(KUndo2CommandSP command,
KisStrokeJobData::Sequentiality sequentiality,
KisStrokeJobData::Exclusivity exclusivity)
{
if(!command) return;
QMutexLocker locker(&m_mutex);
if(m_macroCommand) {
m_macroCommand->addCommand(command, sequentiality, exclusivity);
}
}
void KisStrokeStrategyUndoCommandBased::setCommandExtraData(KUndo2CommandExtraData *data)
{
if (m_undoFacade && m_macroCommand) {
warnKrita << "WARNING: KisStrokeStrategyUndoCommandBased::setCommandExtraData():"
<< "the extra data is set while the stroke has already been started!"
<< "The result is undefined, continued actions may not work!";
}
m_commandExtraData.reset(data);
}
void KisStrokeStrategyUndoCommandBased::setMacroId(int value)
{
m_macroId = value;
}
void KisStrokeStrategyUndoCommandBased::postProcessToplevelCommand(KUndo2Command *command)
{
if (m_commandExtraData) {
command->setExtraData(m_commandExtraData.take());
}
KisSavedMacroCommand *savedCommand = dynamic_cast(command);
if (savedCommand) {
savedCommand->setMacroId(m_macroId);
}
}
+
+ KisStrokeUndoFacade* KisStrokeStrategyUndoCommandBased::undoFacade() const
+ {
+ return m_undoFacade;
+ }
diff --git a/libs/image/kis_stroke_strategy_undo_command_based.h b/libs/image/kis_stroke_strategy_undo_command_based.h
index 9a08fde55c..41ca43c3fd 100644
--- a/libs/image/kis_stroke_strategy_undo_command_based.h
+++ b/libs/image/kis_stroke_strategy_undo_command_based.h
@@ -1,140 +1,162 @@
/*
* Copyright (c) 2011 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_STROKE_STRATEGY_UNDO_COMMAND_BASED_H
#define __KIS_STROKE_STRATEGY_UNDO_COMMAND_BASED_H
#include
#include
#include
#include "kis_types.h"
#include "kis_simple_stroke_strategy.h"
+#include "KisRunnableBasedStrokeStrategy.h"
class KisStrokeJob;
class KisSavedMacroCommand;
class KisStrokeUndoFacade;
+class KisStrokesQueueMutatedJobInterface;
-class KRITAIMAGE_EXPORT KisStrokeStrategyUndoCommandBased : public KisSimpleStrokeStrategy
+
+class KRITAIMAGE_EXPORT KisStrokeStrategyUndoCommandBased : public KisRunnableBasedStrokeStrategy
{
public:
+ struct MutatedCommandInterface
+ {
+ virtual ~MutatedCommandInterface() {}
+
+ void setRunnableJobsInterface(KisRunnableStrokeJobsInterface *interface) {
+ m_mutatedJobsInterface = interface;
+ }
+
+ KisRunnableStrokeJobsInterface* runnableJobsInterface() const {
+ return m_mutatedJobsInterface;
+ }
+
+ private:
+ KisRunnableStrokeJobsInterface *m_mutatedJobsInterface;
+ };
+
+
class Data : public KisStrokeJobData {
public:
Data(KUndo2CommandSP _command,
bool _undo = false,
Sequentiality _sequentiality = SEQUENTIAL,
Exclusivity _exclusivity = NORMAL)
: KisStrokeJobData(_sequentiality, _exclusivity),
command(_command),
undo(_undo)
{
}
Data(KUndo2Command *_command,
bool _undo = false,
Sequentiality _sequentiality = SEQUENTIAL,
Exclusivity _exclusivity = NORMAL)
: KisStrokeJobData(_sequentiality, _exclusivity),
command(_command),
undo(_undo)
{
}
KUndo2CommandSP command;
bool undo;
};
public:
KisStrokeStrategyUndoCommandBased(const KUndo2MagicString &name,
bool undo,
KisStrokeUndoFacade *undoFacade,
KUndo2CommandSP initCommand = KUndo2CommandSP(0),
KUndo2CommandSP finishCommand = KUndo2CommandSP(0));
using KisSimpleStrokeStrategy::setExclusive;
void initStrokeCallback() override;
void finishStrokeCallback() override;
void cancelStrokeCallback() override;
void doStrokeCallback(KisStrokeJobData *data) override;
/**
* Set extra data that will be assigned to the command
* representing this action. Using extra data has the following
* restrictions:
*
* 1) The \p data must be set *before* the stroke has been started.
* Setting the \p data after the stroke has been started with
* image->startStroke(strokeId) leads to an undefined behaviour.
*
* 2) \p data becomes owned by the strategy/command right after
* setting it. Don't try to change it afterwards.
*/
void setCommandExtraData(KUndo2CommandExtraData *data);
/**
* Sets the id of this action. Will be used for merging the undo commands
*
* The \p value must be set *before* the stroke has been started.
* Setting the \p value after the stroke has been started with
* image->startStroke(strokeId) leads to an undefined behaviour.
*/
void setMacroId(int value);
/**
* The undo-command-based is a low-level strategy, so it allows
* changing its wraparound mode status.
*
* WARNING: the switch must be called *before* the stroke has been
* started! Otherwise the mode will not be activated.
*/
using KisStrokeStrategy::setSupportsWrapAroundMode;
void setUsedWhileUndoRedo(bool value);
protected:
void runAndSaveCommand(KUndo2CommandSP command,
KisStrokeJobData::Sequentiality sequentiality,
KisStrokeJobData::Exclusivity exclusivity);
void notifyCommandDone(KUndo2CommandSP command,
KisStrokeJobData::Sequentiality sequentiality,
KisStrokeJobData::Exclusivity exclusivity);
KisStrokeStrategyUndoCommandBased(const KisStrokeStrategyUndoCommandBased &rhs);
virtual void postProcessToplevelCommand(KUndo2Command *command);
+ KisStrokeUndoFacade* undoFacade() const;
+
private:
void executeCommand(KUndo2CommandSP command, bool undo);
private:
bool m_undo;
KUndo2CommandSP m_initCommand;
KUndo2CommandSP m_finishCommand;
KisStrokeUndoFacade *m_undoFacade;
QScopedPointer m_commandExtraData;
int m_macroId;
// protects done commands only
QMutex m_mutex;
KisSavedMacroCommand *m_macroCommand;
};
#endif /* __KIS_STROKE_STRATEGY_UNDO_COMMAND_BASED_H */
diff --git a/plugins/tools/tool_transform2/kis_tool_transform.cc b/plugins/tools/tool_transform2/kis_tool_transform.cc
index 5545dc4950..31e3656630 100644
--- a/plugins/tools/tool_transform2/kis_tool_transform.cc
+++ b/plugins/tools/tool_transform2/kis_tool_transform.cc
@@ -1,1322 +1,1135 @@
/*
* kis_tool_transform.cc -- part of Krita
*
* Copyright (c) 2004 Boudewijn Rempt
* Copyright (c) 2005 C. Boemann
* Copyright (c) 2010 Marc Pegon
* Copyright (c) 2013 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_tool_transform.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
"));
connect(cmbFilter, SIGNAL(activated(KoID)),
this, SLOT(slotFilterChanged(KoID)));
// Init Warp Type combo
cmbWarpType->insertItem(KisWarpTransformWorker::AFFINE_TRANSFORM,i18n("Default (Affine)"));
cmbWarpType->insertItem(KisWarpTransformWorker::RIGID_TRANSFORM,i18n("Strong (Rigid)"));
cmbWarpType->insertItem(KisWarpTransformWorker::SIMILITUDE_TRANSFORM,i18n("Strongest (Similitude)"));
cmbWarpType->setCurrentIndex(KisWarpTransformWorker::AFFINE_TRANSFORM);
connect(cmbWarpType, SIGNAL(currentIndexChanged(int)), this, SLOT(slotWarpTypeChanged(int)));
// Init Rotation Center buttons
m_handleDir[0] = QPointF(1, 0);
m_handleDir[1] = QPointF(1, -1);
m_handleDir[2] = QPointF(0, -1);
m_handleDir[3] = QPointF(-1, -1);
m_handleDir[4] = QPointF(-1, 0);
m_handleDir[5] = QPointF(-1, 1);
m_handleDir[6] = QPointF(0, 1);
m_handleDir[7] = QPointF(1, 1);
m_handleDir[8] = QPointF(0, 0); // also add the center
m_rotationCenterButtons = new QButtonGroup(0);
// we set the ids to match m_handleDir
m_rotationCenterButtons->addButton(middleRightButton, 0);
m_rotationCenterButtons->addButton(topRightButton, 1);
m_rotationCenterButtons->addButton(middleTopButton, 2);
m_rotationCenterButtons->addButton(topLeftButton, 3);
m_rotationCenterButtons->addButton(middleLeftButton, 4);
m_rotationCenterButtons->addButton(bottomLeftButton, 5);
m_rotationCenterButtons->addButton(middleBottomButton, 6);
m_rotationCenterButtons->addButton(bottomRightButton, 7);
m_rotationCenterButtons->addButton(centerButton, 8);
QToolButton *nothingSelected = new QToolButton(0);
nothingSelected->setCheckable(true);
nothingSelected->setAutoExclusive(true);
nothingSelected->hide(); // a convenient button for when no button is checked in the group
m_rotationCenterButtons->addButton(nothingSelected, 9);
// initialize values for free transform sliders
shearXBox->setSuffix(i18n(" px"));
shearYBox->setSuffix(i18n(" px"));
shearXBox->setRange(-5.0, 5.0, 2);
shearYBox->setRange(-5.0, 5.0, 2);
shearXBox->setSingleStep(0.01);
shearYBox->setSingleStep(0.01);
shearXBox->setValue(0.0);
shearYBox->setValue(0.0);
translateXBox->setSuffix(i18n(" px"));
translateYBox->setSuffix(i18n(" px"));
translateXBox->setRange(-10000, 10000);
translateYBox->setRange(-10000, 10000);
scaleXBox->setSuffix("%");
scaleYBox->setSuffix("%");
scaleXBox->setRange(-10000, 10000);
scaleYBox->setRange(-10000, 10000);
scaleXBox->setValue(100.0);
scaleYBox->setValue(100.0);
m_scaleRatio = 1.0;
aXBox->setSuffix(QChar(Qt::Key_degree));
aYBox->setSuffix(QChar(Qt::Key_degree));
aZBox->setSuffix(QChar(Qt::Key_degree));
aXBox->setRange(0.0, 360.0, 2);
aYBox->setRange(0.0, 360.0, 2);
aZBox->setRange(0.0, 360.0, 2);
aXBox->setValue(0.0);
aYBox->setValue(0.0);
aZBox->setValue(0.0);
aXBox->setSingleStep(1.0);
aYBox->setSingleStep(1.0);
aZBox->setSingleStep(1.0);
connect(m_rotationCenterButtons, SIGNAL(buttonPressed(int)), this, SLOT(slotRotationCenterChanged(int)));
connect(btnTransformAroundPivotPoint, SIGNAL(clicked(bool)), this, SLOT(slotTransformAroundRotationCenter(bool)));
// Init Free Transform Values
connect(scaleXBox, SIGNAL(valueChanged(int)), this, SLOT(slotSetScaleX(int)));
connect(scaleYBox, SIGNAL(valueChanged(int)), this, SLOT(slotSetScaleY(int)));
connect(shearXBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetShearX(qreal)));
connect(shearYBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetShearY(qreal)));
connect(translateXBox, SIGNAL(valueChanged(int)), this, SLOT(slotSetTranslateX(int)));
connect(translateYBox, SIGNAL(valueChanged(int)), this, SLOT(slotSetTranslateY(int)));
connect(aXBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetAX(qreal)));
connect(aYBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetAY(qreal)));
connect(aZBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetAZ(qreal)));
connect(aspectButton, SIGNAL(keepAspectRatioChanged(bool)), this, SLOT(slotSetKeepAspectRatio(bool)));
connect(flipXButton, SIGNAL(clicked(bool)), this, SLOT(slotFlipX()));
connect(flipYButton, SIGNAL(clicked(bool)), this, SLOT(slotFlipY()));
connect(rotateCWButton, SIGNAL(clicked(bool)), this, SLOT(slotRotateCW()));
connect(rotateCCWButton, SIGNAL(clicked(bool)), this, SLOT(slotRotateCCW()));
// toggle visibility of different free buttons
connect(freeMoveRadioButton, SIGNAL(clicked(bool)), SLOT(slotTransformAreaVisible(bool)));
connect(freeRotationRadioButton, SIGNAL(clicked(bool)), SLOT(slotTransformAreaVisible(bool)));
connect(freeScaleRadioButton, SIGNAL(clicked(bool)), SLOT(slotTransformAreaVisible(bool)));
connect(freeShearRadioButton, SIGNAL(clicked(bool)), SLOT(slotTransformAreaVisible(bool)));
// only first group for free transform
rotationGroup->hide();
moveGroup->show();
scaleGroup->hide();
shearGroup->hide();
// Init Warp Transform Values
alphaBox->setSingleStep(0.1);
alphaBox->setRange(0, 10, 1);
connect(alphaBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetWarpAlpha(qreal)));
connect(densityBox, SIGNAL(valueChanged(int)), this, SLOT(slotSetWarpDensity(int)));
connect(defaultRadioButton, SIGNAL(clicked(bool)), this, SLOT(slotWarpDefaultPointsButtonClicked(bool)));
connect(customRadioButton, SIGNAL(clicked(bool)), this, SLOT(slotWarpCustomPointsButtonClicked(bool)));
connect(lockUnlockPointsButton, SIGNAL(clicked()), this, SLOT(slotWarpLockPointsButtonClicked()));
connect(resetPointsButton, SIGNAL(clicked()), this, SLOT(slotWarpResetPointsButtonClicked()));
// Init Cage Transform Values
cageTransformButtonGroup->setId(cageAddEditRadio, 0); // we need to set manually since Qt Designer generates negative by default
cageTransformButtonGroup->setId(cageDeformRadio, 1);
connect(cageTransformButtonGroup, SIGNAL(buttonClicked(int)), this, SLOT(slotCageOptionsChanged(int)));
// Init Liquify Transform Values
liquifySizeSlider->setRange(KisLiquifyProperties::minSize(),
KisLiquifyProperties::maxSize(), 2);
liquifySizeSlider->setExponentRatio(4);
liquifySizeSlider->setValue(60.0);
connect(liquifySizeSlider, SIGNAL(valueChanged(qreal)), this, SLOT(liquifySizeChanged(qreal)));
liquifySizeSlider->setToolTip(i18nc("@info:tooltip", "Size of the deformation brush"));
liquifyAmountSlider->setRange(0.0, 1.0, 2);
liquifyAmountSlider->setValue(0.05);
connect(liquifyAmountSlider, SIGNAL(valueChanged(qreal)), this, SLOT(liquifyAmountChanged(qreal)));
liquifyAmountSlider->setToolTip(i18nc("@info:tooltip", "Amount of the deformation you get"));
liquifyFlowSlider->setRange(0.0, 1.0, 2);
liquifyFlowSlider->setValue(1.0);
connect(liquifyFlowSlider, SIGNAL(valueChanged(qreal)), this, SLOT(liquifyFlowChanged(qreal)));
liquifyFlowSlider->setToolTip(i18nc("@info:tooltip", "When in non-buildup mode, shows how fast the deformation limit is reached."));
buidupModeComboBox->setCurrentIndex(0); // set to build-up mode by default
connect(buidupModeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(liquifyBuildUpChanged(int)));
buidupModeComboBox->setToolTip(i18nc("@info:tooltip", "Switch between Build Up and Wash mode of painting. Build Up mode adds deformations one on top of the other without any limits. Wash mode gradually deforms the piece to the selected deformation level."));
liquifySpacingSlider->setRange(0.0, 3.0, 2);
liquifySizeSlider->setExponentRatio(3);
liquifySpacingSlider->setSingleStep(0.01);
liquifySpacingSlider->setValue(0.2);
connect(liquifySpacingSlider, SIGNAL(valueChanged(qreal)), this, SLOT(liquifySpacingChanged(qreal)));
liquifySpacingSlider->setToolTip(i18nc("@info:tooltip", "Space between two sequential applications of the deformation"));
liquifySizePressureBox->setChecked(true);
connect(liquifySizePressureBox, SIGNAL(toggled(bool)), this, SLOT(liquifySizePressureChanged(bool)));
liquifySizePressureBox->setToolTip(i18nc("@info:tooltip", "Scale Size value according to current stylus pressure"));
liquifyAmountPressureBox->setChecked(true);
connect(liquifyAmountPressureBox, SIGNAL(toggled(bool)), this, SLOT(liquifyAmountPressureChanged(bool)));
liquifyAmountPressureBox->setToolTip(i18nc("@info:tooltip", "Scale Amount value according to current stylus pressure"));
liquifyReverseDirectionChk->setChecked(false);
connect(liquifyReverseDirectionChk, SIGNAL(toggled(bool)), this, SLOT(liquifyReverseDirectionChanged(bool)));
liquifyReverseDirectionChk->setToolTip(i18nc("@info:tooltip", "Reverse direction of the current deformation tool"));
QSignalMapper *liquifyModeMapper = new QSignalMapper(this);
connect(liquifyMove, SIGNAL(toggled(bool)), liquifyModeMapper, SLOT(map()));
connect(liquifyScale, SIGNAL(toggled(bool)), liquifyModeMapper, SLOT(map()));
connect(liquifyRotate, SIGNAL(toggled(bool)), liquifyModeMapper, SLOT(map()));
connect(liquifyOffset, SIGNAL(toggled(bool)), liquifyModeMapper, SLOT(map()));
connect(liquifyUndo, SIGNAL(toggled(bool)), liquifyModeMapper, SLOT(map()));
liquifyModeMapper->setMapping(liquifyMove, (int)KisLiquifyProperties::MOVE);
liquifyModeMapper->setMapping(liquifyScale, (int)KisLiquifyProperties::SCALE);
liquifyModeMapper->setMapping(liquifyRotate, (int)KisLiquifyProperties::ROTATE);
liquifyModeMapper->setMapping(liquifyOffset, (int)KisLiquifyProperties::OFFSET);
liquifyModeMapper->setMapping(liquifyUndo, (int)KisLiquifyProperties::UNDO);
connect(liquifyModeMapper, SIGNAL(mapped(int)), SLOT(slotLiquifyModeChanged(int)));
liquifyMove->setToolTip(i18nc("@info:tooltip", "Move: drag the image along the brush stroke"));
liquifyScale->setToolTip(i18nc("@info:tooltip", "Scale: grow/shrink image under cursor"));
liquifyRotate->setToolTip(i18nc("@info:tooltip", "Rotate: twirl image under cursor"));
liquifyOffset->setToolTip(i18nc("@info:tooltip", "Offset: shift the image to the right of the stroke direction"));
liquifyUndo->setToolTip(i18nc("@info:tooltip", "Undo: erase actions of other tools"));
// Connect all edit boxes to the Editing Finished signal
connect(densityBox, SIGNAL(editingFinished()), this, SLOT(notifyEditingFinished()));
// Connect other widget (not having editingFinished signal) to
// the same slot. From Qt 4.6 onwards the sequence of the signal
// delivery is definite.
connect(cmbFilter, SIGNAL(activated(KoID)), this, SLOT(notifyEditingFinished()));
connect(cmbWarpType, SIGNAL(currentIndexChanged(int)), this, SLOT(notifyEditingFinished()));
connect(m_rotationCenterButtons, SIGNAL(buttonPressed(int)), this, SLOT(notifyEditingFinished()));
connect(aspectButton, SIGNAL(keepAspectRatioChanged(bool)), this, SLOT(notifyEditingFinished()));
connect(lockUnlockPointsButton, SIGNAL(clicked()), this, SLOT(notifyEditingFinished()));
connect(resetPointsButton, SIGNAL(clicked()), this, SLOT(notifyEditingFinished()));
connect(defaultRadioButton, SIGNAL(clicked(bool)), this, SLOT(notifyEditingFinished()));
connect(customRadioButton, SIGNAL(clicked(bool)), this, SLOT(notifyEditingFinished()));
// Liquify
//
// liquify brush options do not affect the image directly and are not
// saved to undo, so we don't emit notifyEditingFinished() for them
// Connect Apply/Reset buttons
connect(buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(slotButtonBoxClicked(QAbstractButton*)));
// Mode switch buttons
connect(freeTransformButton, SIGNAL(clicked(bool)), this, SLOT(slotSetFreeTransformModeButtonClicked(bool)));
connect(warpButton, SIGNAL(clicked(bool)), this, SLOT(slotSetWarpModeButtonClicked(bool)));
connect(cageButton, SIGNAL(clicked(bool)), this, SLOT(slotSetCageModeButtonClicked(bool)));
connect(perspectiveTransformButton, SIGNAL(clicked(bool)), this, SLOT(slotSetPerspectiveModeButtonClicked(bool)));
connect(liquifyButton, SIGNAL(clicked(bool)), this, SLOT(slotSetLiquifyModeButtonClicked(bool)));
// Connect Decorations switcher
connect(showDecorationsBox, SIGNAL(toggled(bool)), canvas, SLOT(updateCanvas()));
tooBigLabelWidget->hide();
connect(canvas->viewManager()->mainWindow(), SIGNAL(themeChanged()), SLOT(slotUpdateIcons()), Qt::UniqueConnection);
slotUpdateIcons();
}
void KisToolTransformConfigWidget::slotUpdateIcons()
{
freeTransformButton->setIcon(KisIconUtils::loadIcon("transform_icons_main"));
warpButton->setIcon(KisIconUtils::loadIcon("transform_icons_warp"));
cageButton->setIcon(KisIconUtils::loadIcon("transform_icons_cage"));
perspectiveTransformButton->setIcon(KisIconUtils::loadIcon("transform_icons_perspective"));
liquifyButton->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_main"));
liquifyMove->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_move"));
liquifyScale->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_resize"));
liquifyRotate->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_rotate"));
liquifyOffset->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_offset"));
liquifyUndo->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_erase"));
middleRightButton->setIcon(KisIconUtils::loadIcon("arrow-right"));
topRightButton->setIcon(KisIconUtils::loadIcon("arrow-topright"));
middleTopButton->setIcon(KisIconUtils::loadIcon("arrow-up"));
topLeftButton->setIcon(KisIconUtils::loadIcon("arrow-topleft"));
middleLeftButton->setIcon(KisIconUtils::loadIcon("arrow-left"));
bottomLeftButton->setIcon(KisIconUtils::loadIcon("arrow-downleft"));
middleBottomButton->setIcon(KisIconUtils::loadIcon("arrow-down"));
bottomRightButton->setIcon(KisIconUtils::loadIcon("arrow-downright"));
btnTransformAroundPivotPoint->setIcon(KisIconUtils::loadIcon("pivot-point"));
// pressure icons
liquifySizePressureBox->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure"));
liquifyAmountPressureBox->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure"));
}
double KisToolTransformConfigWidget::radianToDegree(double rad)
{
double piX2 = 2 * M_PI;
if (rad < 0 || rad >= piX2) {
rad = fmod(rad, piX2);
if (rad < 0) {
rad += piX2;
}
}
return (rad * 360. / piX2);
}
double KisToolTransformConfigWidget::degreeToRadian(double degree)
{
if (degree < 0. || degree >= 360.) {
degree = fmod(degree, 360.);
if (degree < 0)
degree += 360.;
}
return (degree * M_PI / 180.);
}
void KisToolTransformConfigWidget::updateLiquifyControls()
{
blockUiSlots();
ToolTransformArgs *config = m_transaction->currentConfig();
KisLiquifyProperties *props =
config->liquifyProperties();
const bool useWashMode = props->useWashMode();
liquifySizeSlider->setValue(props->size());
liquifyAmountSlider->setValue(props->amount());
liquifyFlowSlider->setValue(props->flow());
buidupModeComboBox->setCurrentIndex(useWashMode);
liquifySpacingSlider->setValue(props->spacing());
liquifySizePressureBox->setChecked(props->sizeHasPressure());
liquifyAmountPressureBox->setChecked(props->amountHasPressure());
liquifyReverseDirectionChk->setChecked(props->reverseDirection());
KisLiquifyProperties::LiquifyMode mode =
static_cast(props->mode());
bool canInverseDirection =
mode != KisLiquifyProperties::UNDO;
bool canUseWashMode = mode != KisLiquifyProperties::UNDO;
liquifyReverseDirectionChk->setEnabled(canInverseDirection);
liquifyFlowSlider->setEnabled(canUseWashMode && useWashMode);
buidupModeComboBox->setEnabled(canUseWashMode);
const qreal maxAmount = canUseWashMode ? 5.0 : 1.0;
liquifyAmountSlider->setRange(0.0, maxAmount, 2);
unblockUiSlots();
}
void KisToolTransformConfigWidget::slotLiquifyModeChanged(int value)
{
if (m_uiSlotsBlocked) return;
ToolTransformArgs *config = m_transaction->currentConfig();
KisLiquifyProperties *props =
config->liquifyProperties();
KisLiquifyProperties::LiquifyMode mode =
static_cast(value);
if (mode == props->mode()) return;
props->setMode(mode);
props->loadMode();
updateLiquifyControls();
notifyConfigChanged();
}
void KisToolTransformConfigWidget::liquifySizeChanged(qreal value)
{
if (m_uiSlotsBlocked) return;
ToolTransformArgs *config = m_transaction->currentConfig();
KisLiquifyProperties *props =
config->liquifyProperties();
props->setSize(value);
notifyConfigChanged();
}
void KisToolTransformConfigWidget::liquifyAmountChanged(qreal value)
{
if (m_uiSlotsBlocked) return;
ToolTransformArgs *config = m_transaction->currentConfig();
KisLiquifyProperties *props =
config->liquifyProperties();
props->setAmount(value);
notifyConfigChanged();
}
void KisToolTransformConfigWidget::liquifyFlowChanged(qreal value)
{
if (m_uiSlotsBlocked) return;
ToolTransformArgs *config = m_transaction->currentConfig();
KisLiquifyProperties *props =
config->liquifyProperties();
props->setFlow(value);
notifyConfigChanged();
}
void KisToolTransformConfigWidget::liquifyBuildUpChanged(int value)
{
if (m_uiSlotsBlocked) return;
ToolTransformArgs *config = m_transaction->currentConfig();
KisLiquifyProperties *props =
config->liquifyProperties();
props->setUseWashMode(value); // 0 == build up mode / 1 == wash mode
notifyConfigChanged();
// we need to enable/disable flow slider
updateLiquifyControls();
}
void KisToolTransformConfigWidget::liquifySpacingChanged(qreal value)
{
if (m_uiSlotsBlocked) return;
ToolTransformArgs *config = m_transaction->currentConfig();
KisLiquifyProperties *props =
config->liquifyProperties();
props->setSpacing(value);
notifyConfigChanged();
}
void KisToolTransformConfigWidget::liquifySizePressureChanged(bool value)
{
if (m_uiSlotsBlocked) return;
ToolTransformArgs *config = m_transaction->currentConfig();
KisLiquifyProperties *props =
config->liquifyProperties();
props->setSizeHasPressure(value);
notifyConfigChanged();
}
void KisToolTransformConfigWidget::liquifyAmountPressureChanged(bool value)
{
if (m_uiSlotsBlocked) return;
ToolTransformArgs *config = m_transaction->currentConfig();
KisLiquifyProperties *props =
config->liquifyProperties();
props->setAmountHasPressure(value);
notifyConfigChanged();
}
void KisToolTransformConfigWidget::liquifyReverseDirectionChanged(bool value)
{
if (m_uiSlotsBlocked) return;
ToolTransformArgs *config = m_transaction->currentConfig();
KisLiquifyProperties *props =
config->liquifyProperties();
props->setReverseDirection(value);
notifyConfigChanged();
}
void KisToolTransformConfigWidget::updateConfig(const ToolTransformArgs &config)
{
blockUiSlots();
if (config.mode() == ToolTransformArgs::FREE_TRANSFORM ||
config.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) {
quickTransformGroup->setEnabled(config.mode() == ToolTransformArgs::FREE_TRANSFORM);
stackedWidget->setCurrentIndex(0);
bool freeTransformIsActive = config.mode() == ToolTransformArgs::FREE_TRANSFORM;
if (freeTransformIsActive)
{
freeTransformButton->setChecked(true);
}
else
{
perspectiveTransformButton->setChecked(true);
}
aXBox->setEnabled(freeTransformIsActive);
aYBox->setEnabled(freeTransformIsActive);
aZBox->setEnabled(freeTransformIsActive);
freeRotationRadioButton->setEnabled(freeTransformIsActive);
scaleXBox->setValue(config.scaleX() * 100.);
scaleYBox->setValue(config.scaleY() * 100.);
shearXBox->setValue(config.shearX());
shearYBox->setValue(config.shearY());
const QPointF anchorPoint = config.originalCenter() + config.rotationCenterOffset();
const KisTransformUtils::MatricesPack m(config);
const QPointF anchorPointView = m.finalTransform().map(anchorPoint);
translateXBox->setValue(anchorPointView.x());
translateYBox->setValue(anchorPointView.y());
aXBox->setValue(radianToDegree(config.aX()));
aYBox->setValue(radianToDegree(config.aY()));
aZBox->setValue(radianToDegree(config.aZ()));
aspectButton->setKeepAspectRatio(config.keepAspectRatio());
cmbFilter->setCurrent(config.filterId());
QPointF pt = m_transaction->currentConfig()->rotationCenterOffset();
pt.rx() /= m_transaction->originalHalfWidth();
pt.ry() /= m_transaction->originalHalfHeight();
for (int i = 0; i < 9; i++) {
if (qFuzzyCompare(m_handleDir[i].x(), pt.x()) &&
qFuzzyCompare(m_handleDir[i].y(), pt.y())) {
m_rotationCenterButtons->button(i)->setChecked(true);
break;
}
}
btnTransformAroundPivotPoint->setChecked(config.transformAroundRotationCenter());
} else if (config.mode() == ToolTransformArgs::WARP) {
stackedWidget->setCurrentIndex(1);
warpButton->setChecked(true);
if (config.defaultPoints()) {
densityBox->setValue(std::sqrt(config.numPoints()));
}
cmbWarpType->setCurrentIndex((int)config.warpType());
defaultRadioButton->setChecked(config.defaultPoints());
customRadioButton->setChecked(!config.defaultPoints());
densityBox->setEnabled(config.defaultPoints());
customWarpWidget->setEnabled(!config.defaultPoints());
updateLockPointsButtonCaption();
} else if (config.mode() == ToolTransformArgs::CAGE) {
// default UI options
resetUIOptions();
// we need at least 3 point before we can start actively deforming
if (config.origPoints().size() >= 3)
{
cageTransformDirections->setText(i18n("Switch between editing and deforming cage"));
cageAddEditRadio->setVisible(true);
cageDeformRadio->setVisible(true);
// update to correct radio button
if (config.isEditingTransformPoints())
cageAddEditRadio->setChecked(true);
else
cageDeformRadio->setChecked(true);
changeGranularity->setCurrentIndex(log2(config.pixelPrecision()) - 2);
granularityPreview->setCurrentIndex(log2(config.previewPixelPrecision()) - 2);
}
} else if (config.mode() == ToolTransformArgs::LIQUIFY) {
stackedWidget->setCurrentIndex(3);
liquifyButton->setChecked(true);
const KisLiquifyProperties *props =
config.liquifyProperties();
switch (props->mode()) {
case KisLiquifyProperties::MOVE:
liquifyMove->setChecked(true);
break;
case KisLiquifyProperties::SCALE:
liquifyScale->setChecked(true);
break;
case KisLiquifyProperties::ROTATE:
liquifyRotate->setChecked(true);
break;
case KisLiquifyProperties::OFFSET:
liquifyOffset->setChecked(true);
break;
case KisLiquifyProperties::UNDO:
liquifyUndo->setChecked(true);
break;
case KisLiquifyProperties::N_MODES:
qFatal("Unsupported mode");
}
updateLiquifyControls();
}
unblockUiSlots();
}
void KisToolTransformConfigWidget::updateLockPointsButtonCaption()
{
ToolTransformArgs *config = m_transaction->currentConfig();
if (config->isEditingTransformPoints()) {
lockUnlockPointsButton->setText(i18n("Lock Points"));
} else {
lockUnlockPointsButton->setText(i18n("Unlock Points"));
}
}
void KisToolTransformConfigWidget::setApplyResetDisabled(bool disabled)
{
QAbstractButton *applyButton = buttonBox->button(QDialogButtonBox::Apply);
QAbstractButton *resetButton = buttonBox->button(QDialogButtonBox::Reset);
Q_ASSERT(applyButton);
Q_ASSERT(resetButton);
applyButton->setDisabled(disabled);
resetButton->setDisabled(disabled);
}
void KisToolTransformConfigWidget::resetRotationCenterButtons()
{
int checkedId = m_rotationCenterButtons->checkedId();
if (checkedId >= 0 && checkedId <= 8) {
// uncheck the current checked button
m_rotationCenterButtons->button(9)->setChecked(true);
}
}
bool KisToolTransformConfigWidget::workRecursively() const
{
return chkWorkRecursively->isChecked();
}
void KisToolTransformConfigWidget::setTooBigLabelVisible(bool value)
{
tooBigLabelWidget->setVisible(value);
}
bool KisToolTransformConfigWidget::showDecorations() const
{
return showDecorationsBox->isChecked();
}
void KisToolTransformConfigWidget::blockNotifications()
{
m_notificationsBlocked++;
}
void KisToolTransformConfigWidget::unblockNotifications()
{
m_notificationsBlocked--;
}
void KisToolTransformConfigWidget::notifyConfigChanged()
{
if (!m_notificationsBlocked) {
emit sigConfigChanged();
}
m_configChanged = true;
}
void KisToolTransformConfigWidget::blockUiSlots()
{
m_uiSlotsBlocked++;
}
void KisToolTransformConfigWidget::unblockUiSlots()
{
m_uiSlotsBlocked--;
}
void KisToolTransformConfigWidget::notifyEditingFinished()
{
if (m_uiSlotsBlocked || m_notificationsBlocked || !m_configChanged) return;
emit sigEditingFinished();
m_configChanged = false;
}
void KisToolTransformConfigWidget::resetUIOptions()
{
// reset tool states since we are done (probably can encapsulate this later)
ToolTransformArgs *config = m_transaction->currentConfig();
if (config->mode() == ToolTransformArgs::CAGE)
{
cageAddEditRadio->setVisible(false);
cageAddEditRadio->setChecked(true);
cageDeformRadio->setVisible(false);
cageTransformDirections->setText(i18n("Create 3 points on the canvas to begin"));
// ensure we are on the right options view
stackedWidget->setCurrentIndex(2);
}
}
void KisToolTransformConfigWidget::slotButtonBoxClicked(QAbstractButton *button)
{
QAbstractButton *applyButton = buttonBox->button(QDialogButtonBox::Apply);
QAbstractButton *resetButton = buttonBox->button(QDialogButtonBox::Reset);
resetUIOptions();
if (button == applyButton) {
emit sigApplyTransform();
}
else if (button == resetButton) {
emit sigResetTransform();
}
}
void KisToolTransformConfigWidget::slotSetFreeTransformModeButtonClicked(bool value)
{
if (!value) return;
lblTransformType->setText(freeTransformButton->toolTip());
ToolTransformArgs *config = m_transaction->currentConfig();
config->setMode(ToolTransformArgs::FREE_TRANSFORM);
emit sigResetTransform();
}
void KisToolTransformConfigWidget::slotSetWarpModeButtonClicked(bool value)
{
if (!value) return;
lblTransformType->setText(warpButton->toolTip());
ToolTransformArgs *config = m_transaction->currentConfig();
config->setMode(ToolTransformArgs::WARP);
config->setWarpCalculation(KisWarpTransformWorker::WarpCalculation::GRID);
emit sigResetTransform();
}
void KisToolTransformConfigWidget::slotSetCageModeButtonClicked(bool value)
{
if (!value) return;
lblTransformType->setText(cageButton->toolTip());
ToolTransformArgs *config = m_transaction->currentConfig();
config->setMode(ToolTransformArgs::CAGE);
emit sigResetTransform();
}
void KisToolTransformConfigWidget::slotSetLiquifyModeButtonClicked(bool value)
{
if (!value) return;
lblTransformType->setText(liquifyButton->toolTip());
ToolTransformArgs *config = m_transaction->currentConfig();
config->setMode(ToolTransformArgs::LIQUIFY);
emit sigResetTransform();
}
void KisToolTransformConfigWidget::slotSetPerspectiveModeButtonClicked(bool value)
{
if (!value) return;
lblTransformType->setText(perspectiveTransformButton->toolTip());
ToolTransformArgs *config = m_transaction->currentConfig();
config->setMode(ToolTransformArgs::PERSPECTIVE_4POINT);
emit sigResetTransform();
}
void KisToolTransformConfigWidget::slotFilterChanged(const KoID &filterId)
{
ToolTransformArgs *config = m_transaction->currentConfig();
config->setFilterId(filterId.id());
notifyConfigChanged();
}
void KisToolTransformConfigWidget::slotRotationCenterChanged(int index)
{
if (m_uiSlotsBlocked) return;
if (index >= 0 && index <= 8) {
ToolTransformArgs *config = m_transaction->currentConfig();
double i = m_handleDir[index].x();
double j = m_handleDir[index].y();
config->setRotationCenterOffset(QPointF(i * m_transaction->originalHalfWidth(),
j * m_transaction->originalHalfHeight()));
notifyConfigChanged();
updateConfig(*config);
}
}
void KisToolTransformConfigWidget::slotTransformAroundRotationCenter(bool value)
{
if (m_uiSlotsBlocked) return;
ToolTransformArgs *config = m_transaction->currentConfig();
config->setTransformAroundRotationCenter(value);
notifyConfigChanged();
notifyEditingFinished();
}
void KisToolTransformConfigWidget::slotSetScaleX(int value)
{
if (m_uiSlotsBlocked) return;
ToolTransformArgs *config = m_transaction->currentConfig();
{
KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config);
config->setScaleX(value / 100.);
}
if (config->keepAspectRatio()) {
blockNotifications();
int calculatedValue = int( value/ m_scaleRatio );
scaleYBox->blockSignals(true);
scaleYBox->setValue(calculatedValue);
{
KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config);
config->setScaleY(calculatedValue / 100.);
}
scaleYBox->blockSignals(false);
unblockNotifications();
}
notifyConfigChanged();
notifyEditingFinished();
}
void KisToolTransformConfigWidget::slotSetScaleY(int value)
{
if (m_uiSlotsBlocked) return;
ToolTransformArgs *config = m_transaction->currentConfig();
{
KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config);
config->setScaleY(value / 100.);
}
if (config->keepAspectRatio()) {
blockNotifications();
int calculatedValue = int(m_scaleRatio * value);
scaleXBox->blockSignals(true);
scaleXBox->setValue(calculatedValue);
{
KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config);
config->setScaleX(calculatedValue / 100.);
}
scaleXBox->blockSignals(false);
unblockNotifications();
}
notifyConfigChanged();
notifyEditingFinished();
}
void KisToolTransformConfigWidget::slotSetShearX(qreal value)
{
if (m_uiSlotsBlocked) return;
ToolTransformArgs *config = m_transaction->currentConfig();
{
KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config);
config->setShearX((double)value);
}
notifyConfigChanged();
notifyEditingFinished();
}
void KisToolTransformConfigWidget::slotSetShearY(qreal value)
{
if (m_uiSlotsBlocked) return;
ToolTransformArgs *config = m_transaction->currentConfig();
{
KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config);
config->setShearY((double)value);
}
notifyConfigChanged();
notifyEditingFinished();
}
void KisToolTransformConfigWidget::slotSetTranslateX(int value)
{
if (m_uiSlotsBlocked) return;
ToolTransformArgs *config = m_transaction->currentConfig();
const QPointF anchorPoint = config->originalCenter() + config->rotationCenterOffset();
const KisTransformUtils::MatricesPack m(*config);
const QPointF anchorPointView = m.finalTransform().map(anchorPoint);
const QPointF newAnchorPointView(value, anchorPointView.y());
config->setTransformedCenter(config->transformedCenter() + newAnchorPointView - anchorPointView);
notifyConfigChanged();
}
void KisToolTransformConfigWidget::slotSetTranslateY(int value)
{
if (m_uiSlotsBlocked) return;
ToolTransformArgs *config = m_transaction->currentConfig();
const QPointF anchorPoint = config->originalCenter() + config->rotationCenterOffset();
const KisTransformUtils::MatricesPack m(*config);
const QPointF anchorPointView = m.finalTransform().map(anchorPoint);
const QPointF newAnchorPointView(anchorPointView.x(), value);
config->setTransformedCenter(config->transformedCenter() + newAnchorPointView - anchorPointView);
notifyConfigChanged();
}
void KisToolTransformConfigWidget::slotSetAX(qreal value)
{
if (m_uiSlotsBlocked) return;
ToolTransformArgs *config = m_transaction->currentConfig();
{
KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config);
config->setAX(degreeToRadian((double)value));
}
notifyConfigChanged();
notifyEditingFinished();
}
void KisToolTransformConfigWidget::slotSetAY(qreal value)
{
if (m_uiSlotsBlocked) return;
ToolTransformArgs *config = m_transaction->currentConfig();
{
KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config);
config->setAY(degreeToRadian((double)value));
}
notifyConfigChanged();
notifyEditingFinished();
}
void KisToolTransformConfigWidget::slotSetAZ(qreal value)
{
if (m_uiSlotsBlocked) return;
ToolTransformArgs *config = m_transaction->currentConfig();
{
KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config);
config->setAZ(degreeToRadian((double)value));
}
notifyConfigChanged();
notifyEditingFinished();
}
void KisToolTransformConfigWidget::slotFlipX()
{
ToolTransformArgs *config = m_transaction->currentConfig();
{
KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config);
config->setScaleX(config->scaleX() * -1);
}
notifyConfigChanged();
notifyEditingFinished();
}
void KisToolTransformConfigWidget::slotFlipY()
{
ToolTransformArgs *config = m_transaction->currentConfig();
{
KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config);
config->setScaleY(config->scaleY() * -1);
}
notifyConfigChanged();
notifyEditingFinished();
}
void KisToolTransformConfigWidget::slotRotateCW()
{
ToolTransformArgs *config = m_transaction->currentConfig();
{
KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config);
config->setAZ(normalizeAngle(config->aZ() + M_PI_2));
}
notifyConfigChanged();
notifyEditingFinished();
}
void KisToolTransformConfigWidget::slotRotateCCW()
{
ToolTransformArgs *config = m_transaction->currentConfig();
{
KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config);
config->setAZ(normalizeAngle(config->aZ() - M_PI_2));
}
notifyConfigChanged();
notifyEditingFinished();
}
// change free transform setting we want to alter (all radio buttons call this)
void KisToolTransformConfigWidget::slotTransformAreaVisible(bool value)
{
Q_UNUSED(value);
//QCheckBox sender = (QCheckBox)(*)(QObject::sender());
QString senderName = QObject::sender()->objectName();
// only show setting with what we have selected
rotationGroup->hide();
shearGroup->hide();
scaleGroup->hide();
moveGroup->hide();
if ("freeMoveRadioButton" == senderName)
{
moveGroup->show();
}
else if ("freeShearRadioButton" == senderName)
{
shearGroup->show();
}
else if ("freeScaleRadioButton" == senderName)
{
scaleGroup->show();
}
else
{
rotationGroup->show();
}
}
void KisToolTransformConfigWidget::slotSetKeepAspectRatio(bool value)
{
if (m_uiSlotsBlocked) return;
ToolTransformArgs *config = m_transaction->currentConfig();
config->setKeepAspectRatio(value);
if (value) {
blockNotifications();
int tmpXScaleBox = scaleXBox->value();
int tmpYScaleBox = scaleYBox->value();
m_scaleRatio = (tmpXScaleBox / (double) tmpYScaleBox);
unblockNotifications();
}
notifyConfigChanged();
}
void KisToolTransformConfigWidget::slotSetWarpAlpha(qreal value)
{
if (m_uiSlotsBlocked) return;
ToolTransformArgs *config = m_transaction->currentConfig();
config->setAlpha((double)value);
notifyConfigChanged();
notifyEditingFinished();
}
void KisToolTransformConfigWidget::slotSetWarpDensity(int value)
{
if (m_uiSlotsBlocked) return;
setDefaultWarpPoints(value);
}
void KisToolTransformConfigWidget::setDefaultWarpPoints(int pointsPerLine)
{
- if (pointsPerLine < 0) {
- pointsPerLine = DEFAULT_POINTS_PER_LINE;
- }
-
- int nbPoints = pointsPerLine * pointsPerLine;
- QVector origPoints(nbPoints);
- QVector transfPoints(nbPoints);
- qreal gridSpaceX, gridSpaceY;
-
- if (nbPoints == 1) {
- //there is actually no grid
- origPoints[0] = m_transaction->originalCenterGeometric();
- transfPoints[0] = m_transaction->originalCenterGeometric();
- }
- else if (nbPoints > 1) {
- gridSpaceX = m_transaction->originalRect().width() / (pointsPerLine - 1);
- gridSpaceY = m_transaction->originalRect().height() / (pointsPerLine - 1);
- double y = m_transaction->originalRect().top();
- for (int i = 0; i < pointsPerLine; ++i) {
- double x = m_transaction->originalRect().left();
- for (int j = 0 ; j < pointsPerLine; ++j) {
- origPoints[i * pointsPerLine + j] = QPointF(x, y);
- transfPoints[i * pointsPerLine + j] = QPointF(x, y);
- x += gridSpaceX;
- }
- y += gridSpaceY;
- }
- }
-
ToolTransformArgs *config = m_transaction->currentConfig();
- config->setDefaultPoints(nbPoints > 0);
- config->setPoints(origPoints, transfPoints);
-
+ KisTransformUtils::setDefaultWarpPoints(pointsPerLine, m_transaction, config);
notifyConfigChanged();
}
void KisToolTransformConfigWidget::activateCustomWarpPoints(bool enabled)
{
ToolTransformArgs *config = m_transaction->currentConfig();
densityBox->setEnabled(!enabled);
customWarpWidget->setEnabled(enabled);
if (!enabled) {
config->setEditingTransformPoints(false);
setDefaultWarpPoints(densityBox->value());
config->setWarpCalculation(KisWarpTransformWorker::WarpCalculation::GRID);
} else {
config->setEditingTransformPoints(true);
config->setWarpCalculation(KisWarpTransformWorker::WarpCalculation::DRAW);
setDefaultWarpPoints(0);
}
updateLockPointsButtonCaption();
}
void KisToolTransformConfigWidget::slotWarpDefaultPointsButtonClicked(bool value)
{
if (m_uiSlotsBlocked) return;
activateCustomWarpPoints(!value);
}
void KisToolTransformConfigWidget::slotWarpCustomPointsButtonClicked(bool value)
{
if (m_uiSlotsBlocked) return;
activateCustomWarpPoints(value);
}
void KisToolTransformConfigWidget::slotWarpResetPointsButtonClicked()
{
if (m_uiSlotsBlocked) return;
activateCustomWarpPoints(true);
}
void KisToolTransformConfigWidget::slotWarpLockPointsButtonClicked()
{
if (m_uiSlotsBlocked) return;
ToolTransformArgs *config = m_transaction->currentConfig();
config->setEditingTransformPoints(!config->isEditingTransformPoints());
if (config->isEditingTransformPoints()) {
// reinit the transf points to their original value
ToolTransformArgs *config = m_transaction->currentConfig();
int nbPoints = config->origPoints().size();
for (int i = 0; i < nbPoints; ++i) {
config->transfPoint(i) = config->origPoint(i);
}
}
updateLockPointsButtonCaption();
notifyConfigChanged();
}
void KisToolTransformConfigWidget::slotWarpTypeChanged(int index)
{
if (m_uiSlotsBlocked) return;
ToolTransformArgs *config = m_transaction->currentConfig();
switch (index) {
case KisWarpTransformWorker::AFFINE_TRANSFORM:
case KisWarpTransformWorker::SIMILITUDE_TRANSFORM:
case KisWarpTransformWorker::RIGID_TRANSFORM:
config->setWarpType((KisWarpTransformWorker::WarpType)index);
break;
default:
config->setWarpType(KisWarpTransformWorker::RIGID_TRANSFORM);
break;
}
notifyConfigChanged();
}
void KisToolTransformConfigWidget::slotCageOptionsChanged(int value)
{
if ( value == 0)
{
slotEditCagePoints(true);
}
else
{
slotEditCagePoints(false);
}
notifyEditingFinished();
}
void KisToolTransformConfigWidget::slotEditCagePoints(bool value)
{
if (m_uiSlotsBlocked) return;
ToolTransformArgs *config = m_transaction->currentConfig();
config->refTransformedPoints() = config->origPoints();
config->setEditingTransformPoints(value);
notifyConfigChanged();
}
void KisToolTransformConfigWidget::slotGranularityChanged(QString value)
{
if (m_uiSlotsBlocked) return;
KIS_SAFE_ASSERT_RECOVER_RETURN(value.toInt() > 1);
ToolTransformArgs *config = m_transaction->currentConfig();
config->setPixelPrecision(value.toInt());
notifyConfigChanged();
}
void KisToolTransformConfigWidget::slotPreviewGranularityChanged(QString value)
{
if (m_uiSlotsBlocked) return;
KIS_SAFE_ASSERT_RECOVER_RETURN(value.toInt() > 1);
ToolTransformArgs *config = m_transaction->currentConfig();
config->setPreviewPixelPrecision(value.toInt());
notifyConfigChanged();
}
diff --git a/plugins/tools/tool_transform2/kis_transform_utils.cpp b/plugins/tools/tool_transform2/kis_transform_utils.cpp
index f860e9f3c1..1fbbd525be 100644
--- a/plugins/tools/tool_transform2/kis_transform_utils.cpp
+++ b/plugins/tools/tool_transform2/kis_transform_utils.cpp
@@ -1,397 +1,490 @@
/*
* Copyright (c) 2014 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_transform_utils.h"
#include
#include
#include
#include "tool_transform_args.h"
#include "kis_paint_device.h"
#include "kis_algebra_2d.h"
+#include "transform_transaction_properties.h"
+
+struct TransformTransactionPropertiesRegistrar {
+ TransformTransactionPropertiesRegistrar() {
+ qRegisterMetaType("TransformTransactionProperties");
+ }
+};
+static TransformTransactionPropertiesRegistrar __registrar1;
+
+struct ToolTransformArgsRegistrar {
+ ToolTransformArgsRegistrar() {
+ qRegisterMetaType("ToolTransformArgs");
+ }
+};
+static ToolTransformArgsRegistrar __registrar2;
+
+struct QPainterPathRegistrar {
+ QPainterPathRegistrar() {
+ qRegisterMetaType("QPainterPath");
+ }
+};
+static QPainterPathRegistrar __registrar3;
const int KisTransformUtils::rotationHandleVisualRadius = 12;
const int KisTransformUtils::rotationHandleRadius = 8;
const int KisTransformUtils::handleVisualRadius = 12;
const int KisTransformUtils::handleRadius = 8;
QTransform KisTransformUtils::imageToFlakeTransform(const KisCoordinatesConverter *converter)
{
return converter->imageToDocumentTransform() * converter->documentToFlakeTransform();
}
qreal KisTransformUtils::effectiveHandleGrabRadius(const KisCoordinatesConverter *converter)
{
QPointF handleRadiusPt = flakeToImage(converter, QPointF(handleRadius, handleRadius));
return (handleRadiusPt.x() > handleRadiusPt.y()) ? handleRadiusPt.x() : handleRadiusPt.y();
}
qreal KisTransformUtils::effectiveRotationHandleGrabRadius(const KisCoordinatesConverter *converter)
{
QPointF handleRadiusPt = flakeToImage(converter, QPointF(rotationHandleRadius, rotationHandleRadius));
return (handleRadiusPt.x() > handleRadiusPt.y()) ? handleRadiusPt.x() : handleRadiusPt.y();
}
qreal KisTransformUtils::scaleFromAffineMatrix(const QTransform &t) {
return KoUnit::approxTransformScale(t);
}
qreal KisTransformUtils::scaleFromPerspectiveMatrixX(const QTransform &t, const QPointF &basePt) {
const QPointF pt = basePt + QPointF(1.0, 0);
return kisDistance(t.map(pt), t.map(basePt));
}
qreal KisTransformUtils::scaleFromPerspectiveMatrixY(const QTransform &t, const QPointF &basePt) {
const QPointF pt = basePt + QPointF(0, 1.0);
return kisDistance(t.map(pt), t.map(basePt));
}
qreal KisTransformUtils::effectiveSize(const QRectF &rc) {
return 0.5 * (rc.width() + rc.height());
}
bool KisTransformUtils::thumbnailTooSmall(const QTransform &resultThumbTransform, const QRect &originalImageRect)
{
return KisAlgebra2D::minDimension(resultThumbTransform.mapRect(originalImageRect)) < 32;
}
QRectF handleRectImpl(qreal radius, const QTransform &t, const QRectF &limitingRect, const QPointF &basePoint, qreal *dOutX, qreal *dOutY) {
const qreal handlesExtraScaleX =
KisTransformUtils::scaleFromPerspectiveMatrixX(t, basePoint);
const qreal handlesExtraScaleY =
KisTransformUtils::scaleFromPerspectiveMatrixY(t, basePoint);
const qreal maxD = 0.2 * KisTransformUtils::effectiveSize(limitingRect);
const qreal dX = qMin(maxD, radius / handlesExtraScaleX);
const qreal dY = qMin(maxD, radius / handlesExtraScaleY);
QRectF handleRect(-0.5 * dX, -0.5 * dY, dX, dY);
if (dOutX) {
*dOutX = dX;
}
if (dOutY) {
*dOutY = dY;
}
return handleRect;
}
QRectF KisTransformUtils::handleRect(qreal radius, const QTransform &t, const QRectF &limitingRect, qreal *dOutX, qreal *dOutY) {
return handleRectImpl(radius, t, limitingRect, limitingRect.center(), dOutX, dOutY);
}
QRectF KisTransformUtils::handleRect(qreal radius, const QTransform &t, const QRectF &limitingRect, const QPointF &basePoint) {
return handleRectImpl(radius, t, limitingRect, basePoint, 0, 0);
}
QPointF KisTransformUtils::clipInRect(QPointF p, QRectF r)
{
QPointF center = r.center();
QPointF t = p - center;
r.translate(- center);
if (t.y() != 0) {
if (t.x() != 0) {
double slope = t.y() / t.x();
if (t.x() < r.left()) {
t.setY(r.left() * slope);
t.setX(r.left());
}
else if (t.x() > r.right()) {
t.setY(r.right() * slope);
t.setX(r.right());
}
if (t.y() < r.top()) {
t.setX(r.top() / slope);
t.setY(r.top());
}
else if (t.y() > r.bottom()) {
t.setX(r.bottom() / slope);
t.setY(r.bottom());
}
}
else {
if (t.y() < r.top())
t.setY(r.top());
else if (t.y() > r.bottom())
t.setY(r.bottom());
}
}
else {
if (t.x() < r.left())
t.setX(r.left());
else if (t.x() > r.right())
t.setX(r.right());
}
t += center;
return t;
}
KisTransformUtils::MatricesPack::MatricesPack(const ToolTransformArgs &args)
{
TS = QTransform::fromTranslate(-args.originalCenter().x(), -args.originalCenter().y());
SC = QTransform::fromScale(args.scaleX(), args.scaleY());
S.shear(0, args.shearY()); S.shear(args.shearX(), 0);
if (args.mode() == ToolTransformArgs::FREE_TRANSFORM) {
P.rotate(180. * args.aX() / M_PI, QVector3D(1, 0, 0));
P.rotate(180. * args.aY() / M_PI, QVector3D(0, 1, 0));
P.rotate(180. * args.aZ() / M_PI, QVector3D(0, 0, 1));
projectedP = P.toTransform(args.cameraPos().z());
} else if (args.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) {
projectedP = args.flattenedPerspectiveTransform();
P = QMatrix4x4(projectedP);
}
QPointF translation = args.transformedCenter();
T = QTransform::fromTranslate(translation.x(), translation.y());
}
QTransform KisTransformUtils::MatricesPack::finalTransform() const
{
return TS * SC * S * projectedP * T;
}
bool KisTransformUtils::checkImageTooBig(const QRectF &bounds, const MatricesPack &m)
{
bool imageTooBig = false;
QMatrix4x4 unprojectedMatrix = QMatrix4x4(m.T) * m.P * QMatrix4x4(m.TS * m.SC * m.S);
QVector points;
points << bounds.topLeft();
points << bounds.topRight();
points << bounds.bottomRight();
points << bounds.bottomLeft();
Q_FOREACH (const QPointF &pt, points) {
QVector4D v(pt.x(), pt.y(), 0, 1);
v = unprojectedMatrix * v;
qreal z = v.z() / v.w();
imageTooBig = z > 1024.0;
if (imageTooBig) {
break;
}
}
return imageTooBig;
}
#include
#include
#include
#include
#include
KisTransformWorker KisTransformUtils::createTransformWorker(const ToolTransformArgs &config,
KisPaintDeviceSP device,
KoUpdaterPtr updater,
QVector3D *transformedCenter /* OUT */)
{
{
KisTransformWorker t(0,
config.scaleX(), config.scaleY(),
config.shearX(), config.shearY(),
config.originalCenter().x(),
config.originalCenter().y(),
config.aZ(),
0, // set X and Y translation
0, // to null for calculation
0,
config.filter());
*transformedCenter = QVector3D(t.transform().map(config.originalCenter()));
}
QPointF translation = config.transformedCenter() - (*transformedCenter).toPointF();
KisTransformWorker transformWorker(device,
config.scaleX(), config.scaleY(),
config.shearX(), config.shearY(),
config.originalCenter().x(),
config.originalCenter().y(),
config.aZ(),
(int)(translation.x()),
(int)(translation.y()),
updater,
config.filter());
return transformWorker;
}
void KisTransformUtils::transformDevice(const ToolTransformArgs &config,
KisPaintDeviceSP device,
KisProcessingVisitor::ProgressHelper *helper)
{
if (config.mode() == ToolTransformArgs::WARP) {
KoUpdaterPtr updater = helper->updater();
KisWarpTransformWorker worker(config.warpType(),
device,
config.origPoints(),
config.transfPoints(),
config.alpha(),
updater);
worker.run();
} else if (config.mode() == ToolTransformArgs::CAGE) {
KoUpdaterPtr updater = helper->updater();
KisCageTransformWorker worker(device,
config.origPoints(),
updater,
config.pixelPrecision());
worker.prepareTransform();
worker.setTransformedCage(config.transfPoints());
worker.run();
} else if (config.mode() == ToolTransformArgs::LIQUIFY) {
KoUpdaterPtr updater = helper->updater();
//FIXME:
Q_UNUSED(updater);
config.liquifyWorker()->run(device);
} else {
QVector3D transformedCenter;
KoUpdaterPtr updater1 = helper->updater();
KoUpdaterPtr updater2 = helper->updater();
KisTransformWorker transformWorker =
createTransformWorker(config, device, updater1, &transformedCenter);
transformWorker.run();
if (config.mode() == ToolTransformArgs::FREE_TRANSFORM) {
KisPerspectiveTransformWorker perspectiveWorker(device,
config.transformedCenter(),
config.aX(),
config.aY(),
config.cameraPos().z(),
updater2);
perspectiveWorker.run();
} else if (config.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) {
QTransform T =
QTransform::fromTranslate(config.transformedCenter().x(),
config.transformedCenter().y());
KisPerspectiveTransformWorker perspectiveWorker(device,
T.inverted() * config.flattenedPerspectiveTransform() * T,
updater2);
perspectiveWorker.run();
}
}
}
QRect KisTransformUtils::needRect(const ToolTransformArgs &config,
const QRect &rc,
const QRect &srcBounds)
{
QRect result = rc;
if (config.mode() == ToolTransformArgs::WARP) {
KisWarpTransformWorker worker(config.warpType(),
0,
config.origPoints(),
config.transfPoints(),
config.alpha(),
0);
result = worker.approxNeedRect(rc, srcBounds);
} else if (config.mode() == ToolTransformArgs::CAGE) {
KisCageTransformWorker worker(0,
config.origPoints(),
0,
config.pixelPrecision());
worker.setTransformedCage(config.transfPoints());
result = worker.approxNeedRect(rc, srcBounds);
} else if (config.mode() == ToolTransformArgs::LIQUIFY) {
result = config.liquifyWorker() ?
config.liquifyWorker()->approxNeedRect(rc, srcBounds) : rc;
} else {
KIS_ASSERT_RECOVER_NOOP(0 && "this works for non-affine transformations only!");
}
return result;
}
QRect KisTransformUtils::changeRect(const ToolTransformArgs &config,
const QRect &rc)
{
QRect result = rc;
if (config.mode() == ToolTransformArgs::WARP) {
KisWarpTransformWorker worker(config.warpType(),
0,
config.origPoints(),
config.transfPoints(),
config.alpha(),
0);
result = worker.approxChangeRect(rc);
} else if (config.mode() == ToolTransformArgs::CAGE) {
KisCageTransformWorker worker(0,
config.origPoints(),
0,
config.pixelPrecision());
worker.setTransformedCage(config.transfPoints());
result = worker.approxChangeRect(rc);
} else if (config.mode() == ToolTransformArgs::LIQUIFY) {
result = config.liquifyWorker() ?
config.liquifyWorker()->approxChangeRect(rc) : rc;
} else {
KIS_ASSERT_RECOVER_NOOP(0 && "this works for non-affine transformations only!");
}
return result;
}
KisTransformUtils::AnchorHolder::AnchorHolder(bool enabled, ToolTransformArgs *config)
: m_enabled(enabled),
m_config(config)
{
if (!m_enabled) return;
m_staticPoint = m_config->originalCenter() + m_config->rotationCenterOffset();
const KisTransformUtils::MatricesPack m(*m_config);
m_oldStaticPointInView = m.finalTransform().map(m_staticPoint);
}
KisTransformUtils::AnchorHolder::~AnchorHolder() {
if (!m_enabled) return;
const KisTransformUtils::MatricesPack m(*m_config);
const QPointF newStaticPointInView = m.finalTransform().map(m_staticPoint);
const QPointF diff = m_oldStaticPointInView - newStaticPointInView;
m_config->setTransformedCenter(m_config->transformedCenter() + diff);
}
+
+void KisTransformUtils::setDefaultWarpPoints(int pointsPerLine,
+ const TransformTransactionProperties *transaction,
+ ToolTransformArgs *config)
+{
+ static const int DEFAULT_POINTS_PER_LINE = 3;
+
+ if (pointsPerLine < 0) {
+ pointsPerLine = DEFAULT_POINTS_PER_LINE;
+ }
+
+ int nbPoints = pointsPerLine * pointsPerLine;
+ QVector origPoints(nbPoints);
+ QVector transfPoints(nbPoints);
+ qreal gridSpaceX, gridSpaceY;
+
+ if (nbPoints == 1) {
+ //there is actually no grid
+ origPoints[0] = transaction->originalCenterGeometric();
+ transfPoints[0] = transaction->originalCenterGeometric();
+ }
+ else if (nbPoints > 1) {
+ gridSpaceX = transaction->originalRect().width() / (pointsPerLine - 1);
+ gridSpaceY = transaction->originalRect().height() / (pointsPerLine - 1);
+ double y = transaction->originalRect().top();
+ for (int i = 0; i < pointsPerLine; ++i) {
+ double x = transaction->originalRect().left();
+ for (int j = 0 ; j < pointsPerLine; ++j) {
+ origPoints[i * pointsPerLine + j] = QPointF(x, y);
+ transfPoints[i * pointsPerLine + j] = QPointF(x, y);
+ x += gridSpaceX;
+ }
+ y += gridSpaceY;
+ }
+ }
+
+ config->setDefaultPoints(nbPoints > 0);
+ config->setPoints(origPoints, transfPoints);
+}
+
+ToolTransformArgs KisTransformUtils::resetArgsForMode(ToolTransformArgs::TransformMode mode,
+ const QString &filterId,
+ const TransformTransactionProperties &transaction)
+{
+ ToolTransformArgs args;
+
+ args.setOriginalCenter(transaction.originalCenterGeometric());
+ args.setTransformedCenter(transaction.originalCenterGeometric());
+ args.setFilterId(filterId);
+
+ if (mode == ToolTransformArgs::FREE_TRANSFORM) {
+ args.setMode(ToolTransformArgs::FREE_TRANSFORM);
+ } else if (mode == ToolTransformArgs::WARP) {
+ args.setMode(ToolTransformArgs::WARP);
+ KisTransformUtils::setDefaultWarpPoints(-1, &transaction, &args);
+ args.setEditingTransformPoints(false);
+ } else if (mode == ToolTransformArgs::CAGE) {
+ args.setMode(ToolTransformArgs::CAGE);
+ args.setEditingTransformPoints(true);
+ } else if (mode == ToolTransformArgs::LIQUIFY) {
+ args.setMode(ToolTransformArgs::LIQUIFY);
+ const QRect srcRect = transaction.originalRect().toAlignedRect();
+ if (!srcRect.isEmpty()) {
+ args.initLiquifyTransformMode(transaction.originalRect().toAlignedRect());
+ }
+ } else if (mode == ToolTransformArgs::PERSPECTIVE_4POINT) {
+ args.setMode(ToolTransformArgs::PERSPECTIVE_4POINT);
+ }
+
+ return args;
+}
diff --git a/plugins/tools/tool_transform2/kis_transform_utils.h b/plugins/tools/tool_transform2/kis_transform_utils.h
index 1299e1cf66..652243f190 100644
--- a/plugins/tools/tool_transform2/kis_transform_utils.h
+++ b/plugins/tools/tool_transform2/kis_transform_utils.h
@@ -1,157 +1,169 @@
/*
* Copyright (c) 2014 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_TRANSFORM_UTILS_H
#define __KIS_TRANSFORM_UTILS_H
#include
#include "kis_coordinates_converter.h"
#include
#include
#include
#include
// for kisSquareDistance only
#include "kis_global.h"
+#include "tool_transform_args.h"
+
class ToolTransformArgs;
class KisTransformWorker;
+class TransformTransactionProperties;
class KisTransformUtils
{
public:
static const int rotationHandleVisualRadius;
static const int handleVisualRadius;
static const int handleRadius;
static const int rotationHandleRadius;
template
static T flakeToImage(const KisCoordinatesConverter *converter, T object) {
return converter->documentToImage(converter->flakeToDocument(object));
}
template
static T imageToFlake(const KisCoordinatesConverter *converter, T object) {
return converter->documentToFlake(converter->imageToDocument(object));
}
static QTransform imageToFlakeTransform(const KisCoordinatesConverter *converter);
static qreal effectiveHandleGrabRadius(const KisCoordinatesConverter *converter);
static qreal effectiveRotationHandleGrabRadius(const KisCoordinatesConverter *converter);
static qreal scaleFromAffineMatrix(const QTransform &t);
static qreal scaleFromPerspectiveMatrixX(const QTransform &t, const QPointF &basePt);
static qreal scaleFromPerspectiveMatrixY(const QTransform &t, const QPointF &basePt);
static qreal effectiveSize(const QRectF &rc);
static bool thumbnailTooSmall(const QTransform &resultThumbTransform, const QRect &originalImageRect);
static QRectF handleRect(qreal radius, const QTransform &t, const QRectF &limitingRect, qreal *dOutX, qreal *dOutY);
static QRectF handleRect(qreal radius, const QTransform &t, const QRectF &limitingRect, const QPointF &basePoint);
static QPointF clipInRect(QPointF p, QRectF r);
struct MatricesPack
{
MatricesPack(const ToolTransformArgs &args);
QTransform TS;
QTransform SC;
QTransform S;
QMatrix4x4 P;
QTransform projectedP;
QTransform T;
// the final transformation looks like
// transform = TS * SC * S * projectedP * T
QTransform finalTransform() const;
};
static bool checkImageTooBig(const QRectF &bounds, const MatricesPack &m);
static KisTransformWorker createTransformWorker(const ToolTransformArgs &config,
KisPaintDeviceSP device,
KoUpdaterPtr updater,
QVector3D *transformedCenter /* OUT */);
static void transformDevice(const ToolTransformArgs &config,
KisPaintDeviceSP device,
KisProcessingVisitor::ProgressHelper *helper);
static QRect needRect(const ToolTransformArgs &config,
const QRect &rc,
const QRect &srcBounds);
static QRect changeRect(const ToolTransformArgs &config,
const QRect &rc);
template
class HandleChooser {
public:
HandleChooser(const QPointF &cursorPos, Function defaultFunction)
: m_cursorPos(cursorPos),
m_minDistance(std::numeric_limits::max()),
m_function(defaultFunction)
{
}
bool addFunction(const QPointF &pt, qreal radius, Function function) {
bool result = false;
qreal distance = kisSquareDistance(pt, m_cursorPos);
if (distance < pow2(radius) && distance < m_minDistance) {
m_minDistance = distance;
m_function = function;
result = true;
}
return result;
}
Function function() const {
return m_function;
}
private:
QPointF m_cursorPos;
qreal m_minDistance;
Function m_function;
};
/**
* A special class that ensures that the view position of the anchor point of the
* transformation is unchanged during the lifetime of the object. On destruction
* of the keeper the position of the anchor point will be restored.
*/
struct AnchorHolder {
AnchorHolder(bool enabled, ToolTransformArgs *config);
~AnchorHolder();
private:
bool m_enabled;
ToolTransformArgs *m_config;
QPointF m_staticPoint;
QPointF m_oldStaticPointInView;
};
+
+ static void setDefaultWarpPoints(int pointsPerLine,
+ const TransformTransactionProperties *transaction,
+ ToolTransformArgs *config);
+
+ static ToolTransformArgs resetArgsForMode(ToolTransformArgs::TransformMode mode,
+ const QString &filterId,
+ const TransformTransactionProperties &transaction);
+
};
#endif /* __KIS_TRANSFORM_UTILS_H */
diff --git a/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp b/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp
index ee44b28e2e..fde66f74a2 100644
--- a/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp
+++ b/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp
@@ -1,428 +1,636 @@
/*
* Copyright (c) 2013 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "transform_stroke_strategy.h"
#include
#include "kundo2commandextradata.h"
#include "kis_node_progress_proxy.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include "kis_transform_mask_adapter.h"
#include "kis_transform_utils.h"
#include "kis_abstract_projection_plane.h"
#include "kis_recalculate_transform_mask_job.h"
#include "kis_projection_leaf.h"
#include "kis_modify_transform_mask_command.h"
#include "kis_sequential_iterator.h"
#include "kis_selection_mask.h"
#include "kis_image_config.h"
#include "kis_layer_utils.h"
#include
#include
-
-TransformStrokeStrategy::TransformStrokeStrategy(KisNodeSP rootNode,
- KisNodeList processedNodes,
+#include "transform_transaction_properties.h"
+#include "krita_container_utils.h"
+#include "commands_new/kis_saved_commands.h"
+#include "kis_command_ids.h"
+#include "KisRunnableStrokeJobUtils.h"
+#include "commands_new/KisHoldUIUpdatesCommand.h"
+
+
+TransformStrokeStrategy::TransformStrokeStrategy(ToolTransformArgs::TransformMode mode,
+ bool workRecursively,
+ const QString &filterId,
+ bool forceReset,
+ KisNodeSP rootNode,
KisSelectionSP selection,
- KisStrokeUndoFacade *undoFacade)
+ KisStrokeUndoFacade *undoFacade,
+ KisUpdatesFacade *updatesFacade)
: KisStrokeStrategyUndoCommandBased(kundo2_i18n("Transform"), false, undoFacade),
+ m_updatesFacade(updatesFacade),
+ m_mode(mode),
+ m_workRecursively(workRecursively),
+ m_filterId(filterId),
+ m_forceReset(forceReset),
m_selection(selection)
{
KIS_SAFE_ASSERT_RECOVER_NOOP(!selection || !dynamic_cast(rootNode.data()));
- m_savedRootNode = rootNode;
- m_savedProcessedNodes = processedNodes;
+ m_rootNode = rootNode;
+ setMacroId(KisCommandUtils::TransformToolId);
}
TransformStrokeStrategy::~TransformStrokeStrategy()
{
}
KisPaintDeviceSP TransformStrokeStrategy::createDeviceCache(KisPaintDeviceSP dev)
{
KisPaintDeviceSP cache;
if (m_selection) {
QRect srcRect = m_selection->selectedExactRect();
cache = dev->createCompositionSourceDevice();
KisPainter gc(cache);
gc.setSelection(m_selection);
gc.bitBlt(srcRect.topLeft(), dev, srcRect);
} else {
cache = dev->createCompositionSourceDevice(dev);
}
return cache;
}
bool TransformStrokeStrategy::haveDeviceInCache(KisPaintDeviceSP src)
{
QMutexLocker l(&m_devicesCacheMutex);
return m_devicesCacheHash.contains(src.data());
}
void TransformStrokeStrategy::putDeviceCache(KisPaintDeviceSP src, KisPaintDeviceSP cache)
{
QMutexLocker l(&m_devicesCacheMutex);
m_devicesCacheHash.insert(src.data(), cache);
}
KisPaintDeviceSP TransformStrokeStrategy::getDeviceCache(KisPaintDeviceSP src)
{
QMutexLocker l(&m_devicesCacheMutex);
KisPaintDeviceSP cache = m_devicesCacheHash.value(src.data());
if (!cache) {
warnKrita << "WARNING: Transform Stroke: the device is absent in cache!";
}
return cache;
}
bool TransformStrokeStrategy::checkBelongsToSelection(KisPaintDeviceSP device) const
{
return m_selection &&
(device == m_selection->pixelSelection().data() ||
device == m_selection->projection().data());
}
void TransformStrokeStrategy::doStrokeCallback(KisStrokeJobData *data)
{
TransformData *td = dynamic_cast(data);
ClearSelectionData *csd = dynamic_cast(data);
PreparePreviewData *ppd = dynamic_cast(data);
+ TransformAllData *runAllData = dynamic_cast(data);
+
+
+ if (runAllData) {
+ m_savedTransformArgs = runAllData->config;
- if (ppd) {
- KisNodeSP rootNode = m_savedRootNode;
- KisNodeList processedNodes = m_savedProcessedNodes;
+ QVector mutatedJobs;
+ Q_FOREACH (KisNodeSP node, m_processedNodes) {
+ mutatedJobs << new TransformData(TransformData::PAINT_DEVICE,
+ runAllData->config,
+ node);
+ }
+ mutatedJobs << new TransformData(TransformData::SELECTION,
+ runAllData->config,
+ m_rootNode);
+ addMutatedJobs(mutatedJobs);
+
+ } else if (ppd) {
+ KisNodeSP rootNode = m_rootNode;
+ KisNodeList processedNodes = m_processedNodes;
KisPaintDeviceSP previewDevice;
if (rootNode->childCount() || !rootNode->paintDevice()) {
if (KisTransformMask* tmask =
dynamic_cast(rootNode.data())) {
previewDevice = createDeviceCache(tmask->buildPreviewDevice());
KIS_SAFE_ASSERT_RECOVER(!m_selection) {
m_selection = 0;
}
} else if (KisGroupLayer *group = dynamic_cast(rootNode.data())) {
const QRect bounds = group->image()->bounds();
KisImageSP clonedImage = new KisImage(0,
bounds.width(),
bounds.height(),
group->colorSpace(),
"transformed_image");
KisGroupLayerSP clonedGroup = dynamic_cast(group->clone().data());
// In case the group is pass-through, it needs to be disabled for the preview,
// otherwise it will crash (no parent for a preview leaf).
// Also it needs to be done before setting the root layer for clonedImage.
// Result: preview for pass-through group is the same as for standard group
// (i.e. filter layers in the group won't affect the layer stack for a moment).
clonedGroup->setPassThroughMode(false);
clonedImage->setRootLayer(clonedGroup);
QQueue linearizedSrcNodes;
KisLayerUtils::recursiveApplyNodes(rootNode, [&linearizedSrcNodes] (KisNodeSP node) {
linearizedSrcNodes.enqueue(node);
});
KisLayerUtils::recursiveApplyNodes(KisNodeSP(clonedGroup), [&linearizedSrcNodes, processedNodes] (KisNodeSP node) {
KisNodeSP srcNode = linearizedSrcNodes.dequeue();
if (!processedNodes.contains(srcNode)) {
node->setVisible(false);
}
});
clonedImage->refreshGraph();
KisLayerUtils::forceAllDelayedNodesUpdate(clonedGroup);
clonedImage->waitForDone();
previewDevice = createDeviceCache(clonedImage->projection());
previewDevice->setDefaultBounds(group->projection()->defaultBounds());
// we delete the cloned image in GUI thread to ensure
// no signals are still pending
makeKisDeleteLaterWrapper(clonedImage)->deleteLater();
} else {
rootNode->projectionLeaf()->explicitlyRegeneratePassThroughProjection();
previewDevice = createDeviceCache(rootNode->projection());
}
} else {
KisPaintDeviceSP cacheDevice = createDeviceCache(rootNode->paintDevice());
if (dynamic_cast(rootNode.data())) {
KIS_SAFE_ASSERT_RECOVER (cacheDevice->colorSpace()->colorModelId() == GrayAColorModelID &&
cacheDevice->colorSpace()->colorDepthId() == Integer8BitsColorDepthID) {
cacheDevice->convertTo(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id()));
}
previewDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8());
const QRect srcRect = cacheDevice->exactBounds();
KisSequentialConstIterator srcIt(cacheDevice, srcRect);
KisSequentialIterator dstIt(previewDevice, srcRect);
const int pixelSize = previewDevice->colorSpace()->pixelSize();
KisImageConfig cfg(true);
KoColor pixel(cfg.selectionOverlayMaskColor(), previewDevice->colorSpace());
const qreal coeff = 1.0 / 255.0;
const qreal baseOpacity = 0.5;
while (srcIt.nextPixel() && dstIt.nextPixel()) {
qreal gray = srcIt.rawDataConst()[0];
qreal alpha = srcIt.rawDataConst()[1];
pixel.setOpacity(quint8(gray * alpha * baseOpacity * coeff));
memcpy(dstIt.rawData(), pixel.data(), pixelSize);
}
} else {
previewDevice = cacheDevice;
}
putDeviceCache(rootNode->paintDevice(), cacheDevice);
}
+ QPainterPath selectionOutline;
+ if (m_selection && m_selection->outlineCacheValid()) {
+ selectionOutline = m_selection->outlineCache();
+ } else if (previewDevice) {
+ selectionOutline.addRect(previewDevice->exactBounds());
+ }
- emit sigPreviewDeviceReady(previewDevice);
+ emit sigPreviewDeviceReady(previewDevice, selectionOutline);
} else if(td) {
- m_savedTransformArgs = td->config;
-
if (td->destination == TransformData::PAINT_DEVICE) {
QRect oldExtent = td->node->extent();
KisPaintDeviceSP device = td->node->paintDevice();
if (device && !checkBelongsToSelection(device)) {
KisPaintDeviceSP cachedPortion = getDeviceCache(device);
Q_ASSERT(cachedPortion);
KisTransaction transaction(device);
KisProcessingVisitor::ProgressHelper helper(td->node);
transformAndMergeDevice(td->config, cachedPortion,
device, &helper);
runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()),
KisStrokeJobData::CONCURRENT,
KisStrokeJobData::NORMAL);
td->node->setDirty(oldExtent | td->node->extent());
} else if (KisExternalLayer *extLayer =
dynamic_cast(td->node.data())) {
if (td->config.mode() == ToolTransformArgs::FREE_TRANSFORM ||
(td->config.mode() == ToolTransformArgs::PERSPECTIVE_4POINT &&
extLayer->supportsPerspectiveTransform())) {
QVector3D transformedCenter;
KisTransformWorker w = KisTransformUtils::createTransformWorker(td->config, 0, 0, &transformedCenter);
QTransform t = w.transform();
runAndSaveCommand(KUndo2CommandSP(extLayer->transform(t)),
KisStrokeJobData::CONCURRENT,
KisStrokeJobData::NORMAL);
}
} else if (KisTransformMask *transformMask =
dynamic_cast(td->node.data())) {
runAndSaveCommand(KUndo2CommandSP(
new KisModifyTransformMaskCommand(transformMask,
KisTransformMaskParamsInterfaceSP(
new KisTransformMaskAdapter(td->config)))),
KisStrokeJobData::CONCURRENT,
KisStrokeJobData::NORMAL);
}
} else if (m_selection) {
/**
* We use usual transaction here, because we cannot calsulate
* transformation for perspective and warp workers.
*/
KisTransaction transaction(m_selection->pixelSelection());
KisProcessingVisitor::ProgressHelper helper(td->node);
KisTransformUtils::transformDevice(td->config,
m_selection->pixelSelection(),
&helper);
runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()),
KisStrokeJobData::CONCURRENT,
KisStrokeJobData::NORMAL);
}
} else if (csd) {
KisPaintDeviceSP device = csd->node->paintDevice();
if (device && !checkBelongsToSelection(device)) {
if (!haveDeviceInCache(device)) {
putDeviceCache(device, createDeviceCache(device));
}
clearSelection(device);
/**
* Selection masks might have an overlay enabled, we should disable that
*/
if (KisSelectionMask *mask = dynamic_cast(csd->node.data())) {
KisSelectionSP selection = mask->selection();
if (selection) {
selection->setVisible(false);
m_deactivatedSelections.append(selection);
mask->setDirty();
}
}
} else if (KisExternalLayer *externalLayer = dynamic_cast(csd->node.data())) {
externalLayer->projectionLeaf()->setTemporaryHiddenFromRendering(true);
externalLayer->setDirty();
m_hiddenProjectionLeaves.append(csd->node);
} else if (KisTransformMask *transformMask =
dynamic_cast(csd->node.data())) {
runAndSaveCommand(KUndo2CommandSP(
new KisModifyTransformMaskCommand(transformMask,
KisTransformMaskParamsInterfaceSP(
new KisDumbTransformMaskParams(true)))),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::NORMAL);
}
} else {
KisStrokeStrategyUndoCommandBased::doStrokeCallback(data);
}
}
void TransformStrokeStrategy::clearSelection(KisPaintDeviceSP device)
{
KisTransaction transaction(device);
if (m_selection) {
device->clearSelection(m_selection);
} else {
QRect oldExtent = device->extent();
device->clear();
device->setDirty(oldExtent);
}
runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::NORMAL);
}
void TransformStrokeStrategy::transformAndMergeDevice(const ToolTransformArgs &config,
KisPaintDeviceSP src,
KisPaintDeviceSP dst,
KisProcessingVisitor::ProgressHelper *helper)
{
KoUpdaterPtr mergeUpdater = src != dst ? helper->updater() : 0;
KisTransformUtils::transformDevice(config, src, helper);
if (src != dst) {
QRect mergeRect = src->extent();
KisPainter painter(dst);
painter.setProgress(mergeUpdater);
painter.bitBlt(mergeRect.topLeft(), src, mergeRect);
painter.end();
}
}
struct TransformExtraData : public KUndo2CommandExtraData
{
ToolTransformArgs savedTransformArgs;
KisNodeSP rootNode;
KisNodeList transformedNodes;
+
+ KUndo2CommandExtraData* clone() const override {
+ return new TransformExtraData(*this);
+ }
};
void TransformStrokeStrategy::postProcessToplevelCommand(KUndo2Command *command)
{
TransformExtraData *data = new TransformExtraData();
data->savedTransformArgs = m_savedTransformArgs;
- data->rootNode = m_savedRootNode;
- data->transformedNodes = m_savedProcessedNodes;
+ data->rootNode = m_rootNode;
+ data->transformedNodes = m_processedNodes;
command->setExtraData(data);
+
+ KisSavedMacroCommand *macroCommand = dynamic_cast(command);
+ KIS_SAFE_ASSERT_RECOVER_NOOP(macroCommand);
+
+ if (m_overriddenCommand && macroCommand) {
+ macroCommand->setOverrideInfo(m_overriddenCommand, m_skippedWhileMergeCommands);
+ }
+
+ KisStrokeStrategyUndoCommandBased::postProcessToplevelCommand(command);
}
bool TransformStrokeStrategy::fetchArgsFromCommand(const KUndo2Command *command, ToolTransformArgs *args, KisNodeSP *rootNode, KisNodeList *transformedNodes)
{
const TransformExtraData *data = dynamic_cast(command->extraData());
if (data) {
*args = data->savedTransformArgs;
*rootNode = data->rootNode;
*transformedNodes = data->transformedNodes;
}
return bool(data);
}
+QList TransformStrokeStrategy::fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeSP root, bool recursive)
+{
+ QList result;
+
+ auto fetchFunc =
+ [&result, mode, root] (KisNodeSP node) {
+ if (node->isEditable(node == root) &&
+ (!node->inherits("KisShapeLayer") || mode == ToolTransformArgs::FREE_TRANSFORM) &&
+ !node->inherits("KisFileLayer") &&
+ (!node->inherits("KisTransformMask") || node == root)) {
+
+ result << node;
+ }
+ };
+
+ if (recursive) {
+ KisLayerUtils::recursiveApplyNodes(root, fetchFunc);
+ } else {
+ fetchFunc(root);
+ }
+
+ return result;
+}
+
+bool TransformStrokeStrategy::tryInitArgsFromNode(KisNodeSP node, ToolTransformArgs *args)
+{
+ bool result = false;
+
+ if (KisTransformMaskSP mask =
+ dynamic_cast(node.data())) {
+
+ KisTransformMaskParamsInterfaceSP savedParams =
+ mask->transformParams();
+
+ KisTransformMaskAdapter *adapter =
+ dynamic_cast(savedParams.data());
+
+ if (adapter) {
+ *args = adapter->transformArgs();
+ result = true;
+ }
+ }
+
+ return result;
+}
+
+bool TransformStrokeStrategy::tryFetchArgsFromCommandAndUndo(ToolTransformArgs *outArgs,
+ ToolTransformArgs::TransformMode mode,
+ KisNodeSP currentNode,
+ KisNodeList selectedNodes,
+ QVector *undoJobs)
+{
+ bool result = false;
+
+ const KUndo2Command *lastCommand = undoFacade()->lastExecutedCommand();
+ KisNodeSP oldRootNode;
+ KisNodeList oldTransformedNodes;
+
+ ToolTransformArgs args;
+
+ if (lastCommand &&
+ TransformStrokeStrategy::fetchArgsFromCommand(lastCommand, &args, &oldRootNode, &oldTransformedNodes) &&
+ args.mode() == mode &&
+ oldRootNode == currentNode) {
+
+ if (KritaUtils::compareListsUnordered(oldTransformedNodes, selectedNodes)) {
+ args.saveContinuedState();
+
+ *outArgs = args;
+
+ const KisSavedMacroCommand *command = dynamic_cast(lastCommand);
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(command, false);
+
+ command->getCommandExecutionJobs(undoJobs, true);
+
+ m_overriddenCommand = command;
+ Q_FOREACH (KisStrokeJobData *commonData, *undoJobs) {
+ Data *data = dynamic_cast(commonData);
+ KIS_SAFE_ASSERT_RECOVER(data) { continue; }
+
+ m_skippedWhileMergeCommands << data->command.data();
+ }
+
+
+ result = true;
+ }
+ }
+
+ return result;
+}
+
void TransformStrokeStrategy::initStrokeCallback()
{
KisStrokeStrategyUndoCommandBased::initStrokeCallback();
if (m_selection) {
m_selection->setVisible(false);
m_deactivatedSelections.append(m_selection);
}
+
+ ToolTransformArgs initialTransformArgs;
+ m_processedNodes = fetchNodesList(m_mode, m_rootNode, m_workRecursively);
+
+ bool argsAreInitialized = false;
+ QVector lastCommandUndoJobs;
+
+ if (!m_forceReset && tryFetchArgsFromCommandAndUndo(&initialTransformArgs,
+ m_mode,
+ m_rootNode,
+ m_processedNodes,
+ &lastCommandUndoJobs)) {
+ argsAreInitialized = true;
+ } else if (!m_forceReset && tryInitArgsFromNode(m_rootNode, &initialTransformArgs)) {
+ argsAreInitialized = true;
+ }
+
+ QVector extraInitJobs;
+
+ extraInitJobs << new Data(new KisHoldUIUpdatesCommand(m_updatesFacade, KisCommandUtils::FlipFlopCommand::INITIALIZING), false, KisStrokeJobData::BARRIER);
+
+ extraInitJobs << lastCommandUndoJobs;
+
+ KritaUtils::addJobSequential(extraInitJobs, [this]() {
+ /**
+ * We must ensure that the currently selected subtree
+ * has finished all its updates.
+ */
+ KisLayerUtils::forceAllDelayedNodesUpdate(m_rootNode);
+ });
+
+ KritaUtils::addJobBarrier(extraInitJobs, [this, initialTransformArgs, argsAreInitialized]() mutable {
+ QRect srcRect;
+
+ if (m_selection) {
+ srcRect = m_selection->selectedExactRect();
+ } else {
+ srcRect = QRect();
+ Q_FOREACH (KisNodeSP node, m_processedNodes) {
+ // group layers may have a projection of layers
+ // that are locked and will not be transformed
+ if (node->inherits("KisGroupLayer")) continue;
+
+ if (const KisTransformMask *mask = dynamic_cast(node.data())) {
+ srcRect |= mask->sourceDataBounds();
+ } else {
+ srcRect |= node->exactBounds();
+ }
+ }
+ }
+
+ TransformTransactionProperties transaction(srcRect, &initialTransformArgs, m_rootNode, m_processedNodes);
+ if (!argsAreInitialized) {
+ initialTransformArgs = KisTransformUtils::resetArgsForMode(m_mode, m_filterId, transaction);
+ }
+
+ emit this->sigTransactionGenerated(transaction, initialTransformArgs);
+ });
+
+ extraInitJobs << new PreparePreviewData();
+
+ Q_FOREACH (KisNodeSP node, m_processedNodes) {
+ extraInitJobs << new ClearSelectionData(node);
+ }
+
+ extraInitJobs << new Data(toQShared(new KisHoldUIUpdatesCommand(m_updatesFacade, KisCommandUtils::FlipFlopCommand::FINALIZING)), false, KisStrokeJobData::BARRIER);
+
+ addMutatedJobs(extraInitJobs);
}
void TransformStrokeStrategy::finishStrokeCallback()
{
Q_FOREACH (KisSelectionSP selection, m_deactivatedSelections) {
selection->setVisible(true);
}
Q_FOREACH (KisNodeSP node, m_hiddenProjectionLeaves) {
node->projectionLeaf()->setTemporaryHiddenFromRendering(false);
}
KisStrokeStrategyUndoCommandBased::finishStrokeCallback();
}
void TransformStrokeStrategy::cancelStrokeCallback()
{
KisStrokeStrategyUndoCommandBased::cancelStrokeCallback();
Q_FOREACH (KisSelectionSP selection, m_deactivatedSelections) {
selection->setVisible(true);
}
Q_FOREACH (KisNodeSP node, m_hiddenProjectionLeaves) {
node->projectionLeaf()->setTemporaryHiddenFromRendering(false);
}
}
diff --git a/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.h b/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.h
index 75b86e167c..9b33fe0412 100644
--- a/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.h
+++ b/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.h
@@ -1,136 +1,171 @@
/*
* Copyright (c) 2013 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __TRANSFORM_STROKE_STRATEGY_H
#define __TRANSFORM_STROKE_STRATEGY_H
#include
#include
#include
#include
#include
#include "tool_transform_args.h"
#include
#include
-
class KisPostExecutionUndoAdapter;
+class TransformTransactionProperties;
+class KisUpdatesFacade;
class TransformStrokeStrategy : public QObject, public KisStrokeStrategyUndoCommandBased
{
Q_OBJECT
public:
+ struct TransformAllData : public KisStrokeJobData {
+ TransformAllData(const ToolTransformArgs &_config)
+ : KisStrokeJobData(SEQUENTIAL, NORMAL),
+ config(_config) {}
+
+ ToolTransformArgs config;
+ };
+
+
class TransformData : public KisStrokeJobData {
public:
enum Destination {
PAINT_DEVICE,
SELECTION,
};
public:
TransformData(Destination _destination, const ToolTransformArgs &_config, KisNodeSP _node)
: KisStrokeJobData(CONCURRENT, NORMAL),
destination(_destination),
config(_config),
node(_node)
{
}
Destination destination;
ToolTransformArgs config;
KisNodeSP node;
};
class ClearSelectionData : public KisStrokeJobData {
public:
ClearSelectionData(KisNodeSP _node)
: KisStrokeJobData(SEQUENTIAL, NORMAL),
node(_node)
{
}
KisNodeSP node;
};
class PreparePreviewData : public KisStrokeJobData {
public:
PreparePreviewData()
: KisStrokeJobData(BARRIER, NORMAL)
{
}
};
public:
- TransformStrokeStrategy(KisNodeSP rootNode, KisNodeList processedNodes,
+ TransformStrokeStrategy(ToolTransformArgs::TransformMode mode,
+ bool workRecursively,
+ const QString &filterId,
+ bool forceReset,
+ KisNodeSP rootNode,
KisSelectionSP selection,
- KisStrokeUndoFacade *undoFacade);
+ KisStrokeUndoFacade *undoFacade, KisUpdatesFacade *updatesFacade);
~TransformStrokeStrategy() override;
void initStrokeCallback() override;
void finishStrokeCallback() override;
void cancelStrokeCallback() override;
void doStrokeCallback(KisStrokeJobData *data) override;
static bool fetchArgsFromCommand(const KUndo2Command *command, ToolTransformArgs *args, KisNodeSP *rootNode, KisNodeList *transformedNodes);
Q_SIGNALS:
- void sigPreviewDeviceReady(KisPaintDeviceSP device);
+ void sigTransactionGenerated(TransformTransactionProperties transaction, ToolTransformArgs args);
+ void sigPreviewDeviceReady(KisPaintDeviceSP device, const QPainterPath &selectionOutline);
protected:
void postProcessToplevelCommand(KUndo2Command *command) override;
private:
KoUpdaterPtr fetchUpdater(KisNodeSP node);
void transformAndMergeDevice(const ToolTransformArgs &config,
KisPaintDeviceSP src,
KisPaintDeviceSP dst,
KisProcessingVisitor::ProgressHelper *helper);
void transformDevice(const ToolTransformArgs &config,
KisPaintDeviceSP device,
KisProcessingVisitor::ProgressHelper *helper);
void clearSelection(KisPaintDeviceSP device);
//void transformDevice(KisPaintDeviceSP src, KisPaintDeviceSP dst, KisProcessingVisitor::ProgressHelper *helper);
bool checkBelongsToSelection(KisPaintDeviceSP device) const;
KisPaintDeviceSP createDeviceCache(KisPaintDeviceSP src);
bool haveDeviceInCache(KisPaintDeviceSP src);
void putDeviceCache(KisPaintDeviceSP src, KisPaintDeviceSP cache);
KisPaintDeviceSP getDeviceCache(KisPaintDeviceSP src);
+ QList fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeSP root, bool recursive);
+ ToolTransformArgs resetArgsForMode(ToolTransformArgs::TransformMode mode,
+ const QString &filterId,
+ const TransformTransactionProperties &transaction);
+ bool tryInitArgsFromNode(KisNodeSP node, ToolTransformArgs *args);
+ bool tryFetchArgsFromCommandAndUndo(ToolTransformArgs *args,
+ ToolTransformArgs::TransformMode mode,
+ KisNodeSP currentNode,
+ KisNodeList selectedNodes, QVector *undoJobs);
+
+
private:
+ KisUpdatesFacade *m_updatesFacade;
+ ToolTransformArgs::TransformMode m_mode;
+ bool m_workRecursively;
+ QString m_filterId;
+ bool m_forceReset;
+
KisSelectionSP m_selection;
QMutex m_devicesCacheMutex;
QHash m_devicesCacheHash;
KisTransformMaskSP writeToTransformMask;
ToolTransformArgs m_savedTransformArgs;
- KisNodeSP m_savedRootNode;
- KisNodeList m_savedProcessedNodes;
+ KisNodeSP m_rootNode;
+ KisNodeList m_processedNodes;
QList m_deactivatedSelections;
QList m_hiddenProjectionLeaves;
+
+ const KisSavedMacroCommand *m_overriddenCommand = 0;
+ QVector m_skippedWhileMergeCommands;
};
#endif /* __TRANSFORM_STROKE_STRATEGY_H */
diff --git a/plugins/tools/tool_transform2/transform_transaction_properties.h b/plugins/tools/tool_transform2/transform_transaction_properties.h
index 2005f15220..388d881d3b 100644
--- a/plugins/tools/tool_transform2/transform_transaction_properties.h
+++ b/plugins/tools/tool_transform2/transform_transaction_properties.h
@@ -1,146 +1,157 @@
/*
* Copyright (c) 2013 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __TRANSFORM_TRANSACTION_PROPERTIES_H
#define __TRANSFORM_TRANSACTION_PROPERTIES_H
#include
#include
#include "kis_node.h"
#include "kis_layer_utils.h"
#include "kis_external_layer_iface.h"
class ToolTransformArgs;
-
class TransformTransactionProperties
{
public:
TransformTransactionProperties()
{
}
TransformTransactionProperties(const QRectF &originalRect,
ToolTransformArgs *currentConfig,
KisNodeSP rootNode,
const QList &transformedNodes)
: m_originalRect(originalRect),
m_currentConfig(currentConfig),
m_rootNode(rootNode),
- m_shouldAvoidPerspectiveTransform(false),
- m_transformedNodes(transformedNodes)
+ m_transformedNodes(transformedNodes),
+ m_shouldAvoidPerspectiveTransform(false)
{
- Q_FOREACH (KisNodeSP node, m_transformedNodes) {
+ m_hasInvisibleNodes = false;
+ Q_FOREACH (KisNodeSP node, transformedNodes) {
if (KisExternalLayer *extLayer = dynamic_cast(node.data())) {
if (!extLayer->supportsPerspectiveTransform()) {
m_shouldAvoidPerspectiveTransform = true;
break;
}
}
+
+ m_hasInvisibleNodes |= !node->visible(false);
}
}
qreal originalHalfWidth() const {
return m_originalRect.width() / 2.0;
}
qreal originalHalfHeight() const {
return m_originalRect.height() / 2.0;
}
QRectF originalRect() const {
return m_originalRect;
}
QPointF originalCenterGeometric() const {
return m_originalRect.center();
}
QPointF originalTopLeft() const {
return m_originalRect.topLeft();
}
QPointF originalBottomLeft() const {
return m_originalRect.bottomLeft();
}
QPointF originalBottomRight() const {
return m_originalRect.bottomRight();
}
QPointF originalTopRight() const {
return m_originalRect.topRight();
}
QPointF originalMiddleLeft() const {
return QPointF(m_originalRect.left(), (m_originalRect.top() + m_originalRect.bottom()) / 2.0);
}
QPointF originalMiddleRight() const {
return QPointF(m_originalRect.right(), (m_originalRect.top() + m_originalRect.bottom()) / 2.0);
}
QPointF originalMiddleTop() const {
return QPointF((m_originalRect.left() + m_originalRect.right()) / 2.0, m_originalRect.top());
}
QPointF originalMiddleBottom() const {
return QPointF((m_originalRect.left() + m_originalRect.right()) / 2.0, m_originalRect.bottom());
}
QPoint originalTopLeftAligned() const {
return m_originalRect.toAlignedRect().topLeft();
}
QPoint originalBottomRightAligned() const {
return m_originalRect.toAlignedRect().bottomRight();
}
ToolTransformArgs* currentConfig() const {
return m_currentConfig;
}
KisNodeSP rootNode() const {
return m_rootNode;
}
+ KisNodeList transformedNodes() const {
+ return m_transformedNodes;
+ }
+
qreal basePreviewOpacity() const {
return 0.9 * qreal(m_rootNode->opacity()) / 255.0;
}
bool shouldAvoidPerspectiveTransform() const {
return m_shouldAvoidPerspectiveTransform;
}
- QList nodesList() const {
- return m_transformedNodes;
+ bool hasInvisibleNodes() const {
+ return m_hasInvisibleNodes;
+ }
+
+ void setCurrentConfigLocation(ToolTransformArgs *config) {
+ m_currentConfig = config;
}
private:
/**
* Information about the original selected rect
* (before any transformations)
*/
QRectF m_originalRect;
ToolTransformArgs *m_currentConfig;
KisNodeSP m_rootNode;
+ KisNodeList m_transformedNodes;
bool m_shouldAvoidPerspectiveTransform;
- QList m_transformedNodes;
+ bool m_hasInvisibleNodes;
};
#endif /* __TRANSFORM_TRANSACTION_PROPERTIES_H */