diff --git a/libs/image/CMakeLists.txt b/libs/image/CMakeLists.txt index 06d757f161..cf0ebe3a36 100644 --- a/libs/image/CMakeLists.txt +++ b/libs/image/CMakeLists.txt @@ -1,379 +1,380 @@ add_subdirectory( tests ) add_subdirectory( tiles3 ) include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/metadata ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty ${CMAKE_CURRENT_SOURCE_DIR}/brushengine ${CMAKE_CURRENT_SOURCE_DIR}/commands ${CMAKE_CURRENT_SOURCE_DIR}/commands_new ${CMAKE_CURRENT_SOURCE_DIR}/filter ${CMAKE_CURRENT_SOURCE_DIR}/floodfill ${CMAKE_CURRENT_SOURCE_DIR}/generator ${CMAKE_CURRENT_SOURCE_DIR}/layerstyles ${CMAKE_CURRENT_SOURCE_DIR}/processing ${CMAKE_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_new/kis_saved_commands.cpp commands_new/kis_processing_command.cpp commands_new/kis_image_resize_command.cpp commands_new/kis_image_set_resolution_command.cpp commands_new/kis_node_move_command2.cpp commands_new/kis_set_layer_style_command.cpp commands_new/kis_selection_move_command2.cpp commands_new/kis_update_command.cpp commands_new/kis_switch_current_time_command.cpp commands_new/kis_change_projection_color_command.cpp commands_new/kis_activate_selection_mask_command.cpp processing/kis_do_nothing_processing_visitor.cpp processing/kis_simple_processing_visitor.cpp processing/kis_crop_processing_visitor.cpp processing/kis_crop_selections_processing_visitor.cpp processing/kis_transform_processing_visitor.cpp processing/kis_mirror_processing_visitor.cpp filter/kis_filter.cc filter/kis_filter_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 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 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 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 metadata/kis_meta_data_entry.cc metadata/kis_meta_data_filter.cc metadata/kis_meta_data_filter_p.cc metadata/kis_meta_data_filter_registry.cc metadata/kis_meta_data_filter_registry_model.cc metadata/kis_meta_data_io_backend.cc metadata/kis_meta_data_merge_strategy.cc metadata/kis_meta_data_merge_strategy_p.cc metadata/kis_meta_data_merge_strategy_registry.cc metadata/kis_meta_data_parser.cc metadata/kis_meta_data_schema.cc metadata/kis_meta_data_schema_registry.cc metadata/kis_meta_data_store.cc metadata/kis_meta_data_type_info.cc metadata/kis_meta_data_validator.cc metadata/kis_meta_data_value.cc 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_animation_cycle.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 Qt5::Concurrent ) target_link_libraries(kritaimage PUBLIC ${Boost_SYSTEM_LIBRARY}) if(OPENEXR_FOUND) target_link_libraries(kritaimage PUBLIC ${OPENEXR_LIBRARIES}) endif() if(FFTW3_FOUND) target_link_libraries(kritaimage PRIVATE ${FFTW3_LIBRARIES}) endif() if(HAVE_VC) target_link_libraries(kritaimage PUBLIC ${Vc_LIBRARIES}) endif() if (NOT GSL_FOUND) message (WARNING "KRITA WARNING! No GNU Scientific Library was found! Krita's Shaped Gradients might be non-normalized! Please install GSL library.") else () target_link_libraries(kritaimage PRIVATE ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) endif () target_include_directories(kritaimage PUBLIC $ $ $ $ $ $ ) set_target_properties(kritaimage PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaimage ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/libs/image/kis_animation_cycle.cpp b/libs/image/kis_animation_cycle.cpp new file mode 100644 index 0000000000..69e00fe9d6 --- /dev/null +++ b/libs/image/kis_animation_cycle.cpp @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2018 Jouni Pentikäinen + * + * 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_animation_cycle.h" +#include "kis_time_range.h" +#include "kis_keyframe_channel.h" + +KisAnimationCycle::KisAnimationCycle(KisKeyframeSP firstKeyframe, KisKeyframeSP lastKeyframe) + : m_firstSourceKeyframe(firstKeyframe) + , m_lastSourceKeyframe(lastKeyframe) +{} + +KisKeyframeSP KisAnimationCycle::firstSourceKeyframe() const +{ + return m_firstSourceKeyframe; +} + +KisKeyframeSP KisAnimationCycle::lastSourceKeyframe() const +{ + return m_lastSourceKeyframe; +} + +KisTimeSpan KisAnimationCycle::originalRange() const +{ + const KisKeyframeSP firstAfterCycle = m_lastSourceKeyframe->channel()->nextKeyframe(m_lastSourceKeyframe); + + KisTimeSpan range; + if (firstAfterCycle.isNull()) { + // TODO: semantics of repeat definition without a terminating keyframe? + range = KisTimeSpan(m_firstSourceKeyframe->time(), m_lastSourceKeyframe->time()); + } else { + range = KisTimeSpan(m_firstSourceKeyframe->time(), firstAfterCycle->time() - 1); + } + return range; +} + +int KisAnimationCycle::duration() const +{ + return originalRange().duration(); +} + +void KisAnimationCycle::addRepeat(QSharedPointer repeat) +{ + m_repeats.append(repeat); +} + +void KisAnimationCycle::removeRepeat(QSharedPointer repeat) +{ + m_repeats.removeAll(repeat); +} + +const QVector>& KisAnimationCycle::repeats() const +{ + return m_repeats; +} + +KisFrameSet KisAnimationCycle::instancesWithin(KisKeyframeSP original, KisTimeSpan range) const +{ + KisFrameSet frames; + + const int originalTime = original->time(); + const KisKeyframeSP next = original->channel()->nextKeyframe(original); + const int frameDuration = (next.isNull()) ? -1 : next->time() - originalTime; + const int interval = duration(); + + int infiniteFrom = frameDuration == -1 ? originalTime : -1; + + QVector spans; + Q_FOREACH(const QWeakPointer repeatFrame, m_repeats) { + auto repeat = repeatFrame.toStrongRef(); + if (!repeat) continue; + + KisKeyframeSP terminatingKey = repeat->channel()->nextKeyframe(*repeat); + + if (range.isEmpty()) { + if (terminatingKey) { + range = KisTimeSpan(0, terminatingKey->time() -1); + } else { + infiniteFrom = repeat->firstInstanceOf(originalTime); + continue; + } + } + + KisTimeSpan repeatRange = terminatingKey ? range.truncateLeft(terminatingKey->time() - 1) : range; + int firstInstance = repeat->firstInstanceOf(originalTime); + if (firstInstance < repeatRange.start()) firstInstance += (range.start() - firstInstance) / interval * interval; + + for (int repeatTime = firstInstance; repeatTime <= repeatRange.end(); repeatTime += interval) { + const int repeatEndTime = (frameDuration != -1 && repeatTime + frameDuration - 1 <= repeatRange.end()) ? + repeatTime + frameDuration - 1 : repeatRange.end(); + spans.append(KisTimeSpan(repeatTime, repeatEndTime)); + } + } + + frames |= KisFrameSet(spans); + + if (infiniteFrom != -1) { + frames |= KisFrameSet::infiniteFrom(infiniteFrom); + } else { + frames |= KisFrameSet::between(originalTime, originalTime + frameDuration - 1); + } + + return frames; +} + + +KisKeyframeSP KisRepeatFrame::cloneFor(KisKeyframeChannel*) const +{ + return KisKeyframeSP(); // TODO +} + +bool KisRepeatFrame::isRepeat(KisKeyframeSP keyframe) +{ + return dynamic_cast(keyframe.data()) != nullptr; +} + +KisRepeatFrame::KisRepeatFrame(KisKeyframeChannel *channel, int time, QSharedPointer cycle) + : KisKeyframe(channel, time) + , m_cycle(cycle) +{} + +QSharedPointer KisRepeatFrame::cycle() const +{ + return m_cycle; +} + +QRect KisRepeatFrame::affectedRect() const +{ + KisKeyframeSP keyframe = m_cycle->firstSourceKeyframe(); + + QRect rect; + while (!keyframe.isNull() && keyframe->time() <= m_cycle->lastSourceKeyframe()->time()) { + rect |= keyframe->affectedRect(); + keyframe = channel()->nextKeyframe(keyframe); + } + + return rect; +} + +int KisRepeatFrame::getOriginalTimeFor(int time) const +{ + KisTimeSpan originalRange = m_cycle->originalRange(); + int timeWithinCycle = (time - this->time()) % originalRange.duration(); + return m_cycle->firstSourceKeyframe()->time() + timeWithinCycle; +} + +KisKeyframeSP KisRepeatFrame::getOriginalKeyframeFor(int time) const +{ + return channel()->activeKeyframeAt(getOriginalTimeFor(time)); +} + +int KisRepeatFrame::firstInstanceOf(int originalTime) const +{ + KisTimeSpan originalRange = m_cycle->originalRange(); + const int timeWithinCycle = originalTime - originalRange.start(); + + const int first = this->time() + timeWithinCycle; + + const KisKeyframeSP next = channel()->nextKeyframe(*this); + if (next && next->time() < first) return -1; + + return first; +} diff --git a/libs/image/kis_animation_cycle.h b/libs/image/kis_animation_cycle.h new file mode 100644 index 0000000000..a48d28c145 --- /dev/null +++ b/libs/image/kis_animation_cycle.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2018 Jouni Pentikäinen + * + * 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_ANIMATION_CYCLE_H +#define KIS_ANIMATION_CYCLE_H + +#include "kis_keyframe.h" + +class KisTimeSpan; +class KisFrameSet; +class KisRepeatFrame; + +class KRITAIMAGE_EXPORT KisAnimationCycle { + +public: + KisAnimationCycle(KisKeyframeSP firstKeyframe, KisKeyframeSP lastKeyframe); + + KisKeyframeSP firstSourceKeyframe() const; + KisKeyframeSP lastSourceKeyframe() const; + KisTimeSpan originalRange() const; + int duration() const; + + void addRepeat(QSharedPointer repeat); + void removeRepeat(QSharedPointer repeat); + const QVector>& repeats() const; + + KisFrameSet instancesWithin(KisKeyframeSP original, KisTimeSpan range) const; + +private: + KisKeyframeSP m_firstSourceKeyframe, m_lastSourceKeyframe; + QVector> m_repeats; +}; + +class KRITAIMAGE_EXPORT KisRepeatFrame : public KisKeyframe +{ +public: + KisRepeatFrame(KisKeyframeChannel *channel, int time, QSharedPointer cycle); + + QSharedPointer cycle() const; + QRect affectedRect() const override; + KisKeyframeSP cloneFor(KisKeyframeChannel *channel) const override; + int getOriginalTimeFor(int time) const; + KisKeyframeSP getOriginalKeyframeFor(int time) const; + + /// Returns the earliest time the original frame appears in this repeat, or -1 if it never does. + int firstInstanceOf(int originalTime) const; + + static bool isRepeat(KisKeyframeSP keyframe); + +private: + QSharedPointer m_cycle; +}; + +#endif diff --git a/libs/image/kis_keyframe_channel.cpp b/libs/image/kis_keyframe_channel.cpp index 51078e9b93..25a90f4ed3 100644 --- a/libs/image/kis_keyframe_channel.cpp +++ b/libs/image/kis_keyframe_channel.cpp @@ -1,663 +1,799 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * 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_keyframe_channel.h" #include "KoID.h" #include "kis_global.h" #include "kis_node.h" #include "kis_time_range.h" #include "kundo2command.h" #include "kis_keyframe_commands.h" +#include "kis_animation_cycle.h" #include const KoID KisKeyframeChannel::Content = KoID("content", ki18n("Content")); const KoID KisKeyframeChannel::Opacity = KoID("opacity", ki18n("Opacity")); const KoID KisKeyframeChannel::TransformArguments = KoID("transform_arguments", ki18n("Transform")); const KoID KisKeyframeChannel::TransformPositionX = KoID("transform_pos_x", ki18n("Position (X)")); const KoID KisKeyframeChannel::TransformPositionY = KoID("transform_pos_y", ki18n("Position (Y)")); const KoID KisKeyframeChannel::TransformScaleX = KoID("transform_scale_x", ki18n("Scale (X)")); const KoID KisKeyframeChannel::TransformScaleY = KoID("transform_scale_y", ki18n("Scale (Y)")); const KoID KisKeyframeChannel::TransformShearX = KoID("transform_shear_x", ki18n("Shear (X)")); const KoID KisKeyframeChannel::TransformShearY = KoID("transform_shear_y", ki18n("Shear (Y)")); const KoID KisKeyframeChannel::TransformRotationX = KoID("transform_rotation_x", ki18n("Rotation (X)")); const KoID KisKeyframeChannel::TransformRotationY = KoID("transform_rotation_y", ki18n("Rotation (Y)")); const KoID KisKeyframeChannel::TransformRotationZ = KoID("transform_rotation_z", ki18n("Rotation (Z)")); struct KisKeyframeChannel::Private { Private() {} Private(const Private &rhs, KisNodeWSP newParentNode) { node = newParentNode; id = rhs.id; defaultBounds = rhs.defaultBounds; haveBrokenFrameTimeBug = rhs.haveBrokenFrameTimeBug; } KeyframesMap keys; + QMap> cycles; KisNodeWSP node; KoID id; KisDefaultBoundsBaseSP defaultBounds; bool haveBrokenFrameTimeBug = false; + + void addKeyframe(KisKeyframeSP keyframe) { + keys.insert(keyframe->time(), keyframe); + + auto repeat = keyframe.dynamicCast(); + if (repeat) { + QSharedPointer cycle = repeat->cycle(); + cycles.insert(keyframe->time(), cycle); + cycle->addRepeat(repeat); + } + } + + void removeKeyframe(KisKeyframeSP keyframe) { + keys.remove(keyframe->time()); + + auto repeat = keyframe.dynamicCast(); + if (repeat) { + cycles.remove(keyframe->time()); + repeat->cycle()->removeRepeat(repeat); + } + } + + void moveKeyframe(KisKeyframeSP keyframe, int newTime) { + removeKeyframe(keyframe); + keyframe->setTime(newTime); + addKeyframe(keyframe); + } }; KisKeyframeChannel::KisKeyframeChannel(const KoID &id, KisDefaultBoundsBaseSP defaultBounds) : m_d(new Private) { m_d->id = id; m_d->node = 0; m_d->defaultBounds = defaultBounds; } KisKeyframeChannel::KisKeyframeChannel(const KisKeyframeChannel &rhs, KisNode *newParentNode) : m_d(new Private(*rhs.m_d, newParentNode)) { KIS_ASSERT_RECOVER_NOOP(&rhs != this); Q_FOREACH(KisKeyframeSP keyframe, rhs.m_d->keys) { - m_d->keys.insert(keyframe->time(), keyframe->cloneFor(this)); + m_d->addKeyframe(keyframe->cloneFor(this)); } } KisKeyframeChannel::~KisKeyframeChannel() {} QString KisKeyframeChannel::id() const { return m_d->id.id(); } QString KisKeyframeChannel::name() const { return m_d->id.name(); } void KisKeyframeChannel::setNode(KisNodeWSP node) { m_d->node = node; } KisNodeWSP KisKeyframeChannel::node() const { return m_d->node; } int KisKeyframeChannel::keyframeCount() const { return m_d->keys.count(); } KisKeyframeChannel::KeyframesMap& KisKeyframeChannel::keys() { return m_d->keys; } const KisKeyframeChannel::KeyframesMap& KisKeyframeChannel::constKeys() const { return m_d->keys; } #define LAZY_INITIALIZE_PARENT_COMMAND(cmd) \ QScopedPointer __tempCommand; \ if (!parentCommand) { \ __tempCommand.reset(new KUndo2Command()); \ cmd = __tempCommand.data(); \ } KisKeyframeSP KisKeyframeChannel::addKeyframe(int time, KUndo2Command *parentCommand) { LAZY_INITIALIZE_PARENT_COMMAND(parentCommand); return insertKeyframe(time, KisKeyframeSP(), parentCommand); } KisKeyframeSP KisKeyframeChannel::copyKeyframe(const KisKeyframeSP keyframe, int newTime, KUndo2Command *parentCommand) { LAZY_INITIALIZE_PARENT_COMMAND(parentCommand); return insertKeyframe(newTime, keyframe, parentCommand); } KisKeyframeSP KisKeyframeChannel::linkKeyframe(const KisKeyframeSP, int, KUndo2Command*) { return KisKeyframeSP(); } KisKeyframeSP KisKeyframeChannel::insertKeyframe(int time, const KisKeyframeSP copySrc, KUndo2Command *parentCommand) { KisKeyframeSP keyframe = keyframeAt(time); if (keyframe) { deleteKeyframeImpl(keyframe, parentCommand, false); } Q_ASSERT(parentCommand); keyframe = createKeyframe(time, copySrc, parentCommand); KUndo2Command *cmd = new KisReplaceKeyframeCommand(this, keyframe->time(), keyframe, parentCommand); cmd->redo(); return keyframe; } bool KisKeyframeChannel::deleteKeyframe(KisKeyframeSP keyframe, KUndo2Command *parentCommand) { return deleteKeyframeImpl(keyframe, parentCommand, true); } bool KisKeyframeChannel::moveKeyframe(KisKeyframeSP keyframe, int newTime, KUndo2Command *parentCommand) { LAZY_INITIALIZE_PARENT_COMMAND(parentCommand); if (newTime == keyframe->time()) return false; KisKeyframeSP other = keyframeAt(newTime); if (other) { deleteKeyframeImpl(other, parentCommand, false); } const int srcTime = keyframe->time(); KUndo2Command *cmd = new KisMoveFrameCommand(this, keyframe, srcTime, newTime, parentCommand); cmd->redo(); if (srcTime == 0) { addKeyframe(srcTime, parentCommand); } return true; } bool KisKeyframeChannel::swapFrames(int lhsTime, int rhsTime, KUndo2Command *parentCommand) { LAZY_INITIALIZE_PARENT_COMMAND(parentCommand); if (lhsTime == rhsTime) return false; KisKeyframeSP lhsFrame = keyframeAt(lhsTime); KisKeyframeSP rhsFrame = keyframeAt(rhsTime); if (!lhsFrame && !rhsFrame) return false; if (lhsFrame && !rhsFrame) { moveKeyframe(lhsFrame, rhsTime, parentCommand); } else if (!lhsFrame && rhsFrame) { moveKeyframe(rhsFrame, lhsTime, parentCommand); } else { KUndo2Command *cmd = new KisSwapFramesCommand(this, lhsFrame, rhsFrame, parentCommand); cmd->redo(); } return true; } bool KisKeyframeChannel::deleteKeyframeImpl(KisKeyframeSP keyframe, KUndo2Command *parentCommand, bool recreate) { LAZY_INITIALIZE_PARENT_COMMAND(parentCommand); Q_ASSERT(parentCommand); KUndo2Command *cmd = new KisReplaceKeyframeCommand(this, keyframe->time(), KisKeyframeSP(), parentCommand); cmd->redo(); destroyKeyframe(keyframe, parentCommand); if (recreate && keyframe->time() == 0) { addKeyframe(0, parentCommand); } return true; } void KisKeyframeChannel::moveKeyframeImpl(KisKeyframeSP keyframe, int newTime) { KIS_ASSERT_RECOVER_RETURN(keyframe); KIS_ASSERT_RECOVER_RETURN(!keyframeAt(newTime)); KisFrameSet rangeSrc = affectedFrames(keyframe->time()); QRect rectSrc = keyframe->affectedRect(); emit sigKeyframeAboutToBeMoved(keyframe, newTime); - m_d->keys.remove(keyframe->time()); int oldTime = keyframe->time(); + m_d->moveKeyframe(keyframe, newTime); keyframe->setTime(newTime); - m_d->keys.insert(newTime, keyframe); emit sigKeyframeMoved(keyframe, oldTime); KisFrameSet rangeDst = affectedFrames(keyframe->time()); QRect rectDst = keyframe->affectedRect(); requestUpdate(rangeSrc, rectSrc); requestUpdate(rangeDst, rectDst); } void KisKeyframeChannel::swapKeyframesImpl(KisKeyframeSP lhsKeyframe, KisKeyframeSP rhsKeyframe) { KIS_ASSERT_RECOVER_RETURN(lhsKeyframe); KIS_ASSERT_RECOVER_RETURN(rhsKeyframe); KisFrameSet rangeLhs = affectedFrames(lhsKeyframe->time()); KisFrameSet rangeRhs = affectedFrames(rhsKeyframe->time()); const QRect rectLhsSrc = lhsKeyframe->affectedRect(); const QRect rectRhsSrc = rhsKeyframe->affectedRect(); const int lhsTime = lhsKeyframe->time(); const int rhsTime = rhsKeyframe->time(); emit sigKeyframeAboutToBeMoved(lhsKeyframe, rhsTime); emit sigKeyframeAboutToBeMoved(rhsKeyframe, lhsTime); - m_d->keys.remove(lhsTime); - m_d->keys.remove(rhsTime); + m_d->removeKeyframe(lhsKeyframe); + m_d->removeKeyframe(rhsKeyframe); rhsKeyframe->setTime(lhsTime); lhsKeyframe->setTime(rhsTime); - m_d->keys.insert(lhsTime, rhsKeyframe); - m_d->keys.insert(rhsTime, lhsKeyframe); + m_d->addKeyframe(lhsKeyframe); + m_d->addKeyframe(rhsKeyframe); emit sigKeyframeMoved(lhsKeyframe, lhsTime); emit sigKeyframeMoved(rhsKeyframe, rhsTime); const QRect rectLhsDst = lhsKeyframe->affectedRect(); const QRect rectRhsDst = rhsKeyframe->affectedRect(); requestUpdate(rangeLhs, rectLhsSrc | rectRhsDst); requestUpdate(rangeRhs, rectRhsSrc | rectLhsDst); } KisKeyframeSP KisKeyframeChannel::replaceKeyframeAt(int time, KisKeyframeSP newKeyframe) { Q_ASSERT(newKeyframe.isNull() || time == newKeyframe->time()); KisKeyframeSP existingKeyframe = keyframeAt(time); if (!existingKeyframe.isNull()) { removeKeyframeLogical(existingKeyframe); } if (!newKeyframe.isNull()) { insertKeyframeLogical(newKeyframe); } return existingKeyframe; } void KisKeyframeChannel::insertKeyframeLogical(KisKeyframeSP keyframe) { const int time = keyframe->time(); emit sigKeyframeAboutToBeAdded(keyframe); - m_d->keys.insert(time, keyframe); + m_d->addKeyframe(keyframe); emit sigKeyframeAdded(keyframe); QRect rect = keyframe->affectedRect(); KisFrameSet range = affectedFrames(time); requestUpdate(range, rect); } void KisKeyframeChannel::removeKeyframeLogical(KisKeyframeSP keyframe) { QRect rect = keyframe->affectedRect(); KisFrameSet range = affectedFrames(keyframe->time()); emit sigKeyframeAboutToBeRemoved(keyframe); - m_d->keys.remove(keyframe->time()); + m_d->removeKeyframe(keyframe); emit sigKeyframeRemoved(keyframe); requestUpdate(range, rect); } KisKeyframeSP KisKeyframeChannel::keyframeAt(int time) const { KeyframesMap::const_iterator i = m_d->keys.constFind(time); if (i != m_d->keys.constEnd()) { return i.value(); } return KisKeyframeSP(); } KisKeyframeSP KisKeyframeChannel::activeKeyframeAt(int time) const { KeyframesMap::const_iterator i = activeKeyIterator(time); if (i != m_d->keys.constEnd()) { return i.value(); } return KisKeyframeSP(); } KisKeyframeSP KisKeyframeChannel::visibleKeyframeAt(int time) const { - return activeKeyframeAt(time); + KisKeyframeSP keyframe = activeKeyframeAt(time); + const KisRepeatFrame *repeat = dynamic_cast(keyframe.data()); + + if (repeat) keyframe = repeat->getOriginalKeyframeFor(time); + return keyframe; } KisKeyframeSP KisKeyframeChannel::currentlyActiveKeyframe() const { return activeKeyframeAt(currentTime()); } KisKeyframeSP KisKeyframeChannel::firstKeyframe() const { if (m_d->keys.isEmpty()) return KisKeyframeSP(); return m_d->keys.first(); } KisKeyframeSP KisKeyframeChannel::nextKeyframe(KisKeyframeSP keyframe) const { return nextKeyframe(*keyframe); } KisKeyframeSP KisKeyframeChannel::nextKeyframe(const KisKeyframe &keyframe) const { KeyframesMap::const_iterator i = m_d->keys.constFind(keyframe.time()); if (i == m_d->keys.constEnd()) return KisKeyframeSP(0); i++; if (i == m_d->keys.constEnd()) return KisKeyframeSP(0); return i.value(); } KisKeyframeSP KisKeyframeChannel::previousKeyframe(KisKeyframeSP keyframe) const { return previousKeyframe(*keyframe); } KisKeyframeSP KisKeyframeChannel::previousKeyframe(const KisKeyframe &keyframe) const { KeyframesMap::const_iterator i = m_d->keys.constFind(keyframe.time()); if (i == m_d->keys.constBegin() || i == m_d->keys.constEnd()) return KisKeyframeSP(0); i--; return i.value(); } KisKeyframeSP KisKeyframeChannel::lastKeyframe() const { if (m_d->keys.isEmpty()) return KisKeyframeSP(0); return (m_d->keys.end()-1).value(); } +KisTimeSpan KisKeyframeChannel::cycledRangeAt(int time) const +{ + QSharedPointer cycle = cycleAt(time); + if (cycle.isNull()) return KisTimeSpan(); + + const KisKeyframeSP firstAfterCycle = nextKeyframe(cycle->lastSourceKeyframe()); + + int start = cycle->firstSourceKeyframe()->time(); + + KisTimeSpan originalRange; + if (firstAfterCycle.isNull()) { + originalRange = KisTimeSpan(start, lastKeyframe()->time()); + } else { + originalRange = KisTimeSpan(start, firstAfterCycle->time() - 1); + } + + if (originalRange.contains(time) || KisRepeatFrame::isRepeat(activeKeyframeAt(time))) { + return originalRange; + } + + return KisTimeSpan(); +} + +QSharedPointer KisKeyframeChannel::cycleAt(int time) const +{ + if (!m_d->cycles.isEmpty()) { + auto it = m_d->cycles.upperBound(time); + if (it != m_d->cycles.begin()) it--; + return it.value(); + } + + return QSharedPointer(); +}; + void KisKeyframeChannel::activeKeyframeRange(int time, int *first, int *last) const { *first = *last = -1; const KisKeyframeSP currentKeyframe = activeKeyframeAt(time); if (currentKeyframe.isNull()) return; *first = currentKeyframe->time(); const KisKeyframeSP next = nextKeyframe(currentKeyframe); if (!next.isNull()) { *last = next->time() - 1; } } int KisKeyframeChannel::framesHash() const { KeyframesMap::const_iterator it = m_d->keys.constBegin(); KeyframesMap::const_iterator end = m_d->keys.constEnd(); int hash = 0; while (it != end) { hash += it.key(); ++it; } return hash; } QSet KisKeyframeChannel::allKeyframeIds() const { QSet frames; KeyframesMap::const_iterator it = m_d->keys.constBegin(); KeyframesMap::const_iterator end = m_d->keys.constEnd(); while (it != end) { frames.insert(it.key()); ++it; } return frames; } KisFrameSet KisKeyframeChannel::affectedFrames(int time) const { if (m_d->keys.isEmpty()) return KisFrameSet::infiniteFrom(0); KeyframesMap::const_iterator active = activeKeyIterator(time); KeyframesMap::const_iterator next; int from; if (active == m_d->keys.constEnd()) { // No active keyframe, ie. time is before the first keyframe from = 0; next = m_d->keys.constBegin(); } else { from = active.key(); next = active + 1; } + KisFrameSet frames; + + QSharedPointer cycle = cycleAt(time); + if (!cycle.isNull()) { + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(active != m_d->keys.constEnd() && active.value(), KisFrameSet()); + + if (cycle->originalRange().contains(time)) { + frames = cycle->instancesWithin(active.value(), KisTimeSpan()); + } else { + const KisRepeatFrame *repeat = dynamic_cast(active.value().data()); + if (repeat) { + const KisKeyframeSP original = repeat->getOriginalKeyframeFor(time); + return affectedFrames(original->time()); + } + } + } + if (next == m_d->keys.constEnd()) { - return KisFrameSet::infiniteFrom(from); + frames |= KisFrameSet::infiniteFrom(from); } else { - return KisFrameSet::between(from, next.key() - 1); + frames |= KisFrameSet::between(from, next.key() - 1); } + + return frames; } -KisFrameSet KisKeyframeChannel::identicalFrames(int time, const KisTimeSpan) const +KisFrameSet KisKeyframeChannel::identicalFrames(int time, const KisTimeSpan range) const { KeyframesMap::const_iterator active = activeKeyIterator(time); + QSharedPointer cycle = cycleAt(time); + if (!cycle.isNull()) { + if (cycle->originalRange().contains(time)) { + return cycle->instancesWithin(active.value(), range); + } else { + const KisRepeatFrame *repeat = dynamic_cast(active.value().data()); + if (repeat) { + const KisKeyframeSP original = repeat->getOriginalKeyframeFor(time); + return identicalFrames(original->time(), range); + } + } + } + if (active != m_d->keys.constEnd() && (active+1) != m_d->keys.constEnd()) { if (active->data()->interpolationMode() != KisKeyframe::Constant) { return KisFrameSet::between(time, time); } } return affectedFrames(time); } bool KisKeyframeChannel::areFramesIdentical(int time1, int time2) const { const KisFrameSet identical = identicalFrames(time1, KisTimeSpan(time2, time2)); return identical.contains(time2); } bool KisKeyframeChannel::isFrameAffectedBy(int targetFrame, int changedFrame) const { const KisFrameSet affected = affectedFrames(changedFrame); return affected.contains(targetFrame); } QDomElement KisKeyframeChannel::toXML(QDomDocument doc, const QString &layerFilename) { QDomElement channelElement = doc.createElement("channel"); channelElement.setAttribute("name", id()); Q_FOREACH (KisKeyframeSP keyframe, m_d->keys.values()) { QDomElement keyframeElement = doc.createElement("keyframe"); keyframeElement.setAttribute("time", keyframe->time()); keyframeElement.setAttribute("color-label", keyframe->colorLabel()); saveKeyframe(keyframe, keyframeElement, layerFilename); channelElement.appendChild(keyframeElement); } return channelElement; } void KisKeyframeChannel::loadXML(const QDomElement &channelNode) { for (QDomElement keyframeNode = channelNode.firstChildElement(); !keyframeNode.isNull(); keyframeNode = keyframeNode.nextSiblingElement()) { if (keyframeNode.nodeName().toUpper() != "KEYFRAME") continue; KisKeyframeSP keyframe = loadKeyframe(keyframeNode); KIS_SAFE_ASSERT_RECOVER(keyframe) { continue; } if (keyframeNode.hasAttribute("color-label")) { keyframe->setColorLabel(keyframeNode.attribute("color-label").toUInt()); } - m_d->keys.insert(keyframe->time(), keyframe); + m_d->addKeyframe(keyframe); } } bool KisKeyframeChannel::swapExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, int dstTime, KUndo2Command *parentCommand) { if (srcChannel->id() != id()) { warnKrita << "Cannot copy frames from different ids:" << ppVar(srcChannel->id()) << ppVar(id()); return KisKeyframeSP(); } LAZY_INITIALIZE_PARENT_COMMAND(parentCommand); KisKeyframeSP srcFrame = srcChannel->keyframeAt(srcTime); KisKeyframeSP dstFrame = keyframeAt(dstTime); if (!dstFrame && srcFrame) { copyExternalKeyframe(srcChannel, srcTime, dstTime, parentCommand); srcChannel->deleteKeyframe(srcFrame, parentCommand); } else if (dstFrame && !srcFrame) { srcChannel->copyExternalKeyframe(this, dstTime, srcTime, parentCommand); deleteKeyframe(dstFrame, parentCommand); } else if (dstFrame && srcFrame) { const int fakeFrameTime = -1; KisKeyframeSP newKeyframe = createKeyframe(fakeFrameTime, KisKeyframeSP(), parentCommand); uploadExternalKeyframe(srcChannel, srcTime, newKeyframe); srcChannel->copyExternalKeyframe(this, dstTime, srcTime, parentCommand); // do not recreate frame! deleteKeyframeImpl(dstFrame, parentCommand, false); newKeyframe->setTime(dstTime); KUndo2Command *cmd = new KisReplaceKeyframeCommand(this, newKeyframe->time(), newKeyframe, parentCommand); cmd->redo(); } return true; } KisKeyframeSP KisKeyframeChannel::copyExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, int dstTime, KUndo2Command *parentCommand) { if (srcChannel->id() != id()) { warnKrita << "Cannot copy frames from different ids:" << ppVar(srcChannel->id()) << ppVar(id()); return KisKeyframeSP(); } LAZY_INITIALIZE_PARENT_COMMAND(parentCommand); KisKeyframeSP dstFrame = keyframeAt(dstTime); if (dstFrame) { deleteKeyframeImpl(dstFrame, parentCommand, false); } KisKeyframeSP newKeyframe = createKeyframe(dstTime, KisKeyframeSP(), parentCommand); uploadExternalKeyframe(srcChannel, srcTime, newKeyframe); KUndo2Command *cmd = new KisReplaceKeyframeCommand(this, newKeyframe->time(), newKeyframe, parentCommand); cmd->redo(); return newKeyframe; } +KUndo2Command * KisKeyframeChannel::createCycle(KisKeyframeSP firstKeyframe, KisKeyframeSP lastKeyframe, KUndo2Command *parentCommand) +{ + const QSharedPointer cycle = toQShared(new KisAnimationCycle(firstKeyframe, lastKeyframe)); + return new KisDefineCycleCommand(this, cycle, false, parentCommand); +} + +void KisKeyframeChannel::addCycle(QSharedPointer cycle) +{ + m_d->cycles.insert(cycle->firstSourceKeyframe()->time(), cycle); +} + +KUndo2Command* KisKeyframeChannel::deleteCycle(QSharedPointer cycle, KUndo2Command *parentCommand) +{ + Q_FOREACH(QWeakPointer repeat, cycle->repeats()) { + auto repeatSP = repeat.toStrongRef(); + deleteKeyframe(repeatSP, parentCommand); + } + + return new KisDefineCycleCommand(this, cycle, true, parentCommand); +} + +void KisKeyframeChannel::removeCycle(QSharedPointer cycle) +{ + m_d->cycles.remove(cycle->firstSourceKeyframe()->time()); +} + +KisKeyframeSP KisKeyframeChannel::addRepeat(int time, KisKeyframeSP source, KUndo2Command *parentCommand) +{ + const QSharedPointer cycle = m_d->cycles.value(source->time()); + if (cycle.isNull()) return QSharedPointer(); + + const QSharedPointer repeatFrame = toQShared(new KisRepeatFrame(this, time, cycle)); + KUndo2Command *cmd = new KisReplaceKeyframeCommand(this, time, repeatFrame, parentCommand); + cmd->redo(); + + return repeatFrame; +} + KisKeyframeChannel::KeyframesMap::const_iterator KisKeyframeChannel::activeKeyIterator(int time) const { KeyframesMap::const_iterator i = const_cast(&m_d->keys)->upperBound(time); if (i == m_d->keys.constBegin()) return m_d->keys.constEnd(); return --i; } void KisKeyframeChannel::requestUpdate(const KisFrameSet &range, const QRect &rect) { if (m_d->node) { m_d->node->invalidateFrames(range, rect); int currentTime = m_d->defaultBounds->currentTime(); if (range.contains(currentTime)) { m_d->node->setDirty(rect); } } } void KisKeyframeChannel::workaroundBrokenFrameTimeBug(int *time) { /** * Between Krita 4.1 and 4.4 Krita had a bug which resulted in creating frames * with negative time stamp. The bug has been fixed, but there might be some files * still in the wild. * * TODO: remove this workaround in Krita 5.0, when no such file are left :) */ if (*time < 0) { qWarning() << "WARNING: Loading a file with negative animation frames!"; qWarning() << " The file has been saved with a buggy version of Krita."; qWarning() << " All the frames with negative ids will be dropped!"; qWarning() << " " << ppVar(this->id()) << ppVar(*time); m_d->haveBrokenFrameTimeBug = true; *time = 0; } if (m_d->haveBrokenFrameTimeBug) { while (keyframeAt(*time)) { (*time)++; } } } int KisKeyframeChannel::currentTime() const { return m_d->defaultBounds->currentTime(); } qreal KisKeyframeChannel::minScalarValue() const { return 0; } qreal KisKeyframeChannel::maxScalarValue() const { return 0; } qreal KisKeyframeChannel::scalarValue(const KisKeyframeSP keyframe) const { Q_UNUSED(keyframe); return 0; } void KisKeyframeChannel::setScalarValue(KisKeyframeSP keyframe, qreal value, KUndo2Command *parentCommand) { Q_UNUSED(keyframe); Q_UNUSED(value); Q_UNUSED(parentCommand); } diff --git a/libs/image/kis_keyframe_channel.h b/libs/image/kis_keyframe_channel.h index 98f650d346..cfe72c4588 100644 --- a/libs/image/kis_keyframe_channel.h +++ b/libs/image/kis_keyframe_channel.h @@ -1,178 +1,193 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * 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_KEYFRAME_CHANNEL_H #define KIS_KEYFRAME_CHANNEL_H #include #include #include #include "kis_types.h" #include "KoID.h" #include "kritaimage_export.h" #include "kis_keyframe.h" #include "kis_default_bounds.h" class KisFrameSet; class KisTimeSpan; +class KisAnimationCycle; class KRITAIMAGE_EXPORT KisKeyframeChannel : public QObject { Q_OBJECT public: // Standard Keyframe Ids static const KoID Content; static const KoID Opacity; static const KoID TransformArguments; static const KoID TransformPositionX; static const KoID TransformPositionY; static const KoID TransformScaleX; static const KoID TransformScaleY; static const KoID TransformShearX; static const KoID TransformShearY; static const KoID TransformRotationX; static const KoID TransformRotationY; static const KoID TransformRotationZ; public: KisKeyframeChannel(const KoID& id, KisDefaultBoundsBaseSP defaultBounds); KisKeyframeChannel(const KisKeyframeChannel &rhs, KisNode *newParentNode); ~KisKeyframeChannel() override; QString id() const; QString name() const; void setNode(KisNodeWSP node); KisNodeWSP node() const; KisKeyframeSP addKeyframe(int time, KUndo2Command *parentCommand = 0); bool deleteKeyframe(KisKeyframeSP keyframe, KUndo2Command *parentCommand = 0); bool moveKeyframe(KisKeyframeSP keyframe, int newTime, KUndo2Command *parentCommand = 0); bool swapFrames(int lhsTime, int rhsTime, KUndo2Command *parentCommand = 0); KisKeyframeSP copyKeyframe(const KisKeyframeSP keyframe, int newTime, KUndo2Command *parentCommand = 0); virtual KisKeyframeSP linkKeyframe(const KisKeyframeSP keyframe, int newTime, KUndo2Command *parentCommand = 0); KisKeyframeSP copyExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, int dstTime, KUndo2Command *parentCommand = 0); + KUndo2Command * createCycle(KisKeyframeSP firstKeyframe, KisKeyframeSP lastKeyframe, KUndo2Command *parentCommand = 0); + KUndo2Command * deleteCycle(QSharedPointer cycle, KUndo2Command *parentCommand = 0); + KisKeyframeSP addRepeat(int time, KisKeyframeSP source, KUndo2Command *parentCommand = 0); bool swapExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, int dstTime, KUndo2Command *parentCommand = 0); KisKeyframeSP keyframeAt(int time) const; KisKeyframeSP activeKeyframeAt(int time) const; KisKeyframeSP visibleKeyframeAt(int time) const; KisKeyframeSP currentlyActiveKeyframe() const; KisKeyframeSP firstKeyframe() const; KisKeyframeSP nextKeyframe(KisKeyframeSP keyframe) const; KisKeyframeSP previousKeyframe(KisKeyframeSP keyframe) const; KisKeyframeSP nextKeyframe(const KisKeyframe &keyframe) const; KisKeyframeSP previousKeyframe(const KisKeyframe &keyframe) const; KisKeyframeSP lastKeyframe() const; + /** + * Finds the original range of the cycle defined or repeated at the given time. + * @arg time a time at any frame within the original cycle or any repeat of it. + */ + KisTimeSpan cycledRangeAt(int time) const; + QSharedPointer cycleAt(int time) const; + /** * Finds the span of time of the keyframe active at given time. * If there is no active keyframe, first will be -1. * If the keyframe continues indefinitely, last will be -1. */ void activeKeyframeRange(int time, int *first, int *last) const; /** * Calculates a pseudo-unique keyframes hash. The hash changes * every time any frame is added/removed/moved */ int framesHash() const; QSet allKeyframeIds() const; /** * Get the set of frames affected by any changes to the value * of the active keyframe at the given time. */ virtual KisFrameSet affectedFrames(int time) const; /** * Get a set of frames for which the channel gives identical * results, compared to the given frame. * * Note: this set may be different than the set of affected frames * due to interpolation. */ virtual KisFrameSet identicalFrames(int time, const KisTimeSpan range) const; virtual bool areFramesIdentical(int time1, int time2) const; virtual bool isFrameAffectedBy(int targetFrame, int changedFrame) const; int keyframeCount() const; virtual bool hasScalarValue() const = 0; virtual qreal minScalarValue() const; virtual qreal maxScalarValue() const; virtual qreal scalarValue(const KisKeyframeSP keyframe) const; virtual void setScalarValue(KisKeyframeSP keyframe, qreal value, KUndo2Command *parentCommand = 0); virtual QDomElement toXML(QDomDocument doc, const QString &layerFilename); virtual void loadXML(const QDomElement &channelNode); int currentTime() const; Q_SIGNALS: void sigKeyframeAboutToBeAdded(KisKeyframeSP keyframe); void sigKeyframeAdded(KisKeyframeSP keyframe); void sigKeyframeAboutToBeRemoved(KisKeyframeSP keyframe); void sigKeyframeRemoved(KisKeyframeSP keyframe); void sigKeyframeAboutToBeMoved(KisKeyframeSP keyframe, int toTime); void sigKeyframeMoved(KisKeyframeSP keyframe, int fromTime); void sigKeyframeChanged(KisKeyframeSP keyframe); protected: typedef QMap KeyframesMap; KeyframesMap &keys(); const KeyframesMap &constKeys() const; KeyframesMap::const_iterator activeKeyIterator(int time) const; virtual KisKeyframeSP createKeyframe(int time, const KisKeyframeSP copySrc, KUndo2Command *parentCommand) = 0; virtual void destroyKeyframe(KisKeyframeSP key, KUndo2Command *parentCommand) = 0; virtual void uploadExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, KisKeyframeSP dstFrame) = 0; virtual void requestUpdate(const KisFrameSet &range, const QRect &rect); virtual KisKeyframeSP loadKeyframe(const QDomElement &keyframeNode) = 0; virtual void saveKeyframe(KisKeyframeSP keyframe, QDomElement keyframeElement, const QString &layerFilename) = 0; void workaroundBrokenFrameTimeBug(int *time); private: KisKeyframeSP replaceKeyframeAt(int time, KisKeyframeSP newKeyframe); void insertKeyframeLogical(KisKeyframeSP keyframe); void removeKeyframeLogical(KisKeyframeSP keyframe); bool deleteKeyframeImpl(KisKeyframeSP keyframe, KUndo2Command *parentCommand, bool recreate); void moveKeyframeImpl(KisKeyframeSP keyframe, int newTime); void swapKeyframesImpl(KisKeyframeSP lhsKeyframe, KisKeyframeSP rhsKeyframe); + void addCycle(QSharedPointer cycle); + void removeCycle(QSharedPointer cycle); + friend class KisMoveFrameCommand; friend class KisReplaceKeyframeCommand; friend class KisSwapFramesCommand; + friend class KisDefineCycleCommand; private: KisKeyframeSP insertKeyframe(int time, const KisKeyframeSP copySrc, KUndo2Command *parentCommand); struct Private; QScopedPointer m_d; }; #endif // KIS_KEYFRAME_CHANNEL_H diff --git a/libs/image/kis_keyframe_commands.cpp b/libs/image/kis_keyframe_commands.cpp index 3030d70392..809b04552b 100644 --- a/libs/image/kis_keyframe_commands.cpp +++ b/libs/image/kis_keyframe_commands.cpp @@ -1,55 +1,80 @@ #include "kis_keyframe_commands.h" KisReplaceKeyframeCommand::KisReplaceKeyframeCommand(KisKeyframeChannel *channel, int time, KisKeyframeSP keyframe, KUndo2Command *parentCommand) : KUndo2Command(parentCommand), m_channel(channel), m_time(time), m_keyframe(keyframe), m_existingKeyframe(0) { } void KisReplaceKeyframeCommand::redo() { m_existingKeyframe = m_channel->replaceKeyframeAt(m_time, m_keyframe); } void KisReplaceKeyframeCommand::undo() { m_channel->replaceKeyframeAt(m_time, m_existingKeyframe); } KisMoveFrameCommand::KisMoveFrameCommand(KisKeyframeChannel *channel, KisKeyframeSP keyframe, int oldTime, int newTime, KUndo2Command *parentCommand) : KUndo2Command(parentCommand), m_channel(channel), m_keyframe(keyframe), m_oldTime(oldTime), m_newTime(newTime) { } void KisMoveFrameCommand::redo() { m_channel->moveKeyframeImpl(m_keyframe, m_newTime); } void KisMoveFrameCommand::undo() { m_channel->moveKeyframeImpl(m_keyframe, m_oldTime); } KisSwapFramesCommand::KisSwapFramesCommand(KisKeyframeChannel *channel, KisKeyframeSP lhsFrame, KisKeyframeSP rhsFrame, KUndo2Command *parentCommand) : KUndo2Command(parentCommand), m_channel(channel), m_lhsFrame(lhsFrame), m_rhsFrame(rhsFrame) { } void KisSwapFramesCommand::redo() { m_channel->swapKeyframesImpl(m_lhsFrame, m_rhsFrame); } void KisSwapFramesCommand::undo() { m_channel->swapKeyframesImpl(m_lhsFrame, m_rhsFrame); } + +KisDefineCycleCommand::KisDefineCycleCommand(KisKeyframeChannel *channel, QSharedPointer cycle, bool undefine, KUndo2Command *parentCommand) + : KUndo2Command(parentCommand) + , m_channel(channel) + , m_cycle(cycle) + , m_undefine(undefine) +{} + +void KisDefineCycleCommand::redo() +{ + if (m_undefine) { + m_channel->removeCycle(m_cycle); + } else { + m_channel->addCycle(m_cycle); + } +} + +void KisDefineCycleCommand::undo() +{ + if (m_undefine) { + m_channel->addCycle(m_cycle); + } else { + m_channel->removeCycle(m_cycle); + } +} diff --git a/libs/image/kis_keyframe_commands.h b/libs/image/kis_keyframe_commands.h index b8d4b59144..72b5f49382 100644 --- a/libs/image/kis_keyframe_commands.h +++ b/libs/image/kis_keyframe_commands.h @@ -1,71 +1,85 @@ /* * 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_KEYFRAME_COMMANDS_H #define KIS_KEYFRAME_COMMANDS_H #include "kis_keyframe_channel.h" #include "kundo2command.h" #include "kritaimage_export.h" class KRITAIMAGE_EXPORT KisReplaceKeyframeCommand : public KUndo2Command { public: KisReplaceKeyframeCommand(KisKeyframeChannel *channel, int time, KisKeyframeSP keyframe, KUndo2Command *parentCommand); void redo() override; void undo() override; private: void doSwap(bool insert); private: KisKeyframeChannel *m_channel; int m_time; KisKeyframeSP m_keyframe; KisKeyframeSP m_existingKeyframe; }; class KRITAIMAGE_EXPORT KisMoveFrameCommand : public KUndo2Command { public: KisMoveFrameCommand(KisKeyframeChannel *channel, KisKeyframeSP keyframe, int oldTime, int newTime, KUndo2Command *parentCommand); void redo() override; void undo() override; private: KisKeyframeChannel *m_channel; KisKeyframeSP m_keyframe; int m_oldTime; int m_newTime; }; class KRITAIMAGE_EXPORT KisSwapFramesCommand : public KUndo2Command { public: KisSwapFramesCommand(KisKeyframeChannel *channel, KisKeyframeSP lhsFrame, KisKeyframeSP rhsFrame, KUndo2Command *parentCommand); void redo() override; void undo() override; private: KisKeyframeChannel *m_channel; KisKeyframeSP m_lhsFrame; KisKeyframeSP m_rhsFrame; }; +class KRITAIMAGE_EXPORT KisDefineCycleCommand : public KUndo2Command +{ +public: + KisDefineCycleCommand(KisKeyframeChannel *channel, QSharedPointer cycle, bool undefine, KUndo2Command *parentCommand); + + void redo() override; + void undo() override; + +private: + KisKeyframeChannel *m_channel; + QSharedPointer m_cycle; + bool m_undefine; +}; + #endif diff --git a/libs/image/kis_layer.cc b/libs/image/kis_layer.cc index cc945da37c..d4f10992cf 100644 --- a/libs/image/kis_layer.cc +++ b/libs/image/kis_layer.cc @@ -1,1034 +1,1034 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * Copyright (c) 2009 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_layer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_debug.h" #include "kis_image.h" #include "kis_painter.h" #include "kis_mask.h" #include "kis_effect_mask.h" #include "kis_selection_mask.h" #include "kis_meta_data_store.h" #include "kis_selection.h" #include "kis_paint_layer.h" #include "kis_raster_keyframe_channel.h" #include "kis_clone_layer.h" #include "kis_psd_layer_style.h" #include "kis_layer_projection_plane.h" #include "layerstyles/kis_layer_style_projection_plane.h" #include "krita_utils.h" #include "kis_layer_properties_icons.h" #include "kis_layer_utils.h" #include "kis_projection_leaf.h" class KisSafeProjection { public: KisPaintDeviceSP getDeviceLazy(KisPaintDeviceSP prototype) { QMutexLocker locker(&m_lock); if (!m_reusablePaintDevice) { m_reusablePaintDevice = new KisPaintDevice(*prototype); } if(!m_projection || *m_projection->colorSpace() != *prototype->colorSpace()) { m_projection = m_reusablePaintDevice; m_projection->makeCloneFromRough(prototype, prototype->extent()); m_projection->setProjectionDevice(true); } return m_projection; } void tryCopyFrom(const KisSafeProjection &rhs) { QMutexLocker locker(&m_lock); if (!rhs.m_projection) return; if (!m_reusablePaintDevice) { m_reusablePaintDevice = new KisPaintDevice(*rhs.m_projection); m_projection = m_reusablePaintDevice; } else { m_projection = m_reusablePaintDevice; m_projection->makeCloneFromRough(rhs.m_projection, rhs.m_projection->extent()); } } void freeDevice() { QMutexLocker locker(&m_lock); m_projection = 0; if(m_reusablePaintDevice) { m_reusablePaintDevice->clear(); } } private: QMutex m_lock; KisPaintDeviceSP m_projection; KisPaintDeviceSP m_reusablePaintDevice; }; class KisCloneLayersList { public: void addClone(KisCloneLayerWSP cloneLayer) { m_clonesList.append(cloneLayer); } void removeClone(KisCloneLayerWSP cloneLayer) { m_clonesList.removeOne(cloneLayer); } void setDirty(const QRect &rect) { Q_FOREACH (KisCloneLayerSP clone, m_clonesList) { if (clone) { clone->setDirtyOriginal(rect); } } } const QList registeredClones() const { return m_clonesList; } bool hasClones() const { return !m_clonesList.isEmpty(); } private: QList m_clonesList; }; class KisLayerMasksCache { public: KisLayerMasksCache(KisLayer *parent) : m_parent(parent) { } KisSelectionMaskSP selectionMask() { QReadLocker readLock(&m_lock); if (!m_isSelectionMaskValid) { readLock.unlock(); QWriteLocker writeLock(&m_lock); if (!m_isSelectionMaskValid) { KoProperties properties; properties.setProperty("active", true); properties.setProperty("visible", true); QList masks = m_parent->childNodes(QStringList("KisSelectionMask"), properties); // return the first visible mask Q_FOREACH (KisNodeSP mask, masks) { if (mask) { m_selectionMask = dynamic_cast(mask.data()); break; } } m_isSelectionMaskValid = true; } // return under write lock return m_selectionMask; } // return under read lock return m_selectionMask; } QList effectMasks() { QReadLocker readLock(&m_lock); if (!m_isEffectMasksValid) { readLock.unlock(); QWriteLocker writeLock(&m_lock); if (!m_isEffectMasksValid) { m_effectMasks = m_parent->searchEffectMasks(0); m_isEffectMasksValid = true; } // return under write lock return m_effectMasks; } // return under read lock return m_effectMasks; } void setDirty() { QWriteLocker l(&m_lock); m_isSelectionMaskValid = false; m_isEffectMasksValid = false; m_selectionMask = 0; m_effectMasks.clear(); } private: KisLayer *m_parent; QReadWriteLock m_lock; bool m_isSelectionMaskValid = false; bool m_isEffectMasksValid = false; KisSelectionMaskSP m_selectionMask; QList m_effectMasks; }; struct Q_DECL_HIDDEN KisLayer::Private { Private(KisLayer *q) : masksCache(q) {} KisImageWSP image; QBitArray channelFlags; KisMetaData::Store* metaDataStore; KisSafeProjection safeProjection; KisCloneLayersList clonesList; KisPSDLayerStyleSP layerStyle; KisLayerStyleProjectionPlaneSP layerStyleProjectionPlane; KisAbstractProjectionPlaneSP projectionPlane; KisLayerMasksCache masksCache; }; KisLayer::KisLayer(KisImageWSP image, const QString &name, quint8 opacity) : KisNode() , m_d(new Private(this)) { setName(name); setOpacity(opacity); m_d->image = image; m_d->metaDataStore = new KisMetaData::Store(); m_d->projectionPlane = toQShared(new KisLayerProjectionPlane(this)); } KisLayer::KisLayer(const KisLayer& rhs) : KisNode(rhs) , m_d(new Private(this)) { if (this != &rhs) { m_d->image = rhs.m_d->image; m_d->metaDataStore = new KisMetaData::Store(*rhs.m_d->metaDataStore); m_d->channelFlags = rhs.m_d->channelFlags; m_d->safeProjection.tryCopyFrom(rhs.m_d->safeProjection); setName(rhs.name()); m_d->projectionPlane = toQShared(new KisLayerProjectionPlane(this)); if (rhs.m_d->layerStyle) { m_d->layerStyle = rhs.m_d->layerStyle->clone(); KIS_SAFE_ASSERT_RECOVER_NOOP(rhs.m_d->layerStyleProjectionPlane); if (rhs.m_d->layerStyleProjectionPlane) { m_d->layerStyleProjectionPlane = toQShared( new KisLayerStyleProjectionPlane(*rhs.m_d->layerStyleProjectionPlane, this, m_d->layerStyle)); } } } } KisLayer::~KisLayer() { delete m_d->metaDataStore; delete m_d; } const KoColorSpace * KisLayer::colorSpace() const { KisImageSP image = m_d->image.toStrongRef(); if (!image) { return nullptr; } return image->colorSpace(); } const KoCompositeOp * KisLayer::compositeOp() const { /** * FIXME: This function duplicates the same function from * KisMask. We can't move it to KisBaseNode as it doesn't * know anything about parent() method of KisNode * Please think it over... */ KisNodeSP parentNode = parent(); if (!parentNode) return 0; if (!parentNode->colorSpace()) return 0; const KoCompositeOp* op = parentNode->colorSpace()->compositeOp(compositeOpId()); return op ? op : parentNode->colorSpace()->compositeOp(COMPOSITE_OVER); } KisPSDLayerStyleSP KisLayer::layerStyle() const { return m_d->layerStyle; } void KisLayer::setLayerStyle(KisPSDLayerStyleSP layerStyle) { if (layerStyle) { m_d->layerStyle = layerStyle; KisLayerStyleProjectionPlaneSP plane = !layerStyle->isEmpty() ? KisLayerStyleProjectionPlaneSP(new KisLayerStyleProjectionPlane(this)) : KisLayerStyleProjectionPlaneSP(0); m_d->layerStyleProjectionPlane = plane; } else { m_d->layerStyleProjectionPlane.clear(); m_d->layerStyle.clear(); } } KisBaseNode::PropertyList KisLayer::sectionModelProperties() const { KisBaseNode::PropertyList l = KisBaseNode::sectionModelProperties(); l << KisBaseNode::Property(KoID("opacity", i18n("Opacity")), i18n("%1%", percentOpacity())); const KoCompositeOp * compositeOp = this->compositeOp(); if (compositeOp) { l << KisBaseNode::Property(KoID("compositeop", i18n("Blending Mode")), compositeOp->description()); } if (m_d->layerStyle && !m_d->layerStyle->isEmpty()) { l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::layerStyle, m_d->layerStyle->isEnabled()); } l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::inheritAlpha, alphaChannelDisabled()); return l; } void KisLayer::setSectionModelProperties(const KisBaseNode::PropertyList &properties) { KisBaseNode::setSectionModelProperties(properties); Q_FOREACH (const KisBaseNode::Property &property, properties) { if (property.id == KisLayerPropertiesIcons::inheritAlpha.id()) { disableAlphaChannel(property.state.toBool()); } if (property.id == KisLayerPropertiesIcons::layerStyle.id()) { if (m_d->layerStyle && m_d->layerStyle->isEnabled() != property.state.toBool()) { m_d->layerStyle->setEnabled(property.state.toBool()); baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } } } } void KisLayer::disableAlphaChannel(bool disable) { QBitArray newChannelFlags = m_d->channelFlags; if(newChannelFlags.isEmpty()) newChannelFlags = colorSpace()->channelFlags(true, true); if(disable) newChannelFlags &= colorSpace()->channelFlags(true, false); else newChannelFlags |= colorSpace()->channelFlags(false, true); setChannelFlags(newChannelFlags); } bool KisLayer::alphaChannelDisabled() const { QBitArray flags = colorSpace()->channelFlags(false, true) & m_d->channelFlags; return flags.count(true) == 0 && !m_d->channelFlags.isEmpty(); } void KisLayer::setChannelFlags(const QBitArray & channelFlags) { Q_ASSERT(channelFlags.isEmpty() ||((quint32)channelFlags.count() == colorSpace()->channelCount())); if (KritaUtils::compareChannelFlags(channelFlags, this->channelFlags())) { return; } if (!channelFlags.isEmpty() && channelFlags == QBitArray(channelFlags.size(), true)) { m_d->channelFlags.clear(); } else { m_d->channelFlags = channelFlags; } baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } QBitArray & KisLayer::channelFlags() const { return m_d->channelFlags; } bool KisLayer::temporary() const { return nodeProperties().boolProperty("temporary", false); } void KisLayer::setTemporary(bool t) { setNodeProperty("temporary", t); } KisImageWSP KisLayer::image() const { return m_d->image; } void KisLayer::setImage(KisImageWSP image) { m_d->image = image; // we own the projection device, so we should take care about it KisPaintDeviceSP projection = this->projection(); if (projection && projection != original()) { projection->setDefaultBounds(new KisDefaultBounds(image)); } KisNodeSP node = firstChild(); while (node) { KisLayerUtils::recursiveApplyNodes(node, [image] (KisNodeSP node) { node->setImage(image); }); node = node->nextSibling(); } } bool KisLayer::canMergeAndKeepBlendOptions(KisLayerSP otherLayer) { return this->compositeOpId() == otherLayer->compositeOpId() && this->opacity() == otherLayer->opacity() && this->channelFlags() == otherLayer->channelFlags() && !this->layerStyle() && !otherLayer->layerStyle() && (this->colorSpace() == otherLayer->colorSpace() || *this->colorSpace() == *otherLayer->colorSpace()); } KisLayerSP KisLayer::createMergedLayerTemplate(KisLayerSP prevLayer) { const bool keepBlendingOptions = canMergeAndKeepBlendOptions(prevLayer); KisLayerSP newLayer = new KisPaintLayer(image(), prevLayer->name(), OPACITY_OPAQUE_U8); if (keepBlendingOptions) { newLayer->setCompositeOpId(compositeOpId()); newLayer->setOpacity(opacity()); newLayer->setChannelFlags(channelFlags()); } return newLayer; } void KisLayer::fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer) { const bool keepBlendingOptions = canMergeAndKeepBlendOptions(prevLayer); QRect layerProjectionExtent = this->projection()->extent(); QRect prevLayerProjectionExtent = prevLayer->projection()->extent(); bool alphaDisabled = this->alphaChannelDisabled(); bool prevAlphaDisabled = prevLayer->alphaChannelDisabled(); KisPaintDeviceSP mergedDevice = dstLayer->paintDevice(); if (!keepBlendingOptions) { KisPainter gc(mergedDevice); KisImageSP imageSP = image().toStrongRef(); if (!imageSP) { return; } //Copy the pixels of previous layer with their actual alpha value prevLayer->disableAlphaChannel(false); prevLayer->projectionPlane()->apply(&gc, prevLayerProjectionExtent | imageSP->bounds()); //Restore the previous prevLayer disableAlpha status for correct undo/redo prevLayer->disableAlphaChannel(prevAlphaDisabled); //Paint the pixels of the current layer, using their actual alpha value if (alphaDisabled == prevAlphaDisabled) { this->disableAlphaChannel(false); } this->projectionPlane()->apply(&gc, layerProjectionExtent | imageSP->bounds()); //Restore the layer disableAlpha status for correct undo/redo this->disableAlphaChannel(alphaDisabled); } else { //Copy prevLayer KisPaintDeviceSP srcDev = prevLayer->projection(); mergedDevice->makeCloneFrom(srcDev, srcDev->extent()); //Paint layer on the copy KisPainter gc(mergedDevice); gc.bitBlt(layerProjectionExtent.topLeft(), this->projection(), layerProjectionExtent); } } void KisLayer::registerClone(KisCloneLayerWSP clone) { m_d->clonesList.addClone(clone); } void KisLayer::unregisterClone(KisCloneLayerWSP clone) { m_d->clonesList.removeClone(clone); } const QList KisLayer::registeredClones() const { return m_d->clonesList.registeredClones(); } bool KisLayer::hasClones() const { return m_d->clonesList.hasClones(); } void KisLayer::updateClones(const QRect &rect) { m_d->clonesList.setDirty(rect); } void KisLayer::notifyChildMaskChanged() { m_d->masksCache.setDirty(); } KisSelectionMaskSP KisLayer::selectionMask() const { return m_d->masksCache.selectionMask(); } KisSelectionSP KisLayer::selection() const { KisSelectionMaskSP mask = selectionMask(); if (mask) { return mask->selection(); } KisImageSP image = m_d->image.toStrongRef(); if (image) { return image->globalSelection(); } return KisSelectionSP(); } /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// QList KisLayer::effectMasks() const { return m_d->masksCache.effectMasks(); } QList KisLayer::effectMasks(KisNodeSP lastNode) const { if (lastNode.isNull()) { return effectMasks(); } else { // happens rarely. return searchEffectMasks(lastNode); } } QList KisLayer::searchEffectMasks(KisNodeSP lastNode) const { QList masks; KIS_SAFE_ASSERT_RECOVER_NOOP(projectionLeaf()); KisProjectionLeafSP child = projectionLeaf()->firstChild(); while (child) { if (child->node() == lastNode) break; KIS_SAFE_ASSERT_RECOVER_NOOP(child); KIS_SAFE_ASSERT_RECOVER_NOOP(child->node()); if (child->visible()) { KisEffectMaskSP mask = dynamic_cast(const_cast(child->node().data())); if (mask) { masks.append(mask); } } child = child->nextSibling(); } return masks; } bool KisLayer::hasEffectMasks() const { return !m_d->masksCache.effectMasks().isEmpty(); } QRect KisLayer::masksChangeRect(const QList &masks, const QRect &requestedRect, bool &rectVariesFlag) const { rectVariesFlag = false; QRect prevChangeRect = requestedRect; /** * We set default value of the change rect for the case * when there is no mask at all */ QRect changeRect = requestedRect; Q_FOREACH (const KisEffectMaskSP& mask, masks) { changeRect = mask->changeRect(prevChangeRect); if (changeRect != prevChangeRect) rectVariesFlag = true; prevChangeRect = changeRect; } return changeRect; } QRect KisLayer::masksNeedRect(const QList &masks, const QRect &changeRect, QStack &applyRects, bool &rectVariesFlag) const { rectVariesFlag = false; QRect prevNeedRect = changeRect; QRect needRect; for (qint32 i = masks.size() - 1; i >= 0; i--) { applyRects.push(prevNeedRect); needRect = masks[i]->needRect(prevNeedRect); if (prevNeedRect != needRect) rectVariesFlag = true; prevNeedRect = needRect; } return needRect; } KisNode::PositionToFilthy calculatePositionToFilthy(KisNodeSP nodeInQuestion, KisNodeSP filthy, KisNodeSP parent) { if (parent == filthy || parent != filthy->parent()) { return KisNode::N_ABOVE_FILTHY; } if (nodeInQuestion == filthy) { return KisNode::N_FILTHY; } KisNodeSP node = nodeInQuestion->prevSibling(); while (node) { if (node == filthy) { return KisNode::N_ABOVE_FILTHY; } node = node->prevSibling(); } return KisNode::N_BELOW_FILTHY; } QRect KisLayer::applyMasks(const KisPaintDeviceSP source, KisPaintDeviceSP destination, const QRect &requestedRect, KisNodeSP filthyNode, KisNodeSP lastNode) const { Q_ASSERT(source); Q_ASSERT(destination); QList masks = effectMasks(lastNode); QRect changeRect; QRect needRect; if (masks.isEmpty()) { changeRect = requestedRect; if (source != destination) { copyOriginalToProjection(source, destination, requestedRect); } } else { QStack applyRects; bool changeRectVaries; bool needRectVaries; /** * FIXME: Assume that varying of the changeRect has already * been taken into account while preparing walkers */ changeRectVaries = false; changeRect = requestedRect; //changeRect = masksChangeRect(masks, requestedRect, // changeRectVaries); needRect = masksNeedRect(masks, changeRect, applyRects, needRectVaries); if (!changeRectVaries && !needRectVaries) { /** * A bit of optimization: * All filters will read/write exactly from/to the requested * rect so we needn't create temporary paint device, * just apply it onto destination */ Q_ASSERT(needRect == requestedRect); if (source != destination) { copyOriginalToProjection(source, destination, needRect); } Q_FOREACH (const KisEffectMaskSP& mask, masks) { const QRect maskApplyRect = applyRects.pop(); const QRect maskNeedRect = applyRects.isEmpty() ? needRect : applyRects.top(); PositionToFilthy maskPosition = calculatePositionToFilthy(mask, filthyNode, const_cast(this)); mask->apply(destination, maskApplyRect, maskNeedRect, maskPosition); } Q_ASSERT(applyRects.isEmpty()); } else { /** * We can't eliminate additional copy-op * as filters' behaviour may be quite insane here, * so let them work on their own paintDevice =) */ KisPaintDeviceSP tempDevice = new KisPaintDevice(colorSpace()); tempDevice->prepareClone(source); copyOriginalToProjection(source, tempDevice, needRect); QRect maskApplyRect = applyRects.pop(); QRect maskNeedRect = needRect; Q_FOREACH (const KisEffectMaskSP& mask, masks) { PositionToFilthy maskPosition = calculatePositionToFilthy(mask, filthyNode, const_cast(this)); mask->apply(tempDevice, maskApplyRect, maskNeedRect, maskPosition); if (!applyRects.isEmpty()) { maskNeedRect = maskApplyRect; maskApplyRect = applyRects.pop(); } } Q_ASSERT(applyRects.isEmpty()); KisPainter::copyAreaOptimized(changeRect.topLeft(), tempDevice, destination, changeRect); } } return changeRect; } QRect KisLayer::updateProjection(const QRect& rect, KisNodeSP filthyNode) { QRect updatedRect = rect; KisPaintDeviceSP originalDevice = original(); if (!rect.isValid() || !visible() || !originalDevice) return QRect(); if (!needProjection() && !hasEffectMasks()) { m_d->safeProjection.freeDevice(); } else { if (!updatedRect.isEmpty()) { KisPaintDeviceSP projection = m_d->safeProjection.getDeviceLazy(originalDevice); updatedRect = applyMasks(originalDevice, projection, updatedRect, filthyNode, 0); } } return updatedRect; } QRect KisLayer::partialChangeRect(KisNodeSP lastNode, const QRect& rect) { bool changeRectVaries = false; QRect changeRect = outgoingChangeRect(rect); changeRect = masksChangeRect(effectMasks(lastNode), changeRect, changeRectVaries); return changeRect; } /** * \p rect is a dirty rect in layer's original() coordinates! */ void KisLayer::buildProjectionUpToNode(KisPaintDeviceSP projection, KisNodeSP lastNode, const QRect& rect) { QRect changeRect = partialChangeRect(lastNode, rect); KisPaintDeviceSP originalDevice = original(); KIS_ASSERT_RECOVER_RETURN(needProjection() || hasEffectMasks()); if (!changeRect.isEmpty()) { applyMasks(originalDevice, projection, changeRect, this, lastNode); } } bool KisLayer::needProjection() const { return false; } void KisLayer::copyOriginalToProjection(const KisPaintDeviceSP original, KisPaintDeviceSP projection, const QRect& rect) const { KisPainter::copyAreaOptimized(rect.topLeft(), original, projection, rect); } KisAbstractProjectionPlaneSP KisLayer::projectionPlane() const { return m_d->layerStyleProjectionPlane ? KisAbstractProjectionPlaneSP(m_d->layerStyleProjectionPlane) : m_d->projectionPlane; } KisAbstractProjectionPlaneSP KisLayer::internalProjectionPlane() const { return m_d->projectionPlane; } KisPaintDeviceSP KisLayer::projection() const { KisPaintDeviceSP originalDevice = original(); return needProjection() || hasEffectMasks() ? m_d->safeProjection.getDeviceLazy(originalDevice) : originalDevice; } QRect KisLayer::changeRect(const QRect &rect, PositionToFilthy pos) const { QRect changeRect = rect; changeRect = incomingChangeRect(changeRect); if(pos == KisNode::N_FILTHY) { QRect projectionToBeUpdated = projection()->exactBoundsAmortized() & changeRect; bool changeRectVaries; changeRect = outgoingChangeRect(changeRect); changeRect = masksChangeRect(effectMasks(), changeRect, changeRectVaries); /** * If the projection contains some dirty areas we should also * add them to the change rect, because they might have * changed. E.g. when a visibility of the mask has chnaged * while the parent layer was invinisble. */ if (!projectionToBeUpdated.isEmpty() && !changeRect.contains(projectionToBeUpdated)) { changeRect |= projectionToBeUpdated; } } // TODO: string comparizon: optimize! if (pos != KisNode::N_FILTHY && pos != KisNode::N_FILTHY_PROJECTION && compositeOpId() != COMPOSITE_COPY) { changeRect |= rect; } return changeRect; } void KisLayer::childNodeChanged(KisNodeSP changedChildNode) { if (dynamic_cast(changedChildNode.data())) { notifyChildMaskChanged(); } } QRect KisLayer::incomingChangeRect(const QRect &rect) const { return rect; } QRect KisLayer::outgoingChangeRect(const QRect &rect) const { return rect; } QImage KisLayer::createThumbnail(qint32 w, qint32 h) { if (w == 0 || h == 0) { return QImage(); } KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->createThumbnail(w, h, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) : QImage(); } QImage KisLayer::createThumbnailForFrame(qint32 w, qint32 h, int time) { if (w == 0 || h == 0) { return QImage(); } KisPaintDeviceSP originalDevice = original(); - if (originalDevice) { + if (originalDevice && originalDevice->framesInterface()) { KisRasterKeyframeChannel *channel = originalDevice->keyframeChannel(); if (channel) { KisPaintDeviceSP targetDevice = new KisPaintDevice(colorSpace()); - KisKeyframeSP keyframe = channel->activeKeyframeAt(time); + KisKeyframeSP keyframe = channel->visibleKeyframeAt(time); channel->fetchFrame(keyframe, targetDevice); return targetDevice->createThumbnail(w, h, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } } return createThumbnail(w, h); } qint32 KisLayer::x() const { KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->x() : 0; } qint32 KisLayer::y() const { KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->y() : 0; } void KisLayer::setX(qint32 x) { KisPaintDeviceSP originalDevice = original(); if (originalDevice) originalDevice->setX(x); } void KisLayer::setY(qint32 y) { KisPaintDeviceSP originalDevice = original(); if (originalDevice) originalDevice->setY(y); } QRect KisLayer::layerExtentImpl(bool needExactBounds) const { QRect additionalMaskExtent = QRect(); QList effectMasks = this->effectMasks(); Q_FOREACH(KisEffectMaskSP mask, effectMasks) { additionalMaskExtent |= mask->nonDependentExtent(); } KisPaintDeviceSP originalDevice = original(); QRect layerExtent; if (originalDevice) { layerExtent = needExactBounds ? originalDevice->exactBounds() : originalDevice->extent(); } QRect additionalCompositeOpExtent; if (compositeOpId() == COMPOSITE_DESTINATION_IN || compositeOpId() == COMPOSITE_DESTINATION_ATOP) { additionalCompositeOpExtent = originalDevice->defaultBounds()->bounds(); } return layerExtent | additionalMaskExtent | additionalCompositeOpExtent; } QRect KisLayer::extent() const { return layerExtentImpl(false); } QRect KisLayer::exactBounds() const { return layerExtentImpl(true); } KisLayerSP KisLayer::parentLayer() const { return qobject_cast(parent().data()); } KisMetaData::Store* KisLayer::metaData() { return m_d->metaDataStore; } diff --git a/libs/image/kis_onion_skin_compositor.cpp b/libs/image/kis_onion_skin_compositor.cpp index af843ae89e..ed417576cc 100644 --- a/libs/image/kis_onion_skin_compositor.cpp +++ b/libs/image/kis_onion_skin_compositor.cpp @@ -1,227 +1,227 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * 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_onion_skin_compositor.h" #include "kis_paint_device.h" #include "kis_painter.h" #include "KoColor.h" #include "KoColorSpace.h" #include "KoCompositeOpRegistry.h" #include "KoColorSpaceConstants.h" #include "kis_image_config.h" #include "kis_raster_keyframe_channel.h" Q_GLOBAL_STATIC(KisOnionSkinCompositor, s_instance); struct KisOnionSkinCompositor::Private { int numberOfSkins = 0; int tintFactor = 0; QColor backwardTintColor; QColor forwardTintColor; QVector backwardOpacities; QVector forwardOpacities; int configSeqNo = 0; QList colorLabelFilter; int skinOpacity(int offset) { const QVector &bo = backwardOpacities; const QVector &fo = forwardOpacities; return offset > 0 ? fo[qAbs(offset) - 1] : bo[qAbs(offset) - 1]; } KisPaintDeviceSP setUpTintDevice(const QColor &tintColor, const KoColorSpace *colorSpace) { KisPaintDeviceSP tintDevice = new KisPaintDevice(colorSpace); KoColor color = KoColor(tintColor, colorSpace); tintDevice->setDefaultPixel(color); return tintDevice; } KisKeyframeSP getNextFrameToComposite(KisKeyframeChannel *channel, KisKeyframeSP keyframe, bool backwards) { while (!keyframe.isNull()) { keyframe = backwards ? channel->previousKeyframe(keyframe) : channel->nextKeyframe(keyframe); if (colorLabelFilter.isEmpty()) { return keyframe; } else if (!keyframe.isNull()) { if (colorLabelFilter.contains(keyframe->colorLabel())) { return keyframe; } } } return keyframe; } void tryCompositeFrame(KisRasterKeyframeChannel *keyframes, KisKeyframeSP keyframe, KisPainter &gcFrame, KisPainter &gcDest, KisPaintDeviceSP tintSource, int opacity, const QRect &rect) { if (keyframe.isNull() || opacity == OPACITY_TRANSPARENT_U8) return; keyframes->fetchFrame(keyframe, gcFrame.device()); gcFrame.bitBlt(rect.topLeft(), tintSource, rect); gcDest.setOpacity(opacity); gcDest.bitBlt(rect.topLeft(), gcFrame.device(), rect); } void refreshConfig() { KisImageConfig config(true); numberOfSkins = config.numberOfOnionSkins(); tintFactor = config.onionSkinTintFactor(); backwardTintColor = config.onionSkinTintColorBackward(); forwardTintColor = config.onionSkinTintColorForward(); backwardOpacities.resize(numberOfSkins); forwardOpacities.resize(numberOfSkins); const int mainState = (int) config.onionSkinState(0); const qreal scaleFactor = mainState * config.onionSkinOpacity(0) / 255.0; for (int i = 0; i < numberOfSkins; i++) { int backwardState = (int) config.onionSkinState(-(i + 1)); int forwardState = (int) config.onionSkinState(i + 1); backwardOpacities[i] = scaleFactor * backwardState * config.onionSkinOpacity(-(i + 1)); forwardOpacities[i] = scaleFactor * forwardState * config.onionSkinOpacity(i + 1); } configSeqNo++; } }; KisOnionSkinCompositor *KisOnionSkinCompositor::instance() { return s_instance; } KisOnionSkinCompositor::KisOnionSkinCompositor() : m_d(new Private) { m_d->refreshConfig(); } KisOnionSkinCompositor::~KisOnionSkinCompositor() {} int KisOnionSkinCompositor::configSeqNo() const { return m_d->configSeqNo; } void KisOnionSkinCompositor::setColorLabelFilter(QList colors) { m_d->colorLabelFilter = colors; } void KisOnionSkinCompositor::composite(const KisPaintDeviceSP sourceDevice, KisPaintDeviceSP targetDevice, const QRect& rect) { KisRasterKeyframeChannel *keyframes = sourceDevice->keyframeChannel(); KisPaintDeviceSP frameDevice = new KisPaintDevice(sourceDevice->colorSpace()); KisPainter gcFrame(frameDevice); QBitArray channelFlags = targetDevice->colorSpace()->channelFlags(true, false); gcFrame.setChannelFlags(channelFlags); gcFrame.setOpacity(m_d->tintFactor); KisPaintDeviceSP backwardTintDevice = m_d->setUpTintDevice(m_d->backwardTintColor, sourceDevice->colorSpace()); KisPaintDeviceSP forwardTintDevice = m_d->setUpTintDevice(m_d->forwardTintColor, sourceDevice->colorSpace()); KisPainter gcDest(targetDevice); gcDest.setCompositeOp(sourceDevice->colorSpace()->compositeOp(COMPOSITE_BEHIND)); KisKeyframeSP keyframeBck; KisKeyframeSP keyframeFwd; int time = sourceDevice->defaultBounds()->currentTime(); - keyframeBck = keyframeFwd = keyframes->activeKeyframeAt(time); + keyframeBck = keyframeFwd = keyframes->visibleKeyframeAt(time); for (int offset = 1; offset <= m_d->numberOfSkins; offset++) { keyframeBck = m_d->getNextFrameToComposite(keyframes, keyframeBck, true); keyframeFwd = m_d->getNextFrameToComposite(keyframes, keyframeFwd, false); if (!keyframeBck.isNull()) { m_d->tryCompositeFrame(keyframes, keyframeBck, gcFrame, gcDest, backwardTintDevice, m_d->skinOpacity(-offset), rect); } if (!keyframeFwd.isNull()) { m_d->tryCompositeFrame(keyframes, keyframeFwd, gcFrame, gcDest, forwardTintDevice, m_d->skinOpacity(offset), rect); } } } QRect KisOnionSkinCompositor::calculateFullExtent(const KisPaintDeviceSP device) { QRect rect; KisRasterKeyframeChannel *channel = device->keyframeChannel(); if (!channel) return rect; KisKeyframeSP keyframe = channel->firstKeyframe(); while (keyframe) { rect |= channel->frameExtents(keyframe); keyframe = channel->nextKeyframe(keyframe); } return rect; } QRect KisOnionSkinCompositor::calculateExtent(const KisPaintDeviceSP device) { QRect rect; KisKeyframeSP keyframeBck; KisKeyframeSP keyframeFwd; KisRasterKeyframeChannel *channel = device->keyframeChannel(); keyframeBck = keyframeFwd = channel->activeKeyframeAt(device->defaultBounds()->currentTime()); for (int offset = 1; offset <= m_d->numberOfSkins; offset++) { if (!keyframeBck.isNull()) { keyframeBck = channel->previousKeyframe(keyframeBck); if (!keyframeBck.isNull()) { rect |= channel->frameExtents(keyframeBck); } } if (!keyframeFwd.isNull()) { keyframeFwd = channel->nextKeyframe(keyframeFwd); if (!keyframeFwd.isNull()) { rect |= channel->frameExtents(keyframeFwd); } } } return rect; } void KisOnionSkinCompositor::configChanged() { m_d->refreshConfig(); emit sigOnionSkinChanged(); } diff --git a/libs/image/tests/kis_keyframing_test.cpp b/libs/image/tests/kis_keyframing_test.cpp index 715da19c88..b3d3afda95 100644 --- a/libs/image/tests/kis_keyframing_test.cpp +++ b/libs/image/tests/kis_keyframing_test.cpp @@ -1,535 +1,581 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * 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_keyframing_test.h" #include #include #include "kis_paint_device_frames_interface.h" #include "kis_keyframe_channel.h" #include "kis_scalar_keyframe_channel.h" #include "kis_raster_keyframe_channel.h" #include "kis_node.h" #include "kis_time_range.h" #include "kundo2command.h" #include #include "testing_timed_default_bounds.h" void KisKeyframingTest::initTestCase() { cs = KoColorSpaceRegistry::instance()->rgb8(); red = new quint8[cs->pixelSize()]; green = new quint8[cs->pixelSize()]; blue = new quint8[cs->pixelSize()]; cs->fromQColor(Qt::red, red); cs->fromQColor(Qt::green, green); cs->fromQColor(Qt::blue, blue); } void KisKeyframingTest::cleanupTestCase() { delete[] red; delete[] green; delete[] blue; } void KisKeyframingTest::testScalarChannel() { KisScalarKeyframeChannel *channel = new KisScalarKeyframeChannel(KoID(""), -17, 31, 0); KisKeyframeSP key; bool ok; QCOMPARE(channel->hasScalarValue(), true); QCOMPARE(channel->minScalarValue(), -17.0); QCOMPARE(channel->maxScalarValue(), 31.0); QVERIFY(channel->keyframeAt(0) == 0); // Adding new keyframe key = channel->addKeyframe(42); channel->setScalarValue(key, 7.0); key = channel->keyframeAt(42); QCOMPARE(channel->scalarValue(key), 7.0); // Copying a keyframe KisKeyframeSP key2 = channel->copyKeyframe(key, 13); QVERIFY(key2 != 0); QVERIFY(channel->keyframeAt(13) == key2); QCOMPARE(channel->scalarValue(key2), 7.0); // Adding a keyframe where one exists key2 = channel->addKeyframe(13); QVERIFY(key2 != key); QCOMPARE(channel->keyframeCount(), 2); // Moving keyframes ok = channel->moveKeyframe(key, 10); QCOMPARE(ok, true); QVERIFY(channel->keyframeAt(42) == 0); key = channel->keyframeAt(10); QCOMPARE(channel->scalarValue(key), 7.0); // Moving a keyframe where another one exists ok = channel->moveKeyframe(key, 13); QCOMPARE(ok, true); QVERIFY(channel->keyframeAt(13) != key2); // Deleting a keyframe channel->deleteKeyframe(key); QVERIFY(channel->keyframeAt(10) == 0); QCOMPARE(channel->keyframeCount(), 0); delete channel; } void KisKeyframingTest::testScalarChannelUndoRedo() { KisScalarKeyframeChannel *channel = new KisScalarKeyframeChannel(KoID(""), -17, 31, 0); KisKeyframeSP key; QCOMPARE(channel->hasScalarValue(), true); QCOMPARE(channel->minScalarValue(), -17.0); QCOMPARE(channel->maxScalarValue(), 31.0); QVERIFY(channel->keyframeAt(0) == 0); // Adding new keyframe KUndo2Command addCmd; key = channel->addKeyframe(42, &addCmd); channel->setScalarValue(key, 7.0, &addCmd); key = channel->keyframeAt(42); QCOMPARE(channel->scalarValue(key), 7.0); addCmd.undo(); KisKeyframeSP newKey; newKey = channel->keyframeAt(42); QVERIFY(!newKey); addCmd.redo(); newKey = channel->keyframeAt(42); QVERIFY(newKey); QCOMPARE(channel->scalarValue(key), 7.0); QCOMPARE(channel->scalarValue(newKey), 7.0); delete channel; } void KisKeyframingTest::testScalarInterpolation() { KisScalarKeyframeChannel *channel = new KisScalarKeyframeChannel(KoID(""), 0, 30, 0); KisKeyframeSP key1 = channel->addKeyframe(0); channel->setScalarValue(key1, 15); KisKeyframeSP key2 = channel->addKeyframe(10); channel->setScalarValue(key2, 30); // Constant key1->setInterpolationMode(KisKeyframe::Constant); QCOMPARE(channel->interpolatedValue(4), 15.0f); // Bezier key1->setInterpolationMode(KisKeyframe::Bezier); key1->setInterpolationTangents(QPointF(), QPointF(1,4)); key2->setInterpolationTangents(QPointF(-4,2), QPointF()); QVERIFY(qAbs(channel->interpolatedValue(4) - 24.9812f) < 0.1f); // Bezier, self-intersecting curve (auto-correct) channel->setScalarValue(key2, 15); key1->setInterpolationTangents(QPointF(), QPointF(13,10)); key2->setInterpolationTangents(QPointF(-13,10), QPointF()); QVERIFY(qAbs(channel->interpolatedValue(5) - 20.769f) < 0.1f); // Bezier, result outside allowed range (clamp) channel->setScalarValue(key2, 15); key1->setInterpolationTangents(QPointF(), QPointF(0, 50)); key2->setInterpolationTangents(QPointF(0, 50), QPointF()); QCOMPARE(channel->interpolatedValue(5), 30.0f); delete channel; } void KisKeyframingTest::testRasterChannel() { TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds(); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->setDefaultBounds(bounds); KisRasterKeyframeChannel * channel = dev->createKeyframeChannel(KoID()); QCOMPARE(channel->hasScalarValue(), false); QCOMPARE(channel->keyframeCount(), 1); QCOMPARE(dev->framesInterface()->frames().count(), 1); QCOMPARE(channel->frameIdAt(0), 0); QVERIFY(channel->keyframeAt(0) != 0); KisKeyframeSP key_0 = channel->keyframeAt(0); // New keyframe KisKeyframeSP key_10 = channel->addKeyframe(10); QCOMPARE(channel->keyframeCount(), 2); QCOMPARE(dev->framesInterface()->frames().count(), 2); QVERIFY(channel->frameIdAt(10) != 0); dev->fill(0, 0, 512, 512, red); QImage thumb1a = dev->createThumbnail(50, 50); bounds->testingSetTime(10); dev->fill(0, 0, 512, 512, green); QImage thumb2a = dev->createThumbnail(50, 50); bounds->testingSetTime(0); QImage thumb1b = dev->createThumbnail(50, 50); QVERIFY(thumb2a != thumb1a); QVERIFY(thumb1b == thumb1a); // Duplicate keyframe KisKeyframeSP key_20 = channel->copyKeyframe(key_0, 20); bounds->testingSetTime(20); QImage thumb3a = dev->createThumbnail(50, 50); QVERIFY(thumb3a == thumb1b); dev->fill(0, 0, 512, 512, blue); QImage thumb3b = dev->createThumbnail(50, 50); bounds->testingSetTime(0); QImage thumb1c = dev->createThumbnail(50, 50); QVERIFY(thumb3b != thumb3a); QVERIFY(thumb1c == thumb1b); // Delete keyrame QCOMPARE(channel->keyframeCount(), 3); QCOMPARE(dev->framesInterface()->frames().count(), 3); channel->deleteKeyframe(key_20); QCOMPARE(channel->keyframeCount(), 2); QCOMPARE(dev->framesInterface()->frames().count(), 2); QVERIFY(channel->keyframeAt(20) == 0); channel->deleteKeyframe(key_10); QCOMPARE(channel->keyframeCount(), 1); QCOMPARE(dev->framesInterface()->frames().count(), 1); QVERIFY(channel->keyframeAt(10) == 0); channel->deleteKeyframe(key_0); QCOMPARE(channel->keyframeCount(), 1); QCOMPARE(dev->framesInterface()->frames().count(), 1); QVERIFY(channel->keyframeAt(0) != 0); } void KisKeyframingTest::testChannelSignals() { KisScalarKeyframeChannel *channel = new KisScalarKeyframeChannel(KoID(""), -17, 31, 0); KisKeyframeSP key; KisKeyframeSP resKey; qRegisterMetaType("KisKeyframeSP"); QSignalSpy spyPreAdd(channel, SIGNAL(sigKeyframeAboutToBeAdded(KisKeyframeSP))); QSignalSpy spyPostAdd(channel, SIGNAL(sigKeyframeAdded(KisKeyframeSP))); QSignalSpy spyPreRemove(channel, SIGNAL(sigKeyframeAboutToBeRemoved(KisKeyframeSP))); QSignalSpy spyPostRemove(channel, SIGNAL(sigKeyframeRemoved(KisKeyframeSP))); QSignalSpy spyPreMove(channel, SIGNAL(sigKeyframeAboutToBeMoved(KisKeyframeSP,int))); QSignalSpy spyPostMove(channel, SIGNAL(sigKeyframeMoved(KisKeyframeSP, int))); QVERIFY(spyPreAdd.isValid()); QVERIFY(spyPostAdd.isValid()); QVERIFY(spyPreRemove.isValid()); QVERIFY(spyPostRemove.isValid()); QVERIFY(spyPreMove.isValid()); QVERIFY(spyPostMove.isValid()); // Adding a keyframe QCOMPARE(spyPreAdd.count(), 0); QCOMPARE(spyPostAdd.count(), 0); key = channel->addKeyframe(10); QCOMPARE(spyPreAdd.count(), 1); QCOMPARE(spyPostAdd.count(), 1); resKey = spyPreAdd.at(0).at(0).value(); QVERIFY(resKey == key); resKey = spyPostAdd.at(0).at(0).value(); QVERIFY(resKey == key); // Moving a keyframe QCOMPARE(spyPreMove.count(), 0); QCOMPARE(spyPostMove.count(), 0); channel->moveKeyframe(key, 15); QCOMPARE(spyPreMove.count(), 1); QCOMPARE(spyPostMove.count(), 1); resKey = spyPreMove.at(0).at(0).value(); QVERIFY(resKey == key); QCOMPARE(spyPreMove.at(0).at(1).toInt(), 15); resKey = spyPostMove.at(0).at(0).value(); QVERIFY(resKey == key); // No-op move (no signals) channel->moveKeyframe(key, 15); QCOMPARE(spyPreMove.count(), 1); QCOMPARE(spyPostMove.count(), 1); // Deleting a keyframe QCOMPARE(spyPreRemove.count(), 0); QCOMPARE(spyPostRemove.count(), 0); channel->deleteKeyframe(key); QCOMPARE(spyPreRemove.count(), 1); QCOMPARE(spyPostRemove.count(), 1); delete channel; } void KisKeyframingTest::testRasterFrameFetching() { TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds(); KisPaintDeviceSP dev = new KisPaintDevice(cs); KisPaintDeviceSP devTarget = new KisPaintDevice(cs); dev->setDefaultBounds(bounds); KisRasterKeyframeChannel * channel = dev->createKeyframeChannel(KoID()); channel->addKeyframe(0); channel->addKeyframe(10); channel->addKeyframe(50); bounds->testingSetTime(0); dev->fill(0, 0, 512, 512, red); QImage frame1 = dev->createThumbnail(50, 50); bounds->testingSetTime(10); dev->fill(0, 256, 512, 512, green); QImage frame2 = dev->createThumbnail(50, 50); bounds->testingSetTime(50); dev->fill(0, 0, 256, 512, blue); QImage frame3 = dev->createThumbnail(50, 50); bounds->testingSetTime(10); KisKeyframeSP keyframe = channel->activeKeyframeAt(0); channel->fetchFrame(keyframe, devTarget); QImage fetched1 = devTarget->createThumbnail(50, 50); keyframe = channel->activeKeyframeAt(10); channel->fetchFrame(keyframe, devTarget); QImage fetched2 = devTarget->createThumbnail(50, 50); keyframe = channel->activeKeyframeAt(50); channel->fetchFrame(keyframe, devTarget); QImage fetched3 = devTarget->createThumbnail(50, 50); QVERIFY(fetched1 == frame1); QVERIFY(fetched2 == frame2); QVERIFY(fetched3 == frame3); } void KisKeyframingTest::testDeleteFirstRasterChannel() { // Test Plan: // // delete // undo delete // move // undo move TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds(); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->setDefaultBounds(bounds); KisRasterKeyframeChannel * channel = dev->createKeyframeChannel(KoID()); QCOMPARE(channel->hasScalarValue(), false); QCOMPARE(channel->keyframeCount(), 1); QCOMPARE(dev->framesInterface()->frames().count(), 1); QCOMPARE(channel->frameIdAt(0), 0); QVERIFY(channel->keyframeAt(0) != 0); KisKeyframeSP key_0 = channel->keyframeAt(0); { KUndo2Command cmd; bool deleteResult = channel->deleteKeyframe(key_0, &cmd); QVERIFY(deleteResult); QCOMPARE(dev->framesInterface()->frames().count(), 1); QVERIFY(channel->frameIdAt(0) != 0); QVERIFY(channel->keyframeAt(0)); QVERIFY(channel->keyframeAt(0) != key_0); cmd.undo(); QCOMPARE(dev->framesInterface()->frames().count(), 1); QVERIFY(channel->frameIdAt(0) == 0); QVERIFY(channel->keyframeAt(0)); QVERIFY(channel->keyframeAt(0) == key_0); } { KUndo2Command cmd; bool moveResult = channel->moveKeyframe(key_0, 1, &cmd); QVERIFY(moveResult); QCOMPARE(dev->framesInterface()->frames().count(), 2); QVERIFY(channel->frameIdAt(0) != 0); QVERIFY(channel->frameIdAt(1) == 0); QVERIFY(channel->keyframeAt(0)); QVERIFY(channel->keyframeAt(1)); QVERIFY(channel->keyframeAt(0) != key_0); QVERIFY(channel->keyframeAt(1) == key_0); cmd.undo(); QCOMPARE(dev->framesInterface()->frames().count(), 1); QVERIFY(channel->frameIdAt(0) == 0); QVERIFY(channel->keyframeAt(0)); QVERIFY(channel->keyframeAt(0) == key_0); } } void KisKeyframingTest::testAffectedFrames() { KisScalarKeyframeChannel *channel = new KisScalarKeyframeChannel(KoID(""), -17, 31, 0); KisFrameSet frames; channel->addKeyframe(10); channel->addKeyframe(20); channel->addKeyframe(30); // At a keyframe frames = channel->affectedFrames(20); QCOMPARE(frames, KisFrameSet::between(20, 29)); // Between frames frames = channel->affectedFrames(25); QCOMPARE(frames, KisFrameSet::between(20, 29)); // Before first frame frames = channel->affectedFrames(5); QCOMPARE(frames, KisFrameSet::between(0, 9)); // After last frame frames = channel->affectedFrames(35); QCOMPARE(frames, KisFrameSet::infiniteFrom(30)); // Linked keyframes KisPaintDeviceSP dev = new KisPaintDevice(cs); KisRasterKeyframeChannel * rasterChannel = dev->createKeyframeChannel(KoID()); auto key0 = rasterChannel->addKeyframe(0); auto key5 = rasterChannel->addKeyframe(5); auto key10 = rasterChannel->linkKeyframe(key0, 10, nullptr); QCOMPARE(rasterChannel->affectedFrames(5), KisFrameSet::between(5,9)); KisFrameSet result = rasterChannel->affectedFrames(1); QCOMPARE(result, KisFrameSet::between(0, 4) | KisFrameSet::infiniteFrom(10)); result = rasterChannel->affectedFrames(15); QCOMPARE(result, KisFrameSet::between(0, 4) | KisFrameSet::infiniteFrom(10)); } void KisKeyframingTest::testMovingFrames() { TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds(); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->setDefaultBounds(bounds); KisRasterKeyframeChannel * srcChannel = dev->createKeyframeChannel(KoID()); srcChannel->addKeyframe(0); srcChannel->addKeyframe(10); srcChannel->addKeyframe(50); KisPaintDeviceSP dev2 = new KisPaintDevice(*dev, KritaUtils::CopyAllFrames); KisRasterKeyframeChannel * dstChannel = dev->keyframeChannel(); for (int i = 0; i < 1000; i++) { const int srcTime = 50 + i; const int dstTime = 60 + i; const int src2Time = 51 + i; { KUndo2Command parentCommand; KisKeyframeSP srcKeyframe = srcChannel->keyframeAt(srcTime); KIS_ASSERT(srcKeyframe); dstChannel->copyExternalKeyframe(srcChannel, srcTime, dstTime, &parentCommand); srcChannel->deleteKeyframe(srcKeyframe, &parentCommand); } for (int j = qMax(0, i-15); j < i+5; ++j) { bounds->testingSetTime(j); QRect rc1 = dev->extent(); QRect rc2 = dev2->extent(); Q_UNUSED(rc1); Q_UNUSED(rc2); } { KUndo2Command parentCommand; KisKeyframeSP dstKeyframe = dstChannel->keyframeAt(dstTime); KIS_ASSERT(dstKeyframe); srcChannel->copyExternalKeyframe(dstChannel, dstTime, src2Time, &parentCommand); dstChannel->deleteKeyframe(dstKeyframe, &parentCommand); } } } +void KisKeyframingTest::testCycles() +{ + TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds(); + + KisPaintDeviceSP dev = new KisPaintDevice(cs); + dev->setDefaultBounds(bounds); + KisRasterKeyframeChannel *channel = dev->createKeyframeChannel(KoID()); + + KisKeyframeSP frame10 = channel->addKeyframe(10); + channel->addKeyframe(12); + KisKeyframeSP frame16 = channel->addKeyframe(16); + channel->addKeyframe(20); + + auto cmd = channel->createCycle(frame10, frame16); + cmd->redo(); + + // Cycled range can be queried from by any frame within it + QCOMPARE(channel->cycledRangeAt(9), KisTimeSpan()); + QCOMPARE(channel->cycledRangeAt(20), KisTimeSpan()); + QCOMPARE(channel->cycledRangeAt(10), KisTimeSpan(10, 19)); + QCOMPARE(channel->cycledRangeAt(19), KisTimeSpan(10, 19)); + + channel->addRepeat(30, frame10); + + // Repeats also resolve to the original cycled range + QCOMPARE(channel->cycledRangeAt(29), KisTimeSpan()); + QCOMPARE(channel->cycledRangeAt(30), KisTimeSpan(10, 19)); + QCOMPARE(channel->cycledRangeAt(50), KisTimeSpan(10, 19)); + QCOMPARE(channel->cycledRangeAt(100), KisTimeSpan(10, 19)); + + // Affected frames contain original frames and repeats + QCOMPARE(channel->affectedFrames(15), KisFrameSet::between(12, 15) | KisFrameSet::infiniteFrom(32)); + QCOMPARE(channel->affectedFrames(50), KisFrameSet::between(10, 11) | KisFrameSet::infiniteFrom(30)); + + // All repeats within the queried range are reported as identical. Original is always included. + QCOMPARE(channel->identicalFrames(50, KisTimeSpan(40, 60)), KisFrameSet({{10, 11}, {40, 41}, {50, 51}, {60, 60}})); + + // Repeat ends at the next keyframe + channel->addKeyframe(42); + QCOMPARE(channel->cycledRangeAt(41), KisTimeSpan(10, 19)); + QCOMPARE(channel->cycledRangeAt(42), KisTimeSpan()); + + // Finitely many repeats are all included separately as affected + QCOMPARE(channel->affectedFrames(40), KisFrameSet({{10, 11}, {30, 31}, {40, 41}})); +} + QTEST_MAIN(KisKeyframingTest) diff --git a/libs/image/tests/kis_keyframing_test.h b/libs/image/tests/kis_keyframing_test.h index dbad43ce88..af73361d4c 100644 --- a/libs/image/tests/kis_keyframing_test.h +++ b/libs/image/tests/kis_keyframing_test.h @@ -1,55 +1,57 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * 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_KEYFRAMING_TEST_H #define KIS_KEYFRAMING_TEST_H #include #include "KoColor.h" class KisKeyframingTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testScalarChannel(); void testScalarChannelUndoRedo(); void testScalarInterpolation(); void testRasterChannel(); void testChannelSignals(); void testRasterFrameFetching(); void testDeleteFirstRasterChannel(); void testAffectedFrames(); void cleanupTestCase(); void testMovingFrames(); + void testCycles(); + private: const KoColorSpace *cs; quint8* red; quint8* green; quint8* blue; }; #endif