diff --git a/libs/image/CMakeLists.txt b/libs/image/CMakeLists.txt index 2cb5a21ae5..04b90d1f08 100644 --- a/libs/image/CMakeLists.txt +++ b/libs/image/CMakeLists.txt @@ -1,380 +1,381 @@ add_subdirectory( tests ) add_subdirectory( tiles3 ) include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty ${CMAKE_CURRENT_SOURCE_DIR}/brushengine ${CMAKE_CURRENT_SOURCE_DIR}/commands ${CMAKE_CURRENT_SOURCE_DIR}/commands_new ${CMAKE_CURRENT_SOURCE_DIR}/filter ${CMAKE_CURRENT_SOURCE_DIR}/floodfill ${CMAKE_CURRENT_SOURCE_DIR}/generator ${CMAKE_CURRENT_SOURCE_DIR}/layerstyles ${CMAKE_CURRENT_SOURCE_DIR}/processing ${CMAKE_SOURCE_DIR}/sdk/tests ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ) if(FFTW3_FOUND) include_directories(${FFTW3_INCLUDE_DIR}) endif() if(HAVE_VC) include_directories(SYSTEM ${Vc_INCLUDE_DIR} ${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS}) ko_compile_for_all_implementations(__per_arch_circle_mask_generator_objs kis_brush_mask_applicator_factories.cpp) else() set(__per_arch_circle_mask_generator_objs kis_brush_mask_applicator_factories.cpp) endif() set(kritaimage_LIB_SRCS tiles3/kis_tile.cc tiles3/kis_tile_data.cc tiles3/kis_tile_data_store.cc tiles3/kis_tile_data_pooler.cc tiles3/kis_tiled_data_manager.cc tiles3/KisTiledExtentManager.cpp tiles3/kis_memento_manager.cc tiles3/kis_hline_iterator.cpp tiles3/kis_vline_iterator.cpp tiles3/kis_random_accessor.cc tiles3/swap/kis_abstract_compression.cpp tiles3/swap/kis_lzf_compression.cpp tiles3/swap/kis_abstract_tile_compressor.cpp tiles3/swap/kis_legacy_tile_compressor.cpp tiles3/swap/kis_tile_compressor_2.cpp tiles3/swap/kis_chunk_allocator.cpp tiles3/swap/kis_memory_window.cpp tiles3/swap/kis_swapped_data_store.cpp tiles3/swap/kis_tile_data_swapper.cpp kis_distance_information.cpp kis_painter.cc kis_painter_blt_multi_fixed.cpp kis_marker_painter.cpp KisPrecisePaintDeviceWrapper.cpp kis_progress_updater.cpp brushengine/kis_paint_information.cc brushengine/kis_random_source.cpp brushengine/KisPerStrokeRandomSource.cpp brushengine/kis_stroke_random_source.cpp brushengine/kis_paintop.cc brushengine/kis_paintop_factory.cpp brushengine/kis_paintop_preset.cpp brushengine/kis_paintop_registry.cc brushengine/kis_paintop_settings.cpp brushengine/kis_paintop_settings_update_proxy.cpp brushengine/kis_paintop_utils.cpp brushengine/kis_no_size_paintop_settings.cpp brushengine/kis_locked_properties.cc brushengine/kis_locked_properties_proxy.cpp brushengine/kis_locked_properties_server.cpp brushengine/kis_paintop_config_widget.cpp brushengine/kis_uniform_paintop_property.cpp brushengine/kis_combo_based_paintop_property.cpp brushengine/kis_slider_based_paintop_property.cpp brushengine/kis_standard_uniform_properties_factory.cpp brushengine/KisStrokeSpeedMeasurer.cpp brushengine/KisPaintopSettingsIds.cpp commands/kis_deselect_global_selection_command.cpp commands/KisDeselectActiveSelectionCommand.cpp commands/kis_image_change_layers_command.cpp commands/kis_image_change_visibility_command.cpp commands/kis_image_command.cpp commands/kis_image_set_projection_color_space_command.cpp commands/kis_image_layer_add_command.cpp commands/kis_image_layer_move_command.cpp commands/kis_image_layer_remove_command.cpp commands/kis_image_layer_remove_command_impl.cpp commands/kis_image_lock_command.cpp commands/kis_node_command.cpp commands/kis_node_compositeop_command.cpp commands/kis_node_opacity_command.cpp commands/kis_node_property_list_command.cpp commands/kis_reselect_global_selection_command.cpp commands/KisReselectActiveSelectionCommand.cpp commands/kis_set_global_selection_command.cpp commands/KisNodeRenameCommand.cpp commands_new/kis_saved_commands.cpp commands_new/kis_processing_command.cpp commands_new/kis_image_resize_command.cpp commands_new/kis_image_set_resolution_command.cpp commands_new/kis_node_move_command2.cpp commands_new/kis_set_layer_style_command.cpp commands_new/kis_selection_move_command2.cpp commands_new/kis_update_command.cpp commands_new/kis_switch_current_time_command.cpp commands_new/kis_change_projection_color_command.cpp commands_new/kis_activate_selection_mask_command.cpp commands_new/kis_transaction_based_command.cpp commands_new/KisHoldUIUpdatesCommand.cpp processing/kis_do_nothing_processing_visitor.cpp processing/kis_simple_processing_visitor.cpp processing/kis_convert_color_space_processing_visitor.cpp + processing/kis_assign_profile_processing_visitor.cpp processing/kis_crop_processing_visitor.cpp processing/kis_crop_selections_processing_visitor.cpp processing/kis_transform_processing_visitor.cpp processing/kis_mirror_processing_visitor.cpp processing/KisSelectionBasedProcessingHelper.cpp filter/kis_filter.cc filter/kis_filter_category_ids.cpp filter/kis_filter_configuration.cc filter/kis_color_transformation_configuration.cc filter/kis_filter_registry.cc filter/kis_color_transformation_filter.cc generator/kis_generator.cpp generator/kis_generator_layer.cpp generator/kis_generator_registry.cpp floodfill/kis_fill_interval_map.cpp floodfill/kis_scanline_fill.cpp lazybrush/kis_min_cut_worker.cpp lazybrush/kis_lazy_fill_tools.cpp lazybrush/kis_multiway_cut.cpp lazybrush/KisWatershedWorker.cpp lazybrush/kis_colorize_mask.cpp lazybrush/kis_colorize_stroke_strategy.cpp KisDelayedUpdateNodeInterface.cpp KisCroppedOriginalLayerInterface.cpp 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_config_widget.cpp kis_convolution_kernel.cc kis_convolution_painter.cc kis_gaussian_kernel.cpp kis_edge_detection_kernel.cpp kis_cubic_curve.cpp kis_default_bounds.cpp kis_default_bounds_base.cpp kis_effect_mask.cc kis_fast_math.cpp kis_fill_painter.cc kis_filter_mask.cpp kis_filter_strategy.cc kis_transform_mask.cpp kis_transform_mask_params_interface.cpp kis_recalculate_transform_mask_job.cpp kis_recalculate_generator_layer_job.cpp kis_transform_mask_params_factory_registry.cpp kis_safe_transform.cpp kis_gradient_painter.cc kis_gradient_shape_strategy.cpp kis_cached_gradient_shape_strategy.cpp kis_polygonal_gradient_shape_strategy.cpp kis_iterator_ng.cpp kis_async_merger.cpp kis_merge_walker.cc kis_updater_context.cpp kis_update_job_item.cpp kis_stroke_strategy_undo_command_based.cpp kis_simple_stroke_strategy.cpp KisRunnableBasedStrokeStrategy.cpp KisRunnableStrokeJobDataBase.cpp KisRunnableStrokeJobData.cpp KisRunnableStrokeJobsInterface.cpp KisFakeRunnableStrokeJobsExecutor.cpp kis_stroke_job_strategy.cpp kis_stroke_strategy.cpp kis_stroke.cpp kis_strokes_queue.cpp KisStrokesQueueMutatedJobInterface.cpp kis_simple_update_queue.cpp kis_update_scheduler.cpp kis_queues_progress_updater.cpp kis_composite_progress_proxy.cpp kis_sync_lod_cache_stroke_strategy.cpp kis_lod_capable_layer_offset.cpp kis_update_time_monitor.cpp KisImageConfigNotifier.cpp kis_group_layer.cc kis_count_visitor.cpp kis_histogram.cc kis_image_interfaces.cpp kis_image_animation_interface.cpp kis_time_range.cpp kis_node_graph_listener.cpp kis_image.cc kis_image_signal_router.cpp KisImageSignals.cpp kis_image_config.cpp kis_projection_updates_filter.cpp kis_suspend_projection_updates_stroke_strategy.cpp kis_regenerate_frame_stroke_strategy.cpp kis_switch_time_stroke_strategy.cpp kis_crop_saved_extra_data.cpp kis_timed_signal_threshold.cpp kis_layer.cc kis_indirect_painting_support.cpp kis_abstract_projection_plane.cpp kis_layer_projection_plane.cpp kis_layer_utils.cpp kis_mask_projection_plane.cpp kis_projection_leaf.cpp KisSafeNodeProjectionStore.cpp kis_mask.cc kis_base_mask_generator.cpp kis_rect_mask_generator.cpp kis_circle_mask_generator.cpp kis_gauss_circle_mask_generator.cpp kis_gauss_rect_mask_generator.cpp ${__per_arch_circle_mask_generator_objs} kis_curve_circle_mask_generator.cpp kis_curve_rect_mask_generator.cpp kis_math_toolbox.cpp kis_memory_statistics_server.cpp kis_name_server.cpp kis_node.cpp kis_node_facade.cpp kis_node_progress_proxy.cpp kis_busy_progress_indicator.cpp kis_node_visitor.cpp kis_paint_device.cc kis_paint_device_debug_utils.cpp kis_fixed_paint_device.cpp KisOptimizedByteArray.cpp kis_paint_layer.cc kis_perspective_math.cpp kis_pixel_selection.cpp kis_processing_information.cpp kis_properties_configuration.cc kis_random_accessor_ng.cpp kis_random_generator.cc kis_random_sub_accessor.cpp kis_wrapped_random_accessor.cpp kis_selection.cc KisSelectionUpdateCompressor.cpp kis_selection_mask.cpp kis_update_outline_job.cpp kis_update_selection_job.cpp kis_serializable_configuration.cc kis_transaction_data.cpp kis_transform_worker.cc kis_perspectivetransform_worker.cpp bsplines/kis_bspline_1d.cpp bsplines/kis_bspline_2d.cpp bsplines/kis_nu_bspline_2d.cpp kis_warptransform_worker.cc kis_cage_transform_worker.cpp kis_liquify_transform_worker.cpp kis_green_coordinates_math.cpp kis_transparency_mask.cc kis_undo_adapter.cpp kis_macro_based_undo_store.cpp kis_surrogate_undo_adapter.cpp kis_legacy_undo_adapter.cpp kis_post_execution_undo_adapter.cpp kis_processing_visitor.cpp kis_processing_applicator.cpp krita_utils.cpp kis_outline_generator.cpp kis_layer_composition.cpp kis_selection_filters.cpp KisProofingConfiguration.h KisRecycleProjectionsJob.cpp kis_keyframe.cpp kis_keyframe_channel.cpp kis_keyframe_commands.cpp kis_scalar_keyframe_channel.cpp kis_raster_keyframe_channel.cpp kis_onion_skin_compositor.cpp kis_onion_skin_cache.cpp kis_idle_watcher.cpp kis_psd_layer_style.cpp kis_layer_properties_icons.cpp layerstyles/kis_multiple_projection.cpp layerstyles/kis_layer_style_filter.cpp layerstyles/kis_layer_style_filter_environment.cpp layerstyles/kis_layer_style_filter_projection_plane.cpp layerstyles/kis_layer_style_projection_plane.cpp layerstyles/kis_ls_drop_shadow_filter.cpp layerstyles/kis_ls_satin_filter.cpp layerstyles/kis_ls_stroke_filter.cpp layerstyles/kis_ls_bevel_emboss_filter.cpp layerstyles/kis_ls_overlay_filter.cpp layerstyles/kis_ls_utils.cpp layerstyles/gimp_bump_map.cpp layerstyles/KisLayerStyleKnockoutBlower.cpp KisProofingConfiguration.cpp kis_node_query_path.cc ) set(einspline_SRCS 3rdparty/einspline/bspline_create.cpp 3rdparty/einspline/bspline_data.cpp 3rdparty/einspline/multi_bspline_create.cpp 3rdparty/einspline/nubasis.cpp 3rdparty/einspline/nubspline_create.cpp 3rdparty/einspline/nugrid.cpp ) add_library(kritaimage SHARED ${kritaimage_LIB_SRCS} ${einspline_SRCS}) generate_export_header(kritaimage BASE_NAME kritaimage) target_link_libraries(kritaimage PUBLIC kritaversion kritawidgets kritaglobal kritapsd kritaodf kritapigment kritacommand kritawidgetutils kritametadata Qt5::Concurrent ) target_link_libraries(kritaimage PUBLIC ${Boost_SYSTEM_LIBRARY}) if(NOT HAVE_CXX_ATOMICS_WITHOUT_LIB) if(NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB) target_link_libraries(kritaimage PUBLIC atomic) endif() endif() if(OPENEXR_FOUND) target_link_libraries(kritaimage PUBLIC ${OPENEXR_LIBRARIES}) endif() if(FFTW3_FOUND) target_link_libraries(kritaimage PRIVATE ${FFTW3_LIBRARIES}) endif() if(HAVE_VC) target_link_libraries(kritaimage PUBLIC ${Vc_LIBRARIES}) endif() if (NOT GSL_FOUND) message (WARNING "KRITA WARNING! No GNU Scientific Library was found! Krita's Shaped Gradients might be non-normalized! Please install GSL library.") else () target_link_libraries(kritaimage PRIVATE ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) endif () target_include_directories(kritaimage PUBLIC $ $ $ $ $ ) set_target_properties(kritaimage PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaimage ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/libs/image/kis_change_profile_visitor.h b/libs/image/kis_change_profile_visitor.h deleted file mode 100644 index 0f0928f3e7..0000000000 --- a/libs/image/kis_change_profile_visitor.h +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (c) 2006 Boudewijn Rempt - -/** - * The Change Profile visitor walks over all layers and if the current - * layer has the specified colorspace AND the specified old profile, sets - * the colorspace to the same colorspace with the NEW profile, without doing - * conversions. This is essential if you have loaded an image that didn't - * have an embedded profile to which you want to attach the right profile. - */ -class KRITAIMAGE_EXPORT KisChangeProfileVisitor : public KisNodeVisitor -{ -public: - - using KisNodeVisitor::visit; - - KisChangeProfileVisitor(const KoColorSpace * oldColorSpace, - const KoColorSpace *dstColorSpace) - : KisNodeVisitor() - , m_oldColorSpace(oldColorSpace) - , m_dstColorSpace(dstColorSpace) { - } - - ~KisChangeProfileVisitor() override { - } - - bool visit(KisExternalLayer *) override { - return true; - } - - bool visit(KisGroupLayer * layer) override { - // Clear the projection, we will have to re-render everything. - layer->resetCache(); - - KisLayerSP child = dynamic_cast(layer->firstChild().data()); - while (child) { - child->accept(*this); - child = dynamic_cast(child->nextSibling().data()); - } - return true; - } - - - bool visit(KisPaintLayer *layer) override { - return updatePaintDevice(layer); - } - - bool visit(KisGeneratorLayer *layer) override { - return updatePaintDevice(layer); - } - - bool visit(KisAdjustmentLayer * layer) override { - layer->resetCache(); - return true; - } - - bool visit(KisNode*) override { - return true; - } - bool visit(KisCloneLayer*) override { - return true; - } - bool visit(KisFilterMask*) override { - return true; - } - bool visit(KisTransformMask*) override { - return true; - } - bool visit(KisTransparencyMask*) override { - return true; - } - bool visit(KisSelectionMask*) override { - return true; - } - - bool visit(KisColorizeMask *mask) override { - if (mask->colorSpace()->colorModelId() == m_oldColorSpace->colorModelId()) { - mask->setProfile(m_dstColorSpace->profile()); - } - return true; - } - -private: - - bool updatePaintDevice(KisLayer *layer) { - if (!layer) return false; - if (!layer->paintDevice()) return false; - if (!layer->paintDevice()->colorSpace()) return false; - - const KoColorSpace *cs = layer->paintDevice()->colorSpace(); - - if (cs->colorModelId() == m_oldColorSpace->colorModelId()) { - layer->paintDevice()->setProfile(m_dstColorSpace->profile()); - if (layer->projection() != layer->paintDevice()) { - layer->projection()->setProfile(m_dstColorSpace->profile()); - } - } - - - return true; - } - - const KoColorSpace *m_oldColorSpace; - const KoColorSpace *m_dstColorSpace; -}; - -#endif // KIS_CHANGE_PROFILE_VISITOR_H_ - diff --git a/libs/image/kis_image.cc b/libs/image/kis_image.cc index 8bbf207bff..ece552e5e7 100644 --- a/libs/image/kis_image.cc +++ b/libs/image/kis_image.cc @@ -1,2095 +1,2142 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_image.h" #include // WORDS_BIGENDIAN #include #include #include #include #include #include #include #include #include #include #include "KoColorSpaceRegistry.h" #include "KoColor.h" #include "KoColorProfile.h" #include #include "KisProofingConfiguration.h" #include "kis_adjustment_layer.h" #include "kis_annotation.h" -#include "kis_change_profile_visitor.h" #include "kis_count_visitor.h" #include "kis_filter_strategy.h" #include "kis_group_layer.h" #include "commands/kis_image_commands.h" #include "kis_layer.h" #include "kis_meta_data_merge_strategy_registry.h" #include "kis_name_server.h" #include "kis_paint_layer.h" #include "kis_projection_leaf.h" #include "kis_painter.h" #include "kis_selection.h" #include "kis_transaction.h" #include "kis_meta_data_merge_strategy.h" #include "kis_memory_statistics_server.h" #include "kis_image_config.h" #include "kis_update_scheduler.h" #include "kis_image_signal_router.h" #include "kis_image_animation_interface.h" #include "kis_stroke_strategy.h" #include "kis_simple_stroke_strategy.h" #include "kis_image_barrier_locker.h" #include "kis_undo_stores.h" #include "kis_legacy_undo_adapter.h" #include "kis_post_execution_undo_adapter.h" #include "kis_transform_worker.h" #include "kis_processing_applicator.h" #include "processing/kis_crop_processing_visitor.h" #include "processing/kis_crop_selections_processing_visitor.h" #include "processing/kis_transform_processing_visitor.h" #include "processing/kis_convert_color_space_processing_visitor.h" +#include "processing/kis_assign_profile_processing_visitor.h" #include "commands_new/kis_image_resize_command.h" #include "commands_new/kis_image_set_resolution_command.h" #include "commands_new/kis_activate_selection_mask_command.h" #include "kis_composite_progress_proxy.h" #include "kis_layer_composition.h" #include "kis_wrapped_rect.h" #include "kis_crop_saved_extra_data.h" #include "kis_layer_utils.h" #include "kis_lod_transform.h" #include "kis_suspend_projection_updates_stroke_strategy.h" #include "kis_sync_lod_cache_stroke_strategy.h" #include "kis_projection_updates_filter.h" #include "kis_layer_projection_plane.h" #include "kis_update_time_monitor.h" #include "tiles3/kis_lockless_stack.h" #include #include #include "kis_time_range.h" #include "KisRunnableBasedStrokeStrategy.h" #include "KisRunnableStrokeJobData.h" #include "KisRunnableStrokeJobUtils.h" #include "KisRunnableStrokeJobsInterface.h" // #define SANITY_CHECKS #ifdef SANITY_CHECKS #define SANITY_CHECK_LOCKED(name) \ if (!locked()) warnKrita() << "Locking policy failed:" << name \ << "has been called without the image" \ "being locked"; #else #define SANITY_CHECK_LOCKED(name) #endif struct KisImageSPStaticRegistrar { KisImageSPStaticRegistrar() { qRegisterMetaType("KisImageSP"); } }; static KisImageSPStaticRegistrar __registrar; class KisImage::KisImagePrivate { public: KisImagePrivate(KisImage *_q, qint32 w, qint32 h, const KoColorSpace *c, KisUndoStore *undo, KisImageAnimationInterface *_animationInterface) : q(_q) , lockedForReadOnly(false) , width(w) , height(h) , colorSpace(c ? c : KoColorSpaceRegistry::instance()->rgb8()) , nserver(1) , undoStore(undo ? undo : new KisDumbUndoStore()) , legacyUndoAdapter(undoStore.data(), _q) , postExecutionUndoAdapter(undoStore.data(), _q) , signalRouter(_q) , animationInterface(_animationInterface) , scheduler(_q, _q) , axesCenter(QPointF(0.5, 0.5)) { { KisImageConfig cfg(true); if (cfg.enableProgressReporting()) { scheduler.setProgressProxy(&compositeProgressProxy); } // Each of these lambdas defines a new factory function. scheduler.setLod0ToNStrokeStrategyFactory( [=](bool forgettable) { return KisLodSyncPair( new KisSyncLodCacheStrokeStrategy(KisImageWSP(q), forgettable), KisSyncLodCacheStrokeStrategy::createJobsData(KisImageWSP(q))); }); scheduler.setSuspendUpdatesStrokeStrategyFactory( [=]() { return KisSuspendResumePair( new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), true), KisSuspendProjectionUpdatesStrokeStrategy::createSuspendJobsData(KisImageWSP(q))); }); scheduler.setResumeUpdatesStrokeStrategyFactory( [=]() { return KisSuspendResumePair( new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), false), KisSuspendProjectionUpdatesStrokeStrategy::createResumeJobsData(KisImageWSP(q))); }); } connect(q, SIGNAL(sigImageModified()), KisMemoryStatisticsServer::instance(), SLOT(notifyImageChanged())); } ~KisImagePrivate() { /** * Stop animation interface. It may use the rootLayer. */ delete animationInterface; /** * First delete the nodes, while strokes * and undo are still alive */ rootLayer.clear(); } KisImage *q; quint32 lockCount = 0; bool lockedForReadOnly; qint32 width; qint32 height; double xres = 1.0; double yres = 1.0; const KoColorSpace * colorSpace; KisProofingConfigurationSP proofingConfig; KisSelectionSP deselectedGlobalSelection; KisGroupLayerSP rootLayer; // The layers are contained in here KisSelectionMaskSP targetOverlaySelectionMask; // the overlay switching stroke will try to switch into this mask KisSelectionMaskSP overlaySelectionMask; QList compositions; KisNodeSP isolatedRootNode; bool wrapAroundModePermitted = false; KisNameServer nserver; QScopedPointer undoStore; KisLegacyUndoAdapter legacyUndoAdapter; KisPostExecutionUndoAdapter postExecutionUndoAdapter; vKisAnnotationSP annotations; QAtomicInt disableUIUpdateSignals; KisLocklessStack savedDisabledUIUpdates; KisProjectionUpdatesFilterSP projectionUpdatesFilter; KisImageSignalRouter signalRouter; KisImageAnimationInterface *animationInterface; KisUpdateScheduler scheduler; QAtomicInt disableDirtyRequests; KisCompositeProgressProxy compositeProgressProxy; bool blockLevelOfDetail = false; QPointF axesCenter; bool allowMasksOnRootNode = false; bool tryCancelCurrentStrokeAsync(); void notifyProjectionUpdatedInPatches(const QRect &rc, QVector &jobs); }; KisImage::KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace * colorSpace, const QString& name) : QObject(0) , KisShared() , m_d(new KisImagePrivate(this, width, height, colorSpace, undoStore, new KisImageAnimationInterface(this))) { // make sure KisImage belongs to the GUI thread moveToThread(qApp->thread()); connect(this, SIGNAL(sigInternalStopIsolatedModeRequested()), SLOT(stopIsolatedMode())); setObjectName(name); setRootLayer(new KisGroupLayer(this, "root", OPACITY_OPAQUE_U8)); } KisImage::~KisImage() { dbgImage << "deleting kisimage" << objectName(); /** * Request the tools to end currently running strokes */ waitForDone(); delete m_d; disconnect(); // in case Qt gets confused } KisImageSP KisImage::fromQImage(const QImage &image, KisUndoStore *undoStore) { const KoColorSpace *colorSpace = 0; switch (image.format()) { case QImage::Format_Invalid: case QImage::Format_Mono: case QImage::Format_MonoLSB: colorSpace = KoColorSpaceRegistry::instance()->graya8(); break; case QImage::Format_Indexed8: case QImage::Format_RGB32: case QImage::Format_ARGB32: case QImage::Format_ARGB32_Premultiplied: colorSpace = KoColorSpaceRegistry::instance()->rgb8(); break; case QImage::Format_RGB16: colorSpace = KoColorSpaceRegistry::instance()->rgb16(); break; case QImage::Format_ARGB8565_Premultiplied: case QImage::Format_RGB666: case QImage::Format_ARGB6666_Premultiplied: case QImage::Format_RGB555: case QImage::Format_ARGB8555_Premultiplied: case QImage::Format_RGB888: case QImage::Format_RGB444: case QImage::Format_ARGB4444_Premultiplied: case QImage::Format_RGBX8888: case QImage::Format_RGBA8888: case QImage::Format_RGBA8888_Premultiplied: colorSpace = KoColorSpaceRegistry::instance()->rgb8(); break; case QImage::Format_BGR30: case QImage::Format_A2BGR30_Premultiplied: case QImage::Format_RGB30: case QImage::Format_A2RGB30_Premultiplied: colorSpace = KoColorSpaceRegistry::instance()->rgb8(); break; case QImage::Format_Alpha8: colorSpace = KoColorSpaceRegistry::instance()->alpha8(); break; case QImage::Format_Grayscale8: colorSpace = KoColorSpaceRegistry::instance()->graya8(); break; #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) case QImage::Format_Grayscale16: colorSpace = KoColorSpaceRegistry::instance()->graya16(); break; #endif #if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0) case QImage::Format_RGBX64: case QImage::Format_RGBA64: case QImage::Format_RGBA64_Premultiplied: colorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), 0); break; #endif default: colorSpace = 0; } KisImageSP img = new KisImage(undoStore, image.width(), image.height(), colorSpace, i18n("Imported Image")); KisPaintLayerSP layer = new KisPaintLayer(img, img->nextLayerName(), 255); layer->paintDevice()->convertFromQImage(image, 0, 0, 0); img->addNode(layer.data(), img->rootLayer().data()); return img; } KisImage *KisImage::clone(bool exactCopy) { return new KisImage(*this, 0, exactCopy); } void KisImage::copyFromImage(const KisImage &rhs) { copyFromImageImpl(rhs, REPLACE); } void KisImage::copyFromImageImpl(const KisImage &rhs, int policy) { // make sure we choose exactly one from REPLACE and CONSTRUCT KIS_ASSERT_RECOVER_RETURN((policy & REPLACE) != (policy & CONSTRUCT)); // only when replacing do we need to emit signals #define EMIT_IF_NEEDED if (!(policy & REPLACE)) {} else emit if (policy & REPLACE) { // if we are constructing the image, these are already set if (m_d->width != rhs.width() || m_d->height != rhs.height()) { m_d->width = rhs.width(); m_d->height = rhs.height(); emit sigSizeChanged(QPointF(), QPointF()); } if (m_d->colorSpace != rhs.colorSpace()) { m_d->colorSpace = rhs.colorSpace(); emit sigColorSpaceChanged(m_d->colorSpace); } } // from KisImage::KisImage(const KisImage &, KisUndoStore *, bool) setObjectName(rhs.objectName()); if (m_d->xres != rhs.m_d->xres || m_d->yres != rhs.m_d->yres) { m_d->xres = rhs.m_d->xres; m_d->yres = rhs.m_d->yres; EMIT_IF_NEEDED sigResolutionChanged(m_d->xres, m_d->yres); } m_d->allowMasksOnRootNode = rhs.m_d->allowMasksOnRootNode; if (rhs.m_d->proofingConfig) { KisProofingConfigurationSP proofingConfig(new KisProofingConfiguration(*rhs.m_d->proofingConfig)); if (policy & REPLACE) { setProofingConfiguration(proofingConfig); } else { m_d->proofingConfig = proofingConfig; } } KisNodeSP newRoot = rhs.root()->clone(); newRoot->setGraphListener(this); newRoot->setImage(this); m_d->rootLayer = dynamic_cast(newRoot.data()); setRoot(newRoot); bool exactCopy = policy & EXACT_COPY; if (exactCopy || rhs.m_d->isolatedRootNode) { QQueue linearizedNodes; KisLayerUtils::recursiveApplyNodes(rhs.root(), [&linearizedNodes](KisNodeSP node) { linearizedNodes.enqueue(node); }); KisLayerUtils::recursiveApplyNodes(newRoot, [&linearizedNodes, exactCopy, &rhs, this](KisNodeSP node) { KisNodeSP refNode = linearizedNodes.dequeue(); if (exactCopy) { node->setUuid(refNode->uuid()); } if (rhs.m_d->isolatedRootNode && rhs.m_d->isolatedRootNode == refNode) { m_d->isolatedRootNode = node; } }); } KisLayerUtils::recursiveApplyNodes(newRoot, [](KisNodeSP node) { dbgImage << "Node: " << (void *)node.data(); }); m_d->compositions.clear(); Q_FOREACH (KisLayerCompositionSP comp, rhs.m_d->compositions) { m_d->compositions << toQShared(new KisLayerComposition(*comp, this)); } EMIT_IF_NEEDED sigLayersChangedAsync(); m_d->nserver = rhs.m_d->nserver; vKisAnnotationSP newAnnotations; Q_FOREACH (KisAnnotationSP annotation, rhs.m_d->annotations) { newAnnotations << annotation->clone(); } m_d->annotations = newAnnotations; KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->projectionUpdatesFilter); KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableUIUpdateSignals); KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableDirtyRequests); m_d->blockLevelOfDetail = rhs.m_d->blockLevelOfDetail; /** * The overlay device is not inherited when cloning the image! */ if (rhs.m_d->overlaySelectionMask) { const QRect dirtyRect = rhs.m_d->overlaySelectionMask->extent(); m_d->rootLayer->setDirty(dirtyRect); } #undef EMIT_IF_NEEDED } KisImage::KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy) : KisNodeFacade(), KisNodeGraphListener(), KisShared(), m_d(new KisImagePrivate(this, rhs.width(), rhs.height(), rhs.colorSpace(), undoStore ? undoStore : new KisDumbUndoStore(), new KisImageAnimationInterface(*rhs.animationInterface(), this))) { // make sure KisImage belongs to the GUI thread moveToThread(qApp->thread()); connect(this, SIGNAL(sigInternalStopIsolatedModeRequested()), SLOT(stopIsolatedMode())); copyFromImageImpl(rhs, CONSTRUCT | (exactCopy ? EXACT_COPY : 0)); } void KisImage::aboutToAddANode(KisNode *parent, int index) { KisNodeGraphListener::aboutToAddANode(parent, index); SANITY_CHECK_LOCKED("aboutToAddANode"); } void KisImage::nodeHasBeenAdded(KisNode *parent, int index) { KisNodeGraphListener::nodeHasBeenAdded(parent, index); SANITY_CHECK_LOCKED("nodeHasBeenAdded"); m_d->signalRouter.emitNodeHasBeenAdded(parent, index); } void KisImage::aboutToRemoveANode(KisNode *parent, int index) { KisNodeSP deletedNode = parent->at(index); if (!dynamic_cast(deletedNode.data()) && deletedNode == m_d->isolatedRootNode) { emit sigInternalStopIsolatedModeRequested(); } KisNodeGraphListener::aboutToRemoveANode(parent, index); SANITY_CHECK_LOCKED("aboutToRemoveANode"); m_d->signalRouter.emitAboutToRemoveANode(parent, index); } void KisImage::nodeChanged(KisNode* node) { KisNodeGraphListener::nodeChanged(node); requestStrokeEnd(); m_d->signalRouter.emitNodeChanged(node); } void KisImage::invalidateAllFrames() { invalidateFrames(KisTimeRange::infinite(0), QRect()); } void KisImage::setOverlaySelectionMask(KisSelectionMaskSP mask) { if (m_d->targetOverlaySelectionMask == mask) return; m_d->targetOverlaySelectionMask = mask; struct UpdateOverlaySelectionStroke : public KisSimpleStrokeStrategy { UpdateOverlaySelectionStroke(KisImageSP image) : KisSimpleStrokeStrategy("update-overlay-selection-mask", kundo2_noi18n("update-overlay-selection-mask")), m_image(image) { this->enableJob(JOB_INIT, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); setClearsRedoOnStart(false); } void initStrokeCallback() { KisSelectionMaskSP oldMask = m_image->m_d->overlaySelectionMask; KisSelectionMaskSP newMask = m_image->m_d->targetOverlaySelectionMask; if (oldMask == newMask) return; KIS_SAFE_ASSERT_RECOVER_RETURN(!newMask || newMask->graphListener() == m_image); m_image->m_d->overlaySelectionMask = newMask; if (oldMask || newMask) { m_image->m_d->rootLayer->notifyChildMaskChanged(); } if (oldMask) { m_image->m_d->rootLayer->setDirtyDontResetAnimationCache(oldMask->extent()); } if (newMask) { newMask->setDirty(); } m_image->undoAdapter()->emitSelectionChanged(); } private: KisImageSP m_image; }; KisStrokeId id = startStroke(new UpdateOverlaySelectionStroke(this)); endStroke(id); } KisSelectionMaskSP KisImage::overlaySelectionMask() const { return m_d->overlaySelectionMask; } bool KisImage::hasOverlaySelectionMask() const { return m_d->overlaySelectionMask; } KisSelectionSP KisImage::globalSelection() const { KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask(); if (selectionMask) { return selectionMask->selection(); } else { return 0; } } void KisImage::setGlobalSelection(KisSelectionSP globalSelection) { KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask(); if (!globalSelection) { if (selectionMask) { removeNode(selectionMask); } } else { if (!selectionMask) { selectionMask = new KisSelectionMask(this); selectionMask->initSelection(m_d->rootLayer); addNode(selectionMask); // If we do not set the selection now, the setActive call coming next // can be very, very expensive, depending on the size of the image. selectionMask->setSelection(globalSelection); selectionMask->setActive(true); } else { selectionMask->setSelection(globalSelection); } KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->rootLayer->childCount() > 0); KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->rootLayer->selectionMask()); } m_d->deselectedGlobalSelection = 0; m_d->legacyUndoAdapter.emitSelectionChanged(); } void KisImage::deselectGlobalSelection() { KisSelectionSP savedSelection = globalSelection(); setGlobalSelection(0); m_d->deselectedGlobalSelection = savedSelection; } bool KisImage::canReselectGlobalSelection() { return m_d->deselectedGlobalSelection; } void KisImage::reselectGlobalSelection() { if(m_d->deselectedGlobalSelection) { setGlobalSelection(m_d->deselectedGlobalSelection); } } QString KisImage::nextLayerName(const QString &_baseName) const { QString baseName = _baseName; if (m_d->nserver.currentSeed() == 0) { m_d->nserver.number(); return i18n("background"); } if (baseName.isEmpty()) { baseName = i18n("Layer"); } return QString("%1 %2").arg(baseName).arg(m_d->nserver.number()); } void KisImage::rollBackLayerName() { m_d->nserver.rollback(); } KisCompositeProgressProxy* KisImage::compositeProgressProxy() { return &m_d->compositeProgressProxy; } bool KisImage::locked() const { return m_d->lockCount != 0; } void KisImage::barrierLock(bool readOnly) { if (!locked()) { requestStrokeEnd(); m_d->scheduler.barrierLock(); m_d->lockedForReadOnly = readOnly; } else { m_d->lockedForReadOnly &= readOnly; } m_d->lockCount++; } bool KisImage::tryBarrierLock(bool readOnly) { bool result = true; if (!locked()) { result = m_d->scheduler.tryBarrierLock(); m_d->lockedForReadOnly = readOnly; } if (result) { m_d->lockCount++; m_d->lockedForReadOnly &= readOnly; } return result; } bool KisImage::isIdle(bool allowLocked) { return (allowLocked || !locked()) && m_d->scheduler.isIdle(); } void KisImage::lock() { if (!locked()) { requestStrokeEnd(); m_d->scheduler.lock(); } m_d->lockCount++; m_d->lockedForReadOnly = false; } void KisImage::unlock() { Q_ASSERT(locked()); if (locked()) { m_d->lockCount--; if (m_d->lockCount == 0) { m_d->scheduler.unlock(!m_d->lockedForReadOnly); } } } void KisImage::blockUpdates() { m_d->scheduler.blockUpdates(); } void KisImage::unblockUpdates() { m_d->scheduler.unblockUpdates(); } void KisImage::setSize(const QSize& size) { m_d->width = size.width(); m_d->height = size.height(); } void KisImage::resizeImageImpl(const QRect& newRect, bool cropLayers) { if (newRect == bounds() && !cropLayers) return; KUndo2MagicString actionName = cropLayers ? kundo2_i18n("Crop Image") : kundo2_i18n("Resize Image"); KisImageSignalVector emitSignals; emitSignals << ComplexSizeChangedSignal(newRect, newRect.size()); emitSignals << ModifiedSignal; KisCropSavedExtraData *extraData = new KisCropSavedExtraData(cropLayers ? KisCropSavedExtraData::CROP_IMAGE : KisCropSavedExtraData::RESIZE_IMAGE, newRect); KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | KisProcessingApplicator::NO_UI_UPDATES, emitSignals, actionName, extraData); if (cropLayers || !newRect.topLeft().isNull()) { KisProcessingVisitorSP visitor = new KisCropProcessingVisitor(newRect, cropLayers, true); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } applicator.applyCommand(new KisImageResizeCommand(this, newRect.size())); applicator.end(); } void KisImage::resizeImage(const QRect& newRect) { resizeImageImpl(newRect, false); } void KisImage::cropImage(const QRect& newRect) { resizeImageImpl(newRect, true); } void KisImage::cropNode(KisNodeSP node, const QRect& newRect) { bool isLayer = qobject_cast(node.data()); KUndo2MagicString actionName = isLayer ? kundo2_i18n("Crop Layer") : kundo2_i18n("Crop Mask"); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisCropSavedExtraData *extraData = new KisCropSavedExtraData(KisCropSavedExtraData::CROP_LAYER, newRect, node); KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName, extraData); KisProcessingVisitorSP visitor = new KisCropProcessingVisitor(newRect, true, false); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } void KisImage::scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy) { bool resolutionChanged = xres != xRes() && yres != yRes(); bool sizeChanged = size != this->size(); if (!resolutionChanged && !sizeChanged) return; KisImageSignalVector emitSignals; if (resolutionChanged) emitSignals << ResolutionChangedSignal; if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), size); emitSignals << ModifiedSignal; KUndo2MagicString actionName = sizeChanged ? kundo2_i18n("Scale Image") : kundo2_i18n("Change Image Resolution"); KisProcessingApplicator::ProcessingFlags signalFlags = (resolutionChanged || sizeChanged) ? KisProcessingApplicator::NO_UI_UPDATES : KisProcessingApplicator::NONE; KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | signalFlags, emitSignals, actionName); qreal sx = qreal(size.width()) / this->size().width(); qreal sy = qreal(size.height()) / this->size().height(); QTransform shapesCorrection; if (resolutionChanged) { shapesCorrection = QTransform::fromScale(xRes() / xres, yRes() / yres); } KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(sx, sy, 0, 0, QPointF(), 0, 0, 0, filterStrategy, shapesCorrection); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); if (resolutionChanged) { KUndo2Command *parent = new KisResetShapesCommand(m_d->rootLayer); new KisImageSetResolutionCommand(this, xres, yres, parent); applicator.applyCommand(parent); } if (sizeChanged) { applicator.applyCommand(new KisImageResizeCommand(this, size)); } applicator.end(); } void KisImage::scaleNode(KisNodeSP node, const QPointF ¢er, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy, KisSelectionSP selection) { KUndo2MagicString actionName(kundo2_i18n("Scale Layer")); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; QPointF offset; { KisTransformWorker worker(0, scaleX, scaleY, 0, 0, 0, 0, 0.0, 0, 0, 0, 0); QTransform transform = worker.transform(); offset = center - transform.map(center); } KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName); KisTransformProcessingVisitor *visitor = new KisTransformProcessingVisitor(scaleX, scaleY, 0, 0, QPointF(), 0, offset.x(), offset.y(), filterStrategy); visitor->setSelection(selection); if (selection) { applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); } else { applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } applicator.end(); } void KisImage::rotateImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, double radians, bool resizeImage, KisSelectionSP selection) { // we can either transform (and resize) the whole image or // transform a selection, we cannot do both at the same time KIS_SAFE_ASSERT_RECOVER(!(bool(selection) && resizeImage)) { selection = 0; } const QRect baseBounds = resizeImage ? bounds() : selection ? selection->selectedExactRect() : rootNode->exactBounds(); QPointF offset; QSize newSize; { KisTransformWorker worker(0, 1.0, 1.0, 0, 0, 0, 0, radians, 0, 0, 0, 0); QTransform transform = worker.transform(); if (resizeImage) { QRect newRect = transform.mapRect(baseBounds); newSize = newRect.size(); offset = -newRect.topLeft(); } else { QPointF origin = QRectF(baseBounds).center(); newSize = size(); offset = -(transform.map(origin) - origin); } } bool sizeChanged = resizeImage && (newSize.width() != baseBounds.width() || newSize.height() != baseBounds.height()); // These signals will be emitted after processing is done KisImageSignalVector emitSignals; if (sizeChanged) emitSignals << ComplexSizeChangedSignal(baseBounds, newSize); emitSignals << ModifiedSignal; // These flags determine whether updates are transferred to the UI during processing KisProcessingApplicator::ProcessingFlags signalFlags = sizeChanged ? KisProcessingApplicator::NO_UI_UPDATES : KisProcessingApplicator::NONE; KisProcessingApplicator applicator(this, rootNode, KisProcessingApplicator::RECURSIVE | signalFlags, emitSignals, actionName); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bicubic"); KisTransformProcessingVisitor *visitor = new KisTransformProcessingVisitor(1.0, 1.0, 0.0, 0.0, QPointF(), radians, offset.x(), offset.y(), filter); if (selection) { visitor->setSelection(selection); } if (selection) { applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); } else { applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } if (sizeChanged) { applicator.applyCommand(new KisImageResizeCommand(this, newSize)); } applicator.end(); } void KisImage::rotateImage(double radians) { rotateImpl(kundo2_i18n("Rotate Image"), root(), radians, true, 0); } void KisImage::rotateNode(KisNodeSP node, double radians, KisSelectionSP selection) { if (node->inherits("KisMask")) { rotateImpl(kundo2_i18n("Rotate Mask"), node, radians, false, selection); } else { rotateImpl(kundo2_i18n("Rotate Layer"), node, radians, false, selection); } } void KisImage::shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double angleX, double angleY, KisSelectionSP selection) { const QRect baseBounds = resizeImage ? bounds() : selection ? selection->selectedExactRect() : rootNode->exactBounds(); const QPointF origin = QRectF(baseBounds).center(); //angleX, angleY are in degrees const qreal pi = 3.1415926535897932385; const qreal deg2rad = pi / 180.0; qreal tanX = tan(angleX * deg2rad); qreal tanY = tan(angleY * deg2rad); QPointF offset; QSize newSize; { KisTransformWorker worker(0, 1.0, 1.0, tanX, tanY, origin.x(), origin.y(), 0, 0, 0, 0, 0); QRect newRect = worker.transform().mapRect(baseBounds); newSize = newRect.size(); if (resizeImage) offset = -newRect.topLeft(); } if (newSize == baseBounds.size()) return; KisImageSignalVector emitSignals; if (resizeImage) emitSignals << ComplexSizeChangedSignal(baseBounds, newSize); emitSignals << ModifiedSignal; KisProcessingApplicator::ProcessingFlags signalFlags = KisProcessingApplicator::RECURSIVE; if (resizeImage) signalFlags |= KisProcessingApplicator::NO_UI_UPDATES; KisProcessingApplicator applicator(this, rootNode, signalFlags, emitSignals, actionName); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bilinear"); KisTransformProcessingVisitor *visitor = new KisTransformProcessingVisitor(1.0, 1.0, tanX, tanY, origin, 0, offset.x(), offset.y(), filter); if (selection) { visitor->setSelection(selection); } if (selection) { applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); } else { applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } if (resizeImage) { applicator.applyCommand(new KisImageResizeCommand(this, newSize)); } applicator.end(); } void KisImage::shearNode(KisNodeSP node, double angleX, double angleY, KisSelectionSP selection) { if (node->inherits("KisMask")) { shearImpl(kundo2_i18n("Shear Mask"), node, false, angleX, angleY, selection); } else { shearImpl(kundo2_i18n("Shear Layer"), node, false, angleX, angleY, selection); } } void KisImage::shear(double angleX, double angleY) { shearImpl(kundo2_i18n("Shear Image"), m_d->rootLayer, true, angleX, angleY, 0); } void KisImage::convertLayerColorSpace(KisNodeSP node, const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { if (!node->projectionLeaf()->isLayer()) return; const KoColorSpace *srcColorSpace = node->colorSpace(); if (!dstColorSpace || *srcColorSpace == *dstColorSpace) return; KUndo2MagicString actionName = kundo2_i18n("Convert Layer Type"); // TODO: in Krita 4.3 change Type -> Color Space KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE | KisProcessingApplicator::NO_UI_UPDATES, emitSignals, actionName); applicator.applyVisitor( new KisConvertColorSpaceProcessingVisitor( srcColorSpace, dstColorSpace, renderingIntent, conversionFlags), KisStrokeJobData::CONCURRENT); applicator.end(); } void KisImage::convertImageColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { const KoColorSpace *srcColorSpace = m_d->colorSpace; if (!dstColorSpace || *srcColorSpace == *dstColorSpace) return; KUndo2MagicString actionName = kundo2_i18n("Convert Image Color Space"); KisImageSignalVector emitSignals; emitSignals << ColorSpaceChangedSignal; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | KisProcessingApplicator::NO_UI_UPDATES, emitSignals, actionName); applicator.applyCommand( new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace), KisStrokeJobData::SEQUENTIAL); applicator.applyVisitor( new KisConvertColorSpaceProcessingVisitor( srcColorSpace, dstColorSpace, renderingIntent, conversionFlags), KisStrokeJobData::CONCURRENT); applicator.end(); } +bool KisImage::assignLayerProfile(KisNodeSP node, const KoColorProfile *profile) +{ + const KoColorSpace *srcColorSpace = node->colorSpace(); + + if (!node->projectionLeaf()->isLayer()) return false; + if (!profile || *srcColorSpace->profile() == *profile) return false; + + KUndo2MagicString actionName = kundo2_i18n("Assign Profile to Layer"); + + KisImageSignalVector emitSignals; + emitSignals << ModifiedSignal; + + const KoColorSpace *dstColorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); + if (!dstColorSpace) return false; + + KisProcessingApplicator applicator(this, node, + KisProcessingApplicator::RECURSIVE | + KisProcessingApplicator::NO_UI_UPDATES, + emitSignals, actionName); + + applicator.applyVisitor( + new KisAssignProfileProcessingVisitor( + srcColorSpace, dstColorSpace), + KisStrokeJobData::CONCURRENT); + + applicator.end(); + + return true; +} + + bool KisImage::assignImageProfile(const KoColorProfile *profile) { - if (!profile) return false; + const KoColorSpace *srcColorSpace = m_d->colorSpace; + + if (!profile || *srcColorSpace->profile() == *profile) return false; + + KUndo2MagicString actionName = kundo2_i18n("Assign Profile"); - const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); - const KoColorSpace *srcCs = colorSpace(); + KisImageSignalVector emitSignals; + emitSignals << ProfileChangedSignal; + emitSignals << ModifiedSignal; + + const KoColorSpace *dstColorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); + if (!dstColorSpace) return false; + + KisProcessingApplicator applicator(this, m_d->rootLayer, + KisProcessingApplicator::RECURSIVE | + KisProcessingApplicator::NO_UI_UPDATES, + emitSignals, actionName); - if (!dstCs) return false; + applicator.applyCommand( + new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace), + KisStrokeJobData::SEQUENTIAL); - m_d->colorSpace = dstCs; + applicator.applyVisitor( + new KisAssignProfileProcessingVisitor( + srcColorSpace, dstColorSpace), + KisStrokeJobData::CONCURRENT); - KisChangeProfileVisitor visitor(srcCs, dstCs); - bool retval = m_d->rootLayer->accept(visitor); - m_d->signalRouter.emitNotification(ProfileChangedSignal); - return retval; + applicator.end(); + return true; } void KisImage::setProjectionColorSpace(const KoColorSpace * colorSpace) { m_d->colorSpace = colorSpace; } const KoColorSpace * KisImage::colorSpace() const { return m_d->colorSpace; } const KoColorProfile * KisImage::profile() const { return colorSpace()->profile(); } double KisImage::xRes() const { return m_d->xres; } double KisImage::yRes() const { return m_d->yres; } void KisImage::setResolution(double xres, double yres) { m_d->xres = xres; m_d->yres = yres; m_d->signalRouter.emitNotification(ResolutionChangedSignal); } QPointF KisImage::documentToPixel(const QPointF &documentCoord) const { return QPointF(documentCoord.x() * xRes(), documentCoord.y() * yRes()); } QPoint KisImage::documentToImagePixelFloored(const QPointF &documentCoord) const { QPointF pixelCoord = documentToPixel(documentCoord); return QPoint(qFloor(pixelCoord.x()), qFloor(pixelCoord.y())); } QRectF KisImage::documentToPixel(const QRectF &documentRect) const { return QRectF(documentToPixel(documentRect.topLeft()), documentToPixel(documentRect.bottomRight())); } QPointF KisImage::pixelToDocument(const QPointF &pixelCoord) const { return QPointF(pixelCoord.x() / xRes(), pixelCoord.y() / yRes()); } QPointF KisImage::pixelToDocument(const QPoint &pixelCoord) const { return QPointF((pixelCoord.x() + 0.5) / xRes(), (pixelCoord.y() + 0.5) / yRes()); } QRectF KisImage::pixelToDocument(const QRectF &pixelCoord) const { return QRectF(pixelToDocument(pixelCoord.topLeft()), pixelToDocument(pixelCoord.bottomRight())); } qint32 KisImage::width() const { return m_d->width; } qint32 KisImage::height() const { return m_d->height; } KisGroupLayerSP KisImage::rootLayer() const { Q_ASSERT(m_d->rootLayer); return m_d->rootLayer; } KisPaintDeviceSP KisImage::projection() const { if (m_d->isolatedRootNode) { return m_d->isolatedRootNode->projection(); } Q_ASSERT(m_d->rootLayer); KisPaintDeviceSP projection = m_d->rootLayer->projection(); Q_ASSERT(projection); return projection; } qint32 KisImage::nlayers() const { QStringList list; list << "KisLayer"; KisCountVisitor visitor(list, KoProperties()); m_d->rootLayer->accept(visitor); return visitor.count(); } qint32 KisImage::nHiddenLayers() const { QStringList list; list << "KisLayer"; KoProperties properties; properties.setProperty("visible", false); KisCountVisitor visitor(list, properties); m_d->rootLayer->accept(visitor); return visitor.count(); } void KisImage::flatten(KisNodeSP activeNode) { KisLayerUtils::flattenImage(this, activeNode); } void KisImage::mergeMultipleLayers(QList mergedNodes, KisNodeSP putAfter) { if (!KisLayerUtils::tryMergeSelectionMasks(this, mergedNodes, putAfter)) { KisLayerUtils::mergeMultipleLayers(this, mergedNodes, putAfter); } } void KisImage::mergeDown(KisLayerSP layer, const KisMetaData::MergeStrategy* strategy) { KisLayerUtils::mergeDown(this, layer, strategy); } void KisImage::flattenLayer(KisLayerSP layer) { KisLayerUtils::flattenLayer(this, layer); } void KisImage::setModified() { m_d->signalRouter.emitNotification(ModifiedSignal); } QImage KisImage::convertToQImage(QRect imageRect, const KoColorProfile * profile) { qint32 x; qint32 y; qint32 w; qint32 h; imageRect.getRect(&x, &y, &w, &h); return convertToQImage(x, y, w, h, profile); } QImage KisImage::convertToQImage(qint32 x, qint32 y, qint32 w, qint32 h, const KoColorProfile * profile) { KisPaintDeviceSP dev = projection(); if (!dev) return QImage(); QImage image = dev->convertToQImage(const_cast(profile), x, y, w, h, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return image; } QImage KisImage::convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile) { if (scaledImageSize.isEmpty()) { return QImage(); } KisPaintDeviceSP dev = new KisPaintDevice(colorSpace()); KisPainter gc; gc.copyAreaOptimized(QPoint(0, 0), projection(), dev, bounds()); gc.end(); double scaleX = qreal(scaledImageSize.width()) / width(); double scaleY = qreal(scaledImageSize.height()) / height(); QPointer updater = new KoDummyUpdater(); KisTransformWorker worker(dev, scaleX, scaleY, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, updater, KisFilterStrategyRegistry::instance()->value("Bicubic")); worker.run(); delete updater; return dev->convertToQImage(profile); } void KisImage::notifyLayersChanged() { m_d->signalRouter.emitNotification(LayersChangedSignal); } QRect KisImage::bounds() const { return QRect(0, 0, width(), height()); } QRect KisImage::effectiveLodBounds() const { QRect boundRect = bounds(); const int lod = currentLevelOfDetail(); if (lod > 0) { KisLodTransform t(lod); boundRect = t.map(boundRect); } return boundRect; } KisPostExecutionUndoAdapter* KisImage::postExecutionUndoAdapter() const { const int lod = currentLevelOfDetail(); return lod > 0 ? m_d->scheduler.lodNPostExecutionUndoAdapter() : &m_d->postExecutionUndoAdapter; } const KUndo2Command* KisImage::lastExecutedCommand() const { return m_d->undoStore->presentCommand(); } void KisImage::setUndoStore(KisUndoStore *undoStore) { m_d->legacyUndoAdapter.setUndoStore(undoStore); m_d->postExecutionUndoAdapter.setUndoStore(undoStore); m_d->undoStore.reset(undoStore); } KisUndoStore* KisImage::undoStore() { return m_d->undoStore.data(); } KisUndoAdapter* KisImage::undoAdapter() const { return &m_d->legacyUndoAdapter; } void KisImage::setDefaultProjectionColor(const KoColor &color) { KIS_ASSERT_RECOVER_RETURN(m_d->rootLayer); m_d->rootLayer->setDefaultProjectionColor(color); } KoColor KisImage::defaultProjectionColor() const { KIS_ASSERT_RECOVER(m_d->rootLayer) { return KoColor(Qt::transparent, m_d->colorSpace); } return m_d->rootLayer->defaultProjectionColor(); } void KisImage::setRootLayer(KisGroupLayerSP rootLayer) { emit sigInternalStopIsolatedModeRequested(); KoColor defaultProjectionColor(Qt::transparent, m_d->colorSpace); if (m_d->rootLayer) { m_d->rootLayer->setGraphListener(0); m_d->rootLayer->disconnect(); KisPaintDeviceSP original = m_d->rootLayer->original(); defaultProjectionColor = original->defaultPixel(); } m_d->rootLayer = rootLayer; m_d->rootLayer->disconnect(); m_d->rootLayer->setGraphListener(this); m_d->rootLayer->setImage(this); setRoot(m_d->rootLayer.data()); this->setDefaultProjectionColor(defaultProjectionColor); } void KisImage::addAnnotation(KisAnnotationSP annotation) { // Find the icc annotation, if there is one vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == annotation->type()) { *it = annotation; return; } ++it; } m_d->annotations.push_back(annotation); } KisAnnotationSP KisImage::annotation(const QString& type) { vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == type) { return *it; } ++it; } return KisAnnotationSP(0); } void KisImage::removeAnnotation(const QString& type) { vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == type) { m_d->annotations.erase(it); return; } ++it; } } vKisAnnotationSP_it KisImage::beginAnnotations() { return m_d->annotations.begin(); } vKisAnnotationSP_it KisImage::endAnnotations() { return m_d->annotations.end(); } void KisImage::notifyAboutToBeDeleted() { emit sigAboutToBeDeleted(); } KisImageSignalRouter* KisImage::signalRouter() { return &m_d->signalRouter; } void KisImage::waitForDone() { requestStrokeEnd(); m_d->scheduler.waitForDone(); } KisStrokeId KisImage::startStroke(KisStrokeStrategy *strokeStrategy) { /** * Ask open strokes to end gracefully. All the strokes clients * (including the one calling this method right now) will get * a notification that they should probably end their strokes. * However this is purely their choice whether to end a stroke * or not. */ if (strokeStrategy->requestsOtherStrokesToEnd()) { requestStrokeEnd(); } /** * Some of the strokes can cancel their work with undoing all the * changes they did to the paint devices. The problem is that undo * stack will know nothing about it. Therefore, just notify it * explicitly */ if (strokeStrategy->clearsRedoOnStart()) { m_d->undoStore->purgeRedoState(); } return m_d->scheduler.startStroke(strokeStrategy); } void KisImage::KisImagePrivate::notifyProjectionUpdatedInPatches(const QRect &rc, QVector &jobs) { KisImageConfig imageConfig(true); int patchWidth = imageConfig.updatePatchWidth(); int patchHeight = imageConfig.updatePatchHeight(); for (int y = 0; y < rc.height(); y += patchHeight) { for (int x = 0; x < rc.width(); x += patchWidth) { QRect patchRect(x, y, patchWidth, patchHeight); patchRect &= rc; KritaUtils::addJobConcurrent(jobs, std::bind(&KisImage::notifyProjectionUpdated, q, patchRect)); } } } bool KisImage::startIsolatedMode(KisNodeSP node) { struct StartIsolatedModeStroke : public KisRunnableBasedStrokeStrategy { StartIsolatedModeStroke(KisNodeSP node, KisImageSP image) : KisRunnableBasedStrokeStrategy("start-isolated-mode", kundo2_noi18n("start-isolated-mode")), m_node(node), m_image(image) { this->enableJob(JOB_INIT, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); this->enableJob(JOB_DOSTROKE, true); setClearsRedoOnStart(false); } void initStrokeCallback() { // pass-though node don't have any projection prepared, so we should // explicitly regenerate it before activating isolated mode. m_node->projectionLeaf()->explicitlyRegeneratePassThroughProjection(); m_image->m_d->isolatedRootNode = m_node; emit m_image->sigIsolatedModeChanged(); // the GUI uses our thread to do the color space conversion so we // need to emit this signal in multiple threads QVector jobs; m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds(), jobs); this->runnableJobsInterface()->addRunnableJobs(jobs); m_image->invalidateAllFrames(); } private: KisNodeSP m_node; KisImageSP m_image; }; KisStrokeId id = startStroke(new StartIsolatedModeStroke(node, this)); endStroke(id); return true; } void KisImage::stopIsolatedMode() { if (!m_d->isolatedRootNode) return; struct StopIsolatedModeStroke : public KisRunnableBasedStrokeStrategy { StopIsolatedModeStroke(KisImageSP image) : KisRunnableBasedStrokeStrategy("stop-isolated-mode", kundo2_noi18n("stop-isolated-mode")), m_image(image) { this->enableJob(JOB_INIT); this->enableJob(JOB_DOSTROKE, true); setClearsRedoOnStart(false); } void initStrokeCallback() { if (!m_image->m_d->isolatedRootNode) return; //KisNodeSP oldRootNode = m_image->m_d->isolatedRootNode; m_image->m_d->isolatedRootNode = 0; emit m_image->sigIsolatedModeChanged(); m_image->invalidateAllFrames(); // the GUI uses our thread to do the color space conversion so we // need to emit this signal in multiple threads QVector jobs; m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds(), jobs); this->runnableJobsInterface()->addRunnableJobs(jobs); // TODO: Substitute notifyProjectionUpdated() with this code // when update optimization is implemented // // QRect updateRect = bounds() | oldRootNode->extent(); // oldRootNode->setDirty(updateRect); } private: KisImageSP m_image; }; KisStrokeId id = startStroke(new StopIsolatedModeStroke(this)); endStroke(id); } KisNodeSP KisImage::isolatedModeRoot() const { return m_d->isolatedRootNode; } void KisImage::addJob(KisStrokeId id, KisStrokeJobData *data) { KisUpdateTimeMonitor::instance()->reportJobStarted(data); m_d->scheduler.addJob(id, data); } void KisImage::endStroke(KisStrokeId id) { m_d->scheduler.endStroke(id); } bool KisImage::cancelStroke(KisStrokeId id) { return m_d->scheduler.cancelStroke(id); } bool KisImage::KisImagePrivate::tryCancelCurrentStrokeAsync() { return scheduler.tryCancelCurrentStrokeAsync(); } void KisImage::requestUndoDuringStroke() { emit sigUndoDuringStrokeRequested(); } void KisImage::requestStrokeCancellation() { if (!m_d->tryCancelCurrentStrokeAsync()) { emit sigStrokeCancellationRequested(); } } UndoResult KisImage::tryUndoUnfinishedLod0Stroke() { return m_d->scheduler.tryUndoLastStrokeAsync(); } void KisImage::requestStrokeEnd() { emit sigStrokeEndRequested(); emit sigStrokeEndRequestedActiveNodeFiltered(); } void KisImage::requestStrokeEndActiveNode() { emit sigStrokeEndRequested(); } void KisImage::refreshGraph(KisNodeSP root) { refreshGraph(root, bounds(), bounds()); } void KisImage::refreshGraph(KisNodeSP root, const QRect &rc, const QRect &cropRect) { if (!root) root = m_d->rootLayer; m_d->animationInterface->notifyNodeChanged(root.data(), rc, true); m_d->scheduler.fullRefresh(root, rc, cropRect); } void KisImage::initialRefreshGraph() { /** * NOTE: Tricky part. We set crop rect to null, so the clones * will not rely on precalculated projections of their sources */ refreshGraphAsync(0, bounds(), QRect()); waitForDone(); } void KisImage::refreshGraphAsync(KisNodeSP root) { refreshGraphAsync(root, bounds(), bounds()); } void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc) { refreshGraphAsync(root, rc, bounds()); } void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect) { if (!root) root = m_d->rootLayer; m_d->animationInterface->notifyNodeChanged(root.data(), rc, true); m_d->scheduler.fullRefreshAsync(root, rc, cropRect); } void KisImage::requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect) { KIS_ASSERT_RECOVER_RETURN(pseudoFilthy); m_d->animationInterface->notifyNodeChanged(pseudoFilthy.data(), rc, false); m_d->scheduler.updateProjectionNoFilthy(pseudoFilthy, rc, cropRect); } void KisImage::addSpontaneousJob(KisSpontaneousJob *spontaneousJob) { m_d->scheduler.addSpontaneousJob(spontaneousJob); } bool KisImage::hasUpdatesRunning() const { return m_d->scheduler.hasUpdatesRunning(); } void KisImage::setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter) { // update filters are *not* recursive! KIS_ASSERT_RECOVER_NOOP(!filter || !m_d->projectionUpdatesFilter); m_d->projectionUpdatesFilter = filter; } KisProjectionUpdatesFilterSP KisImage::projectionUpdatesFilter() const { return m_d->projectionUpdatesFilter; } void KisImage::disableDirtyRequests() { setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP(new KisDropAllProjectionUpdatesFilter())); } void KisImage::enableDirtyRequests() { setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP()); } void KisImage::disableUIUpdates() { m_d->disableUIUpdateSignals.ref(); } void KisImage::notifyBatchUpdateStarted() { m_d->signalRouter.emitNotifyBatchUpdateStarted(); } void KisImage::notifyBatchUpdateEnded() { m_d->signalRouter.emitNotifyBatchUpdateEnded(); } void KisImage::notifyUIUpdateCompleted(const QRect &rc) { notifyProjectionUpdated(rc); } QVector KisImage::enableUIUpdates() { m_d->disableUIUpdateSignals.deref(); QRect rect; QVector postponedUpdates; while (m_d->savedDisabledUIUpdates.pop(rect)) { postponedUpdates.append(rect); } return postponedUpdates; } void KisImage::notifyProjectionUpdated(const QRect &rc) { KisUpdateTimeMonitor::instance()->reportUpdateFinished(rc); if (!m_d->disableUIUpdateSignals) { int lod = currentLevelOfDetail(); QRect dirtyRect = !lod ? rc : KisLodTransform::upscaledRect(rc, lod); if (dirtyRect.isEmpty()) return; emit sigImageUpdated(dirtyRect); } else { m_d->savedDisabledUIUpdates.push(rc); } } void KisImage::setWorkingThreadsLimit(int value) { m_d->scheduler.setThreadsLimit(value); } int KisImage::workingThreadsLimit() const { return m_d->scheduler.threadsLimit(); } void KisImage::notifySelectionChanged() { /** * The selection is calculated asynchronously, so it is not * handled by disableUIUpdates() and other special signals of * KisImageSignalRouter */ m_d->legacyUndoAdapter.emitSelectionChanged(); /** * Editing of selection masks doesn't necessary produce a * setDirty() call, so in the end of the stroke we need to request * direct update of the UI's cache. */ if (m_d->isolatedRootNode && dynamic_cast(m_d->isolatedRootNode.data())) { notifyProjectionUpdated(bounds()); } } void KisImage::requestProjectionUpdateImpl(KisNode *node, const QVector &rects, const QRect &cropRect) { if (rects.isEmpty()) return; m_d->scheduler.updateProjection(node, rects, cropRect); } void KisImage::requestProjectionUpdate(KisNode *node, const QVector &rects, bool resetAnimationCache) { if (m_d->projectionUpdatesFilter && m_d->projectionUpdatesFilter->filter(this, node, rects, resetAnimationCache)) { return; } if (resetAnimationCache) { m_d->animationInterface->notifyNodeChanged(node, rects, false); } /** * Here we use 'permitted' instead of 'active' intentively, * because the updates may come after the actual stroke has been * finished. And having some more updates for the stroke not * supporting the wrap-around mode will not make much harm. */ if (m_d->wrapAroundModePermitted) { QVector allSplitRects; const QRect boundRect = effectiveLodBounds(); Q_FOREACH (const QRect &rc, rects) { KisWrappedRect splitRect(rc, boundRect); allSplitRects.append(splitRect); } requestProjectionUpdateImpl(node, allSplitRects, boundRect); } else { requestProjectionUpdateImpl(node, rects, bounds()); } KisNodeGraphListener::requestProjectionUpdate(node, rects, resetAnimationCache); } void KisImage::invalidateFrames(const KisTimeRange &range, const QRect &rect) { m_d->animationInterface->invalidateFrames(range, rect); } void KisImage::requestTimeSwitch(int time) { m_d->animationInterface->requestTimeSwitchNonGUI(time); } KisNode *KisImage::graphOverlayNode() const { return m_d->overlaySelectionMask.data(); } QList KisImage::compositions() { return m_d->compositions; } void KisImage::addComposition(KisLayerCompositionSP composition) { m_d->compositions.append(composition); } void KisImage::removeComposition(KisLayerCompositionSP composition) { m_d->compositions.removeAll(composition); } bool checkMasksNeedConversion(KisNodeSP root, const QRect &bounds) { KisSelectionMask *mask = dynamic_cast(root.data()); if (mask && (!bounds.contains(mask->paintDevice()->exactBounds()) || mask->selection()->hasShapeSelection())) { return true; } KisNodeSP node = root->firstChild(); while (node) { if (checkMasksNeedConversion(node, bounds)) { return true; } node = node->nextSibling(); } return false; } void KisImage::setWrapAroundModePermitted(bool value) { if (m_d->wrapAroundModePermitted != value) { requestStrokeEnd(); } m_d->wrapAroundModePermitted = value; if (m_d->wrapAroundModePermitted && checkMasksNeedConversion(root(), bounds())) { KisProcessingApplicator applicator(this, root(), KisProcessingApplicator::RECURSIVE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Crop Selections")); KisProcessingVisitorSP visitor = new KisCropSelectionsProcessingVisitor(bounds()); applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } } bool KisImage::wrapAroundModePermitted() const { return m_d->wrapAroundModePermitted; } bool KisImage::wrapAroundModeActive() const { return m_d->wrapAroundModePermitted && m_d->scheduler.wrapAroundModeSupported(); } void KisImage::setDesiredLevelOfDetail(int lod) { if (m_d->blockLevelOfDetail) { qWarning() << "WARNING: KisImage::setDesiredLevelOfDetail()" << "was called while LoD functionality was being blocked!"; return; } m_d->scheduler.setDesiredLevelOfDetail(lod); } int KisImage::currentLevelOfDetail() const { if (m_d->blockLevelOfDetail) { return 0; } return m_d->scheduler.currentLevelOfDetail(); } void KisImage::setLevelOfDetailBlocked(bool value) { KisImageBarrierLockerRaw l(this); if (value && !m_d->blockLevelOfDetail) { m_d->scheduler.setDesiredLevelOfDetail(0); } m_d->blockLevelOfDetail = value; } void KisImage::explicitRegenerateLevelOfDetail() { if (!m_d->blockLevelOfDetail) { m_d->scheduler.explicitRegenerateLevelOfDetail(); } } bool KisImage::levelOfDetailBlocked() const { return m_d->blockLevelOfDetail; } void KisImage::nodeCollapsedChanged(KisNode * node) { Q_UNUSED(node); emit sigNodeCollapsedChanged(); } KisImageAnimationInterface* KisImage::animationInterface() const { return m_d->animationInterface; } void KisImage::setProofingConfiguration(KisProofingConfigurationSP proofingConfig) { m_d->proofingConfig = proofingConfig; emit sigProofingConfigChanged(); } KisProofingConfigurationSP KisImage::proofingConfiguration() const { if (m_d->proofingConfig) { return m_d->proofingConfig; } return KisProofingConfigurationSP(); } QPointF KisImage::mirrorAxesCenter() const { return m_d->axesCenter; } void KisImage::setMirrorAxesCenter(const QPointF &value) const { m_d->axesCenter = value; } void KisImage::setAllowMasksOnRootNode(bool value) { m_d->allowMasksOnRootNode = value; } bool KisImage::allowMasksOnRootNode() const { return m_d->allowMasksOnRootNode; } diff --git a/libs/image/kis_image.h b/libs/image/kis_image.h index dce5c8db00..1c2d92e0c5 100644 --- a/libs/image/kis_image.h +++ b/libs/image/kis_image.h @@ -1,1189 +1,1197 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_IMAGE_H_ #define KIS_IMAGE_H_ #include #include #include #include #include #include #include #include "kis_types.h" #include "kis_shared.h" #include "kis_node_graph_listener.h" #include "kis_node_facade.h" #include "kis_image_interfaces.h" #include "kis_strokes_queue_undo_result.h" #include class KoColorSpace; class KoColor; class KisCompositeProgressProxy; class KisUndoStore; class KisUndoAdapter; class KisImageSignalRouter; class KisPostExecutionUndoAdapter; class KisFilterStrategy; class KoColorProfile; class KisLayerComposition; class KisSpontaneousJob; class KisImageAnimationInterface; class KUndo2MagicString; class KisProofingConfiguration; class KisPaintDevice; namespace KisMetaData { class MergeStrategy; } /** * This is the image class, it contains a tree of KisLayer stack and * meta information about the image. And it also provides some * functions to manipulate the whole image. */ class KRITAIMAGE_EXPORT KisImage : public QObject, public KisStrokesFacade, public KisStrokeUndoFacade, public KisUpdatesFacade, public KisProjectionUpdateListener, public KisNodeFacade, public KisNodeGraphListener, public KisShared { Q_OBJECT public: /// @p colorSpace can be null. In that case, it will be initialised to a default color space. KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace *colorSpace, const QString& name); ~KisImage() override; static KisImageSP fromQImage(const QImage &image, KisUndoStore *undoStore); public: // KisNodeGraphListener implementation void aboutToAddANode(KisNode *parent, int index) override; void nodeHasBeenAdded(KisNode *parent, int index) override; void aboutToRemoveANode(KisNode *parent, int index) override; void nodeChanged(KisNode * node) override; void nodeCollapsedChanged(KisNode *node) override; void invalidateAllFrames() override; void notifySelectionChanged() override; void requestProjectionUpdate(KisNode *node, const QVector &rects, bool resetAnimationCache) override; void invalidateFrames(const KisTimeRange &range, const QRect &rect) override; void requestTimeSwitch(int time) override; KisNode* graphOverlayNode() const override; public: // KisProjectionUpdateListener implementation void notifyProjectionUpdated(const QRect &rc) override; public: /** * Set the number of threads used by the image's working threads */ void setWorkingThreadsLimit(int value); /** * Return the number of threads available to the image's working threads */ int workingThreadsLimit() const; /** * Makes a copy of the image with all the layers. If possible, shallow * copies of the layers are made. * * \p exactCopy shows if the copied image should look *exactly* the same as * the other one (according to it's .kra xml representation). It means that * the layers will have the same UUID keys and, therefore, you are not * expected to use the copied image anywhere except for saving. Don't use * this option if you plan to work with the copied image later. */ KisImage *clone(bool exactCopy = false); void copyFromImage(const KisImage &rhs); private: // must specify exactly one from CONSTRUCT or REPLACE. enum CopyPolicy { CONSTRUCT = 1, ///< we are copy-constructing a new KisImage REPLACE = 2, ///< we are replacing the current KisImage with another EXACT_COPY = 4, /// we need an exact copy of the original image }; void copyFromImageImpl(const KisImage &rhs, int policy); public: /** * Render the projection onto a QImage. */ QImage convertToQImage(qint32 x1, qint32 y1, qint32 width, qint32 height, const KoColorProfile * profile); /** * Render the projection onto a QImage. * (this is an overloaded function) */ QImage convertToQImage(QRect imageRect, const KoColorProfile * profile); /** * Render a thumbnail of the projection onto a QImage. */ QImage convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile); /** * [low-level] Lock the image without waiting for all the internal job queues are processed * * WARNING: Don't use it unless you really know what you are doing! Use barrierLock() instead! * * Waits for all the **currently running** internal jobs to complete and locks the image * for writing. Please note that this function does **not** wait for all the internal * queues to process, so there might be some non-finished actions pending. It means that * you just postpone these actions until you unlock() the image back. Until then, then image * might easily be frozen in some inconsistent state. * * The only sane usage for this function is to lock the image for **emergency** * processing, when some internal action or scheduler got hung up, and you just want * to fetch some data from the image without races. * * In all other cases, please use barrierLock() instead! */ void lock(); /** * Unlocks the image and starts/resumes all the pending internal jobs. If the image * has been locked for a non-readOnly access, then all the internal caches of the image * (e.g. lod-planes) are reset and regeneration jobs are scheduled. */ void unlock(); /** * @return return true if the image is in a locked state, i.e. all the internal * jobs are blocked from execution by calling wither lock() or barrierLock(). * * When the image is locked, the user can do some modifications to the image * contents safely without a perspective having race conditions with internal * image jobs. */ bool locked() const; /** * Sets the mask (it must be a part of the node hierarchy already) to be paited on * the top of all layers. This method does all the locking and syncing for you. It * is executed asynchronously. */ void setOverlaySelectionMask(KisSelectionMaskSP mask); /** * \see setOverlaySelectionMask */ KisSelectionMaskSP overlaySelectionMask() const; /** * \see setOverlaySelectionMask */ bool hasOverlaySelectionMask() const; /** * @return the global selection object or 0 if there is none. The * global selection is always read-write. */ KisSelectionSP globalSelection() const; /** * Retrieve the next automatic layername (XXX: fix to add option to return Mask X) */ QString nextLayerName(const QString &baseName = "") const; /** * Set the automatic layer name counter one back. */ void rollBackLayerName(); /** * @brief start asynchronous operation on resizing the image * * The method will resize the image to fit the new size without * dropping any pixel data. The GUI will get correct * notification with old and new sizes, so it adjust canvas origin * accordingly and avoid jumping of the canvas on screen * * @param newRect the rectangle of the image which will be visible * after operation is completed * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after this call. */ void resizeImage(const QRect& newRect); /** * @brief start asynchronous operation on cropping the image * * The method will **drop** all the image data outside \p newRect * and resize the image to fit the new size. The GUI will get correct * notification with old and new sizes, so it adjust canvas origin * accordingly and avoid jumping of the canvas on screen * * @param newRect the rectangle of the image which will be cut-out * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after this call. */ void cropImage(const QRect& newRect); /** * @brief start asynchronous operation on cropping a subtree of nodes starting at \p node * * The method will **drop** all the layer data outside \p newRect. Neither * image nor a layer will be moved anywhere * * @param node node to crop * @param newRect the rectangle of the layer which will be cut-out * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after this call. */ void cropNode(KisNodeSP node, const QRect& newRect); /** * @brief start asynchronous operation on scaling the image * @param size new image size in pixels * @param xres new image x-resolution pixels-per-pt * @param yres new image y-resolution pixels-per-pt * @param filterStrategy filtering strategy * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after this call. */ void scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy); /** * @brief start asynchronous operation on scaling a subtree of nodes starting at \p node * @param node node to scale * @param center the center of the scaling * @param scaleX x-scale coefficient to be applied to the node * @param scaleY y-scale coefficient to be applied to the node * @param filterStrategy filtering strategy * @param selection the selection we based on * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after this call. */ void scaleNode(KisNodeSP node, const QPointF ¢er, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy, KisSelectionSP selection); /** * @brief start asynchronous operation on rotating the image * * The image is resized to fit the rotated rectangle * * @param radians rotation angle in radians * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the operation being completed * right after the call */ void rotateImage(double radians); /** * @brief start asynchronous operation on rotating a subtree of nodes starting at \p node * * The image is not resized! * * @param node the root of the subtree to rotate * @param radians rotation angle in radians * @param selection the selection we based on * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the operation being completed * right after the call */ void rotateNode(KisNodeSP node, double radians, KisSelectionSP selection); /** * @brief start asynchronous operation on shearing the image * * The image is resized to fit the sheared polygon * * @p angleX, @p angleY are given in degrees. * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the operation being completed * right after the call */ void shear(double angleX, double angleY); /** * @brief start asynchronous operation on shearing a subtree of nodes starting at \p node * * The image is not resized! * * @param node the root of the subtree to rotate * @param angleX x-shear given in degrees. * @param angleY y-shear given in degrees. * @param selection the selection we based on * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the operation being completed * right after the call */ void shearNode(KisNodeSP node, double angleX, double angleY, KisSelectionSP selection); /** * Convert the image and all its layers to the dstColorSpace */ void convertImageColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags); /** * Convert layer and all its child layers to dstColorSpace */ void convertLayerColorSpace(KisNodeSP node, const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags); // Get the profile associated with this image const KoColorProfile * profile() const; + /** + * Set the profile of the layer and all its children to the new profile. + * It doesn't do any pixel conversion. + * + * This is essential if you have loaded an image that didn't + * have an embedded profile to which you want to attach the right profile. + * + * @returns false if the profile could not be assigned + */ + bool assignLayerProfile(KisNodeSP node, const KoColorProfile *profile); + /** * Set the profile of the image to the new profile and do the same for * all layers that have the same colorspace and profile of the image. * It doesn't do any pixel conversion. * * This is essential if you have loaded an image that didn't * have an embedded profile to which you want to attach the right profile. * - * This does not create an undo action; only call it when creating or - * loading an image. - * * @returns false if the profile could not be assigned */ bool assignImageProfile(const KoColorProfile *profile); /** * Returns the current undo adapter. You can add new commands to the * undo stack using the adapter. This adapter is used for a backward * compatibility for old commands created before strokes. It blocks * all the porcessing at the scheduler, waits until it's finished * and executes commands exclusively. */ KisUndoAdapter* undoAdapter() const; /** * This adapter is used by the strokes system. The commands are added * to it *after* redo() is done (in the scheduler context). They are * wrapped into a special command and added to the undo stack. redo() * in not called. */ KisPostExecutionUndoAdapter* postExecutionUndoAdapter() const override; /** * Return the lastly executed LoD0 command. It is effectively the same * as to call undoAdapter()->presentCommand(); */ const KUndo2Command* lastExecutedCommand() const override; /** * Replace current undo store with the new one. The old store * will be deleted. * This method is used by KisDocument for dropping all the commands * during file loading. */ void setUndoStore(KisUndoStore *undoStore); /** * Return current undo store of the image */ KisUndoStore* undoStore(); /** * Tell the image it's modified; this emits the sigImageModified * signal. This happens when the image needs to be saved */ void setModified(); /** * The default colorspace of this image: new layers will have this * colorspace and the projection will have this colorspace. */ const KoColorSpace * colorSpace() const; /** * X resolution in pixels per pt */ double xRes() const; /** * Y resolution in pixels per pt */ double yRes() const; /** * Set the resolution in pixels per pt. */ void setResolution(double xres, double yres); /** * Convert a document coordinate to a pixel coordinate. * * @param documentCoord PostScript Pt coordinate to convert. */ QPointF documentToPixel(const QPointF &documentCoord) const; /** * Convert a document coordinate to an integer pixel coordinate rounded down. * * @param documentCoord PostScript Pt coordinate to convert. */ QPoint documentToImagePixelFloored(const QPointF &documentCoord) const; /** * Convert a document rectangle to a pixel rectangle. * * @param documentRect PostScript Pt rectangle to convert. */ QRectF documentToPixel(const QRectF &documentRect) const; /** * Convert a pixel coordinate to a document coordinate. * * @param pixelCoord pixel coordinate to convert. */ QPointF pixelToDocument(const QPointF &pixelCoord) const; /** * Convert an integer pixel coordinate to a document coordinate. * The document coordinate is at the centre of the pixel. * * @param pixelCoord pixel coordinate to convert. */ QPointF pixelToDocument(const QPoint &pixelCoord) const; /** * Convert a document rectangle to an integer pixel rectangle. * * @param pixelCoord pixel coordinate to convert. */ QRectF pixelToDocument(const QRectF &pixelCoord) const; /** * Return the width of the image */ qint32 width() const; /** * Return the height of the image */ qint32 height() const; /** * Return the size of the image */ QSize size() const { return QSize(width(), height()); } /** * @return the root node of the image node graph */ KisGroupLayerSP rootLayer() const; /** * Return the projection; that is, the complete, composited * representation of this image. */ KisPaintDeviceSP projection() const; /** * Return the number of layers (not other nodes) that are in this * image. */ qint32 nlayers() const; /** * Return the number of layers (not other node types) that are in * this image and that are hidden. */ qint32 nHiddenLayers() const; /** * Merge all visible layers and discard hidden ones. */ void flatten(KisNodeSP activeNode); /** * Merge the specified layer with the layer * below this layer, remove the specified layer. */ void mergeDown(KisLayerSP l, const KisMetaData::MergeStrategy* strategy); /** * flatten the layer: that is, the projection becomes the layer * and all subnodes are removed. If this is not a paint layer, it will morph * into a paint layer. */ void flattenLayer(KisLayerSP layer); /** * Merges layers in \p mergedLayers and creates a new layer above * \p putAfter */ void mergeMultipleLayers(QList mergedLayers, KisNodeSP putAfter); /// @return the exact bounds of the image in pixel coordinates. QRect bounds() const override; /** * Returns the actual bounds of the image, taking LevelOfDetail * into account. This value is used as a bounds() value of * KisDefaultBounds object. */ QRect effectiveLodBounds() const; /// use if the layers have changed _completely_ (eg. when flattening) void notifyLayersChanged(); /** * Sets the default color of the root layer projection. All the layers * will be merged on top of this very color */ void setDefaultProjectionColor(const KoColor &color); /** * \see setDefaultProjectionColor() */ KoColor defaultProjectionColor() const; void setRootLayer(KisGroupLayerSP rootLayer); /** * Add an annotation for this image. This can be anything: Gamma, EXIF, etc. * Note that the "icc" annotation is reserved for the color strategies. * If the annotation already exists, overwrite it with this one. */ void addAnnotation(KisAnnotationSP annotation); /** get the annotation with the given type, can return 0 */ KisAnnotationSP annotation(const QString& type); /** delete the annotation, if the image contains it */ void removeAnnotation(const QString& type); /** * Start of an iteration over the annotations of this image (including the ICC Profile) */ vKisAnnotationSP_it beginAnnotations(); /** end of an iteration over the annotations of this image */ vKisAnnotationSP_it endAnnotations(); /** * Called before the image is deleted and sends the sigAboutToBeDeleted signal */ void notifyAboutToBeDeleted(); KisImageSignalRouter* signalRouter(); /** * Returns whether we can reselect current global selection * * \see reselectGlobalSelection() */ bool canReselectGlobalSelection(); /** * Returns the layer compositions for the image */ QList compositions(); /** * Adds a new layer composition, will be saved with the image */ void addComposition(KisLayerCompositionSP composition); /** * Remove the layer compostion */ void removeComposition(KisLayerCompositionSP composition); /** * Permit or deny the wrap-around mode for all the paint devices * of the image. Note that permitting the wraparound mode will not * necessarily activate it right now. To be activated the wrap * around mode should be 1) permitted; 2) supported by the * currently running stroke. */ void setWrapAroundModePermitted(bool value); /** * \return whether the wrap-around mode is permitted for this * image. If the wrap around mode is permitted and the * currently running stroke supports it, the mode will be * activated for all paint devices of the image. * * \see setWrapAroundMode */ bool wrapAroundModePermitted() const; /** * \return whether the wraparound mode is activated for all the * devices of the image. The mode is activated when both * factors are true: the user permitted it and the stroke * supports it */ bool wrapAroundModeActive() const; /** * \return current level of detail which is used when processing the image. * Current working zoom = 2 ^ (- currentLevelOfDetail()). Default value is * null, which means we work on the original image. */ int currentLevelOfDetail() const; /** * Notify KisImage which level of detail should be used in the * lod-mode. Setting the mode does not guarantee the LOD to be * used. It will be activated only when the stokes supports it. */ void setDesiredLevelOfDetail(int lod); /** * Relative position of the mirror axis center * 0,0 - topleft corner of the image * 1,1 - bottomright corner of the image */ QPointF mirrorAxesCenter() const; /** * Sets the relative position of the axes center * \see mirrorAxesCenter() for details */ void setMirrorAxesCenter(const QPointF &value) const; /** * Configure the image to allow masks on the root not (as reported by * root()->allowAsChild()). By default it is not allowed (because it * looks weird from GUI point of view) */ void setAllowMasksOnRootNode(bool value); /** * \see setAllowMasksOnRootNode() */ bool allowMasksOnRootNode() const; public Q_SLOTS: /** * Explicitly start regeneration of LoD planes of all the devices * in the image. This call should be performed when the user is idle, * just to make the quality of image updates better. */ void explicitRegenerateLevelOfDetail(); public: /** * Blocks usage of level of detail functionality. After this method * has been called, no new strokes will use LoD. */ void setLevelOfDetailBlocked(bool value); /** * \see setLevelOfDetailBlocked() */ bool levelOfDetailBlocked() const; KisImageAnimationInterface *animationInterface() const; /** * @brief setProofingConfiguration, this sets the image's proofing configuration, and signals * the proofingConfiguration has changed. * @param proofingConfig - the kis proofing config that will be used instead. */ void setProofingConfiguration(KisProofingConfigurationSP proofingConfig); /** * @brief proofingConfiguration * @return the proofing configuration of the image. */ KisProofingConfigurationSP proofingConfiguration() const; public Q_SLOTS: bool startIsolatedMode(KisNodeSP node); void stopIsolatedMode(); public: KisNodeSP isolatedModeRoot() const; Q_SIGNALS: /** * Emitted whenever an action has caused the image to be * recomposited. Parameter is the rect that has been recomposited. */ void sigImageUpdated(const QRect &); /** Emitted whenever the image has been modified, so that it doesn't match with the version saved on disk. */ void sigImageModified(); /** * The signal is emitted when the size of the image is changed. * \p oldStillPoint and \p newStillPoint give the receiver the * hint about how the new and old rect of the image correspond to * each other. They specify the point of the image around which * the conversion was done. This point will stay still on the * user's screen. That is the \p newStillPoint of the new image * will be painted at the same screen position, where \p * oldStillPoint of the old image was painted. * * \param oldStillPoint is a still point represented in *old* * image coordinates * * \param newStillPoint is a still point represented in *new* * image coordinates */ void sigSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint); void sigProfileChanged(const KoColorProfile * profile); void sigColorSpaceChanged(const KoColorSpace* cs); void sigResolutionChanged(double xRes, double yRes); void sigRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes); /** * Inform the model that a node was changed */ void sigNodeChanged(KisNodeSP node); /** * Inform that the image is going to be deleted */ void sigAboutToBeDeleted(); /** * The signal is emitted right after a node has been connected * to the graph of the nodes. * * WARNING: you must not request any graph-related information * about the node being run in a not-scheduler thread. If you need * information about the parent/siblings of the node connect * with Qt::DirectConnection, get needed information and then * emit another Qt::AutoConnection signal to pass this information * to your thread. See details of the implementation * in KisDummiesfacadeBase. */ void sigNodeAddedAsync(KisNodeSP node); /** * This signal is emitted right before a node is going to removed * from the graph of the nodes. * * WARNING: you must not request any graph-related information * about the node being run in a not-scheduler thread. * * \see comment in sigNodeAddedAsync() */ void sigRemoveNodeAsync(KisNodeSP node); /** * Emitted when the root node of the image has changed. * It happens, e.g. when we flatten the image. When * this happens the receiver should reload information * about the image */ void sigLayersChangedAsync(); /** * Emitted when the UI has requested the undo of the last stroke's * operation. The point is, we cannot deal with the internals of * the stroke without its creator knowing about it (which most * probably cause a crash), so we just forward this request from * the UI to the creator of the stroke. * * If your tool supports undoing part of its work, just listen to * this signal and undo when it comes */ void sigUndoDuringStrokeRequested(); /** * Emitted when the UI has requested the cancellation of * the stroke. The point is, we cannot cancel the stroke * without its creator knowing about it (which most probably * cause a crash), so we just forward this request from the UI * to the creator of the stroke. * * If your tool supports cancelling of its work in the middle * of operation, just listen to this signal and cancel * the stroke when it comes */ void sigStrokeCancellationRequested(); /** * Emitted when the image decides that the stroke should better * be ended. The point is, we cannot just end the stroke * without its creator knowing about it (which most probably * cause a crash), so we just forward this request from the UI * to the creator of the stroke. * * If your tool supports long strokes that may involve multiple * mouse actions in one stroke, just listen to this signal and * end the stroke when it comes. */ void sigStrokeEndRequested(); /** * Same as sigStrokeEndRequested() but is not emitted when the active node * is changed. */ void sigStrokeEndRequestedActiveNodeFiltered(); /** * Emitted when the isolated mode status has changed. * * Can be used by the receivers to catch a fact of forcefully * stopping the isolated mode by the image when some complex * action was requested */ void sigIsolatedModeChanged(); /** * Emitted when one or more nodes changed the collapsed state * */ void sigNodeCollapsedChanged(); /** * Emitted when the proofing configuration of the image is being changed. * */ void sigProofingConfigChanged(); /** * Internal signal for asynchronously requesting isolated mode to stop. Don't use it * outside KisImage, use sigIsolatedModeChanged() instead. */ void sigInternalStopIsolatedModeRequested(); public Q_SLOTS: KisCompositeProgressProxy* compositeProgressProxy(); bool isIdle(bool allowLocked = false); /** * @brief Wait until all the queued background jobs are completed and lock the image. * * KisImage object has a local scheduler that executes long-running image * rendering/modifying jobs (we call them "strokes") in a background. Basically, * one should either access the image from the scope of such jobs (strokes) or * just lock the image before using. * * Calling barrierLock() will wait until all the queued operations are finished * and lock the image, so you can start accessing it in a safe way. * * @p readOnly tells the image if the caller is going to modify the image during * holding the lock. Locking with non-readOnly access will reset all * the internal caches of the image (lod-planes) when the lock status * will be lifted. */ void barrierLock(bool readOnly = false); /** * @brief Tries to lock the image without waiting for the jobs to finish * * Same as barrierLock(), but doesn't block execution of the calling thread * until all the background jobs are finished. Instead, in case of presence of * unfinished jobs in the queue, it just returns false * * @return whether the lock has been acquired * @see barrierLock */ bool tryBarrierLock(bool readOnly = false); /** * Wait for all the internal image jobs to complete and return without locking * the image. This function is handly for tests or other synchronous actions, * when one needs to wait for the result of his actions. */ void waitForDone(); KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy) override; void addJob(KisStrokeId id, KisStrokeJobData *data) override; void endStroke(KisStrokeId id) override; bool cancelStroke(KisStrokeId id) override; /** * @brief blockUpdates block updating the image projection */ void blockUpdates() override; /** * @brief unblockUpdates unblock updating the image project. This * only restarts the scheduler and does not schedule a full refresh. */ void unblockUpdates() override; /** * Disables notification of the UI about the changes in the image. * This feature is used by KisProcessingApplicator. It is needed * when we change the size of the image. In this case, the whole * image will be reloaded into UI by sigSizeChanged(), so there is * no need to inform the UI about individual dirty rects. * * The last call to enableUIUpdates() will return the list of updates * that were requested while they were blocked. */ void disableUIUpdates() override; /** * Notify GUI about a bunch of updates planned. GUI is expected to wait * until all the updates are completed, and render them on screen only * in the very and of the batch. */ void notifyBatchUpdateStarted() override; /** * Notify GUI that batch update has been completed. Now GUI can start * showing all of them on screen. */ void notifyBatchUpdateEnded() override; /** * Notify GUI that rect \p rc is now prepared in the image and * GUI can read data from it. * * WARNING: GUI will read the data right in the handler of this * signal, so exclusive access to the area must be guaranteed * by the caller. */ void notifyUIUpdateCompleted(const QRect &rc) override; /** * \see disableUIUpdates */ QVector enableUIUpdates() override; /** * Disables the processing of all the setDirty() requests that * come to the image. The incoming requests are effectively * *dropped*. * * This feature is used by KisProcessingApplicator. For many cases * it provides its own updates interface, which recalculates the * whole subtree of nodes. But while we change any particular * node, it can ask for an update itself. This method is a way of * blocking such intermediate (and excessive) requests. * * NOTE: this is a convenience function for setProjectionUpdatesFilter() * that installs a predefined filter that eats everything. Please * note that these calls are *not* recursive */ void disableDirtyRequests() override; /** * \see disableDirtyRequests() */ void enableDirtyRequests() override; /** * Installs a filter object that will filter all the incoming projection update * requests. If the filter return true, the incoming update is dropped. * * NOTE: you cannot set filters recursively! */ void setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter) override; /** * \see setProjectionUpdatesFilter() */ KisProjectionUpdatesFilterSP projectionUpdatesFilter() const override; void refreshGraphAsync(KisNodeSP root = KisNodeSP()) override; void refreshGraphAsync(KisNodeSP root, const QRect &rc) override; void refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect) override; /** * Triggers synchronous recomposition of the projection */ void refreshGraph(KisNodeSP root = KisNodeSP()); void refreshGraph(KisNodeSP root, const QRect& rc, const QRect &cropRect); void initialRefreshGraph(); /** * Initiate a stack regeneration skipping the recalculation of the * filthy node's projection. * * Works exactly as pseudoFilthy->setDirty() with the only * exception that pseudoFilthy::updateProjection() will not be * called. That is used by KisRecalculateTransformMaskJob to avoid * cyclic dependencies. */ void requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect); /** * Adds a spontaneous job to the updates queue. * * A spontaneous job may do some trivial tasks in the background, * like updating the outline of selection or purging unused tiles * from the existing paint devices. */ void addSpontaneousJob(KisSpontaneousJob *spontaneousJob); /** * \return true if there are some updates in the updates queue * Please note, that is doesn't guarantee that there are no updates * running in in the updater context at the very moment. To guarantee that * there are no updates left at all, please use barrier jobs instead. */ bool hasUpdatesRunning() const override; /** * This method is called by the UI (*not* by the creator of the * stroke) when it thinks the current stroke should undo its last * action, for example, when the user presses Ctrl+Z while some * stroke is active. * * If the creator of the stroke supports undoing of intermediate * actions, it will be notified about this request and can undo * its last action. */ void requestUndoDuringStroke(); /** * This method is called by the UI (*not* by the creator of the * stroke) when it thinks current stroke should be cancelled. If * there is a running stroke that has already been detached from * its creator (ended or cancelled), it will be forcefully * cancelled and reverted. If there is an open stroke present, and * if its creator supports cancelling, it will be notified about * the request and the stroke will be cancelled */ void requestStrokeCancellation(); /** * This method requests the last stroke executed on the image to become undone. * If the stroke is not ended, or if all the Lod0 strokes are completed, the method * returns UNDO_FAIL. If the last Lod0 is going to be finished soon, then UNDO_WAIT * is returned and the caller should just wait for its completion and call global undo * instead. UNDO_OK means one unfinished stroke has been undone. */ UndoResult tryUndoUnfinishedLod0Stroke(); /** * This method is called when image or some other part of Krita * (*not* the creator of the stroke) decides that the stroke * should be ended. If the creator of the stroke supports it, it * will be notified and the stroke will be cancelled */ void requestStrokeEnd(); /** * Same as requestStrokeEnd() but is called by view manager when * the current node is changed. Use to distinguish * sigStrokeEndRequested() and * sigStrokeEndRequestedActiveNodeFiltered() which are used by * KisNodeJugglerCompressed */ void requestStrokeEndActiveNode(); private: KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy); KisImage& operator=(const KisImage& rhs); void emitSizeChanged(); void resizeImageImpl(const QRect& newRect, bool cropLayers); void rotateImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, double radians, bool resizeImage, KisSelectionSP selection); void shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double angleX, double angleY, KisSelectionSP selection); void safeRemoveTwoNodes(KisNodeSP node1, KisNodeSP node2); void refreshHiddenArea(KisNodeSP rootNode, const QRect &preparedArea); void requestProjectionUpdateImpl(KisNode *node, const QVector &rects, const QRect &cropRect); friend class KisImageResizeCommand; void setSize(const QSize& size); friend class KisImageSetProjectionColorSpaceCommand; void setProjectionColorSpace(const KoColorSpace * colorSpace); friend class KisDeselectGlobalSelectionCommand; friend class KisReselectGlobalSelectionCommand; friend class KisSetGlobalSelectionCommand; friend class KisImageTest; friend class Document; // For libkis /** * Replaces the current global selection with globalSelection. If * \p globalSelection is empty, removes the selection object, so that * \ref globalSelection() will return 0 after that. */ void setGlobalSelection(KisSelectionSP globalSelection); /** * Deselects current global selection. * \ref globalSelection() will return 0 after that. */ void deselectGlobalSelection(); /** * Reselects current deselected selection * * \see deselectGlobalSelection() */ void reselectGlobalSelection(); private: class KisImagePrivate; KisImagePrivate * m_d; }; #endif // KIS_IMAGE_H_ diff --git a/libs/image/kis_paint_device.cc b/libs/image/kis_paint_device.cc index 7bac1420cd..9c29a18271 100644 --- a/libs/image/kis_paint_device.cc +++ b/libs/image/kis_paint_device.cc @@ -1,2215 +1,2236 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_paint_device.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_image.h" #include "kis_random_sub_accessor.h" #include "kis_selection.h" #include "kis_node.h" #include "kis_datamanager.h" #include "kis_paint_device_writer.h" #include "kis_selection_component.h" #include "kis_pixel_selection.h" #include "kis_repeat_iterators_pixel.h" #include "kis_fixed_paint_device.h" #include "tiles3/kis_hline_iterator.h" #include "tiles3/kis_vline_iterator.h" #include "tiles3/kis_random_accessor.h" #include "kis_default_bounds.h" #include "kis_lod_transform.h" #include "kis_raster_keyframe_channel.h" #include "kis_paint_device_cache.h" #include "kis_paint_device_data.h" #include "kis_paint_device_frames_interface.h" #include "kis_transform_worker.h" #include "kis_filter_strategy.h" #include "krita_utils.h" struct KisPaintDeviceSPStaticRegistrar { KisPaintDeviceSPStaticRegistrar() { qRegisterMetaType("KisPaintDeviceSP"); } }; static KisPaintDeviceSPStaticRegistrar __registrar; struct KisPaintDevice::Private { /** * Used when the paint device is loading to ensure no lod/animation * interferes the process. */ static const KisDefaultBoundsSP transitionalDefaultBounds; public: class KisPaintDeviceStrategy; class KisPaintDeviceWrappedStrategy; + class DeviceChangeProfileCommand; + class DeviceChangeColorSpaceCommand; + Private(KisPaintDevice *paintDevice); ~Private(); KisPaintDevice *q; KisNodeWSP parent; QScopedPointer contentChannel; KisDefaultBoundsBaseSP defaultBounds; QScopedPointer basicStrategy; QScopedPointer wrappedStrategy; QMutex m_wrappedStrategyMutex; QScopedPointer framesInterface; bool isProjectionDevice; KisPaintDeviceStrategy* currentStrategy(); void init(const KoColorSpace *cs, const quint8 *defaultPixel); void convertColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, KUndo2Command *parentCommand); - bool assignProfile(const KoColorProfile * profile); + bool assignProfile(const KoColorProfile * profile, KUndo2Command *parentCommand); inline const KoColorSpace* colorSpace() const { return currentData()->colorSpace(); } inline KisDataManagerSP dataManager() const { return currentData()->dataManager(); } inline qint32 x() const { return currentData()->x(); } inline qint32 y() const { return currentData()->y(); } inline void setX(qint32 x) { currentData()->setX(x); } inline void setY(qint32 y) { currentData()->setY(y); } inline KisPaintDeviceCache* cache() { return currentData()->cache(); } inline KisIteratorCompleteListener* cacheInvalidator() { return currentData()->cacheInvalidator(); } void cloneAllDataObjects(Private *rhs, bool copyFrames) { m_lodData.reset(); m_externalFrameData.reset(); if (!m_frames.isEmpty()) { m_frames.clear(); } if (!copyFrames) { if (m_data) { m_data->prepareClone(rhs->currentNonLodData(), true); } else { m_data = toQShared(new KisPaintDeviceData(q, rhs->currentNonLodData(), true)); } } else { if (m_data && !rhs->m_data) { m_data.clear(); } else if (!m_data && rhs->m_data) { m_data = toQShared(new KisPaintDeviceData(q, rhs->m_data.data(), true)); } else if (m_data && rhs->m_data) { m_data->prepareClone(rhs->m_data.data(), true); } if (!rhs->m_frames.isEmpty()) { FramesHash::const_iterator it = rhs->m_frames.constBegin(); FramesHash::const_iterator end = rhs->m_frames.constEnd(); for (; it != end; ++it) { DataSP data = toQShared(new KisPaintDeviceData(q, it.value().data(), true)); m_frames.insert(it.key(), data); } } m_nextFreeFrameId = rhs->m_nextFreeFrameId; } if (rhs->m_lodData) { m_lodData.reset(new KisPaintDeviceData(q, rhs->m_lodData.data(), true)); } } void prepareClone(KisPaintDeviceSP src) { prepareCloneImpl(src, src->m_d->currentData()); Q_ASSERT(fastBitBltPossible(src)); } bool fastBitBltPossible(KisPaintDeviceSP src) { return fastBitBltPossibleImpl(src->m_d->currentData()); } int currentFrameId() const { KIS_ASSERT_RECOVER(contentChannel) { return -1; } return !defaultBounds->currentLevelOfDetail() ? contentChannel->frameIdAt(defaultBounds->currentTime()) : -1; } KisDataManagerSP frameDataManager(int frameId) const { DataSP data = m_frames[frameId]; return data->dataManager(); } void invalidateFrameCache(int frameId) { DataSP data = m_frames[frameId]; return data->cache()->invalidate(); } private: typedef KisPaintDeviceData Data; typedef QSharedPointer DataSP; typedef QHash FramesHash; class FrameInsertionCommand : public KUndo2Command { public: FrameInsertionCommand(FramesHash *hash, DataSP data, int frameId, bool insert, KUndo2Command *parentCommand) : KUndo2Command(parentCommand), m_hash(hash), m_data(data), m_frameId(frameId), m_insert(insert) { } void redo() override { doSwap(m_insert); } void undo() override { doSwap(!m_insert); } private: void doSwap(bool insert) { if (insert) { m_hash->insert(m_frameId, m_data); } else { DataSP deletedData = m_hash->take(m_frameId); } } private: FramesHash *m_hash; DataSP m_data; int m_frameId; bool m_insert; }; public: int getNextFrameId() { int frameId = 0; while (m_frames.contains(frameId = m_nextFreeFrameId++)); KIS_SAFE_ASSERT_RECOVER_NOOP(!m_frames.contains(frameId)); return frameId; } int createFrame(bool copy, int copySrc, const QPoint &offset, KUndo2Command *parentCommand) { KIS_ASSERT_RECOVER(parentCommand) { return -1; } DataSP data; bool initialFrame = false; if (m_frames.isEmpty()) { /** * Here we move the contents of the paint device to the * new frame and clear m_data to make the "background" for * the areas where there is no frame at all. */ data = toQShared(new Data(q, m_data.data(), true)); m_data->dataManager()->clear(); m_data->cache()->invalidate(); initialFrame = true; } else if (copy) { DataSP srcData = m_frames[copySrc]; data = toQShared(new Data(q, srcData.data(), true)); } else { DataSP srcData = m_frames.begin().value(); data = toQShared(new Data(q, srcData.data(), false)); } if (!initialFrame && !copy) { data->setX(offset.x()); data->setY(offset.y()); } int frameId = getNextFrameId(); KUndo2Command *cmd = new FrameInsertionCommand(&m_frames, data, frameId, true, parentCommand); cmd->redo(); return frameId; } void deleteFrame(int frame, KUndo2Command *parentCommand) { KIS_ASSERT_RECOVER_RETURN(m_frames.contains(frame)); KIS_ASSERT_RECOVER_RETURN(parentCommand); DataSP deletedData = m_frames[frame]; KUndo2Command *cmd = new FrameInsertionCommand(&m_frames, deletedData, frame, false, parentCommand); cmd->redo(); } QRect frameBounds(int frameId) { DataSP data = m_frames[frameId]; QRect extent = data->dataManager()->extent(); extent.translate(data->x(), data->y()); return extent; } QPoint frameOffset(int frameId) const { DataSP data = m_frames[frameId]; return QPoint(data->x(), data->y()); } void setFrameOffset(int frameId, const QPoint &offset) { DataSP data = m_frames[frameId]; data->setX(offset.x()); data->setY(offset.y()); } const QList frameIds() const { return m_frames.keys(); } bool readFrame(QIODevice *stream, int frameId) { bool retval = false; DataSP data = m_frames[frameId]; retval = data->dataManager()->read(stream); data->cache()->invalidate(); return retval; } bool writeFrame(KisPaintDeviceWriter &store, int frameId) { DataSP data = m_frames[frameId]; return data->dataManager()->write(store); } void setFrameDefaultPixel(const KoColor &defPixel, int frameId) { DataSP data = m_frames[frameId]; KoColor color(defPixel); color.convertTo(data->colorSpace()); data->dataManager()->setDefaultPixel(color.data()); } KoColor frameDefaultPixel(int frameId) const { DataSP data = m_frames[frameId]; return KoColor(data->dataManager()->defaultPixel(), data->colorSpace()); } void fetchFrame(int frameId, KisPaintDeviceSP targetDevice); void uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice); void uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice); void uploadFrameData(DataSP srcData, DataSP dstData); struct LodDataStructImpl; LodDataStruct* createLodDataStruct(int lod); void updateLodDataStruct(LodDataStruct *dst, const QRect &srcRect); void uploadLodDataStruct(LodDataStruct *dst); QRegion regionForLodSyncing() const; void updateLodDataManager(KisDataManager *srcDataManager, KisDataManager *dstDataManager, const QPoint &srcOffset, const QPoint &dstOffset, const QRect &originalRect, int lod); void generateLodCloneDevice(KisPaintDeviceSP dst, const QRect &originalRect, int lod); void tesingFetchLodDevice(KisPaintDeviceSP targetDevice); private: qint64 estimateDataSize(Data *data) const { const QRect &rc = data->dataManager()->extent(); return rc.width() * rc.height() * data->colorSpace()->pixelSize(); } public: void estimateMemoryStats(qint64 &imageData, qint64 &temporaryData, qint64 &lodData) const { imageData = 0; temporaryData = 0; lodData = 0; if (m_data) { imageData += estimateDataSize(m_data.data()); } if (m_lodData) { lodData += estimateDataSize(m_lodData.data()); } if (m_externalFrameData) { temporaryData += estimateDataSize(m_externalFrameData.data()); } Q_FOREACH (DataSP value, m_frames.values()) { imageData += estimateDataSize(value.data()); } } private: QRegion syncWholeDevice(Data *srcData); inline DataSP currentFrameData() const { DataSP data; const int numberOfFrames = contentChannel->keyframeCount(); if (numberOfFrames > 1) { int frameId = contentChannel->frameIdAt(defaultBounds->currentTime()); if (frameId == -1) { data = m_data; } else { KIS_ASSERT_RECOVER(m_frames.contains(frameId)) { return m_frames.begin().value(); } data = m_frames[frameId]; } } else if (numberOfFrames == 1) { data = m_frames.begin().value(); } else { data = m_data; } return data; } inline Data* currentNonLodData() const { Data *data = m_data.data(); if (contentChannel) { data = currentFrameData().data(); } else if (isProjectionDevice && defaultBounds->externalFrameActive()) { if (!m_externalFrameData) { QMutexLocker l(&m_dataSwitchLock); if (!m_externalFrameData) { m_externalFrameData.reset(new Data(q, m_data.data(), false)); } } data = m_externalFrameData.data(); } return data; } inline void ensureLodDataPresent() const { if (!m_lodData) { Data *srcData = currentNonLodData(); QMutexLocker l(&m_dataSwitchLock); if (!m_lodData) { m_lodData.reset(new Data(q, srcData, false)); } } } inline Data* currentData() const { Data *data; if (defaultBounds->currentLevelOfDetail()) { ensureLodDataPresent(); data = m_lodData.data(); } else { data = currentNonLodData(); } return data; } void prepareCloneImpl(KisPaintDeviceSP src, Data *srcData) { currentData()->prepareClone(srcData); q->setDefaultPixel(KoColor(srcData->dataManager()->defaultPixel(), colorSpace())); q->setDefaultBounds(src->defaultBounds()); } bool fastBitBltPossibleImpl(Data *srcData) { return x() == srcData->x() && y() == srcData->y() && *colorSpace() == *srcData->colorSpace(); } QList allDataObjects() const { QList dataObjects; if (m_frames.isEmpty()) { dataObjects << m_data.data(); } dataObjects << m_lodData.data(); dataObjects << m_externalFrameData.data(); Q_FOREACH (DataSP value, m_frames.values()) { dataObjects << value.data(); } return dataObjects; } void transferFromData(Data *data, KisPaintDeviceSP targetDevice); struct Q_DECL_HIDDEN StrategyPolicy; typedef KisSequentialIteratorBase, StrategyPolicy> InternalSequentialConstIterator; typedef KisSequentialIteratorBase, StrategyPolicy> InternalSequentialIterator; private: friend class KisPaintDeviceFramesInterface; private: DataSP m_data; mutable QScopedPointer m_lodData; mutable QScopedPointer m_externalFrameData; mutable QMutex m_dataSwitchLock; FramesHash m_frames; int m_nextFreeFrameId; }; const KisDefaultBoundsSP KisPaintDevice::Private::transitionalDefaultBounds = new KisDefaultBounds(); #include "kis_paint_device_strategies.h" KisPaintDevice::Private::Private(KisPaintDevice *paintDevice) : q(paintDevice), basicStrategy(new KisPaintDeviceStrategy(paintDevice, this)), isProjectionDevice(false), m_data(new Data(paintDevice)), m_nextFreeFrameId(0) { } KisPaintDevice::Private::~Private() { m_frames.clear(); } KisPaintDevice::Private::KisPaintDeviceStrategy* KisPaintDevice::Private::currentStrategy() { if (!defaultBounds->wrapAroundMode()) { return basicStrategy.data(); } const QRect wrapRect = defaultBounds->bounds(); if (!wrappedStrategy || wrappedStrategy->wrapRect() != wrapRect) { QMutexLocker locker(&m_wrappedStrategyMutex); if (!wrappedStrategy) { wrappedStrategy.reset(new KisPaintDeviceWrappedStrategy(wrapRect, q, this)); } else if (wrappedStrategy->wrapRect() != wrapRect) { wrappedStrategy->setWrapRect(wrapRect); } } return wrappedStrategy.data(); } struct KisPaintDevice::Private::StrategyPolicy { StrategyPolicy(KisPaintDevice::Private::KisPaintDeviceStrategy *strategy, KisDataManager *dataManager, qint32 offsetX, qint32 offsetY) : m_strategy(strategy), m_dataManager(dataManager), m_offsetX(offsetX), m_offsetY(offsetY) { } KisHLineConstIteratorSP createConstIterator(const QRect &rect) { return m_strategy->createHLineConstIteratorNG(m_dataManager, rect.x(), rect.y(), rect.width(), m_offsetX, m_offsetY); } KisHLineIteratorSP createIterator(const QRect &rect) { return m_strategy->createHLineIteratorNG(m_dataManager, rect.x(), rect.y(), rect.width(), m_offsetX, m_offsetY); } int pixelSize() const { return m_dataManager->pixelSize(); } KisPaintDeviceStrategy *m_strategy; KisDataManager *m_dataManager; int m_offsetX; int m_offsetY; }; struct KisPaintDevice::Private::LodDataStructImpl : public KisPaintDevice::LodDataStruct { LodDataStructImpl(Data *_lodData) : lodData(_lodData) {} QScopedPointer lodData; }; QRegion KisPaintDevice::Private::regionForLodSyncing() const { Data *srcData = currentNonLodData(); return srcData->dataManager()->region().translated(srcData->x(), srcData->y()); } KisPaintDevice::LodDataStruct* KisPaintDevice::Private::createLodDataStruct(int newLod) { KIS_SAFE_ASSERT_RECOVER_NOOP(newLod > 0); Data *srcData = currentNonLodData(); Data *lodData = new Data(q, srcData, false); LodDataStruct *lodStruct = new LodDataStructImpl(lodData); int expectedX = KisLodTransform::coordToLodCoord(srcData->x(), newLod); int expectedY = KisLodTransform::coordToLodCoord(srcData->y(), newLod); /** * We compare color spaces as pure pointers, because they must be * exactly the same, since they come from the common source. */ if (lodData->levelOfDetail() != newLod || lodData->colorSpace() != srcData->colorSpace() || lodData->x() != expectedX || lodData->y() != expectedY) { lodData->prepareClone(srcData); lodData->setLevelOfDetail(newLod); lodData->setX(expectedX); lodData->setY(expectedY); // FIXME: different kind of synchronization } //QRegion dirtyRegion = syncWholeDevice(srcData); lodData->cache()->invalidate(); return lodStruct; } void KisPaintDevice::Private::updateLodDataManager(KisDataManager *srcDataManager, KisDataManager *dstDataManager, const QPoint &srcOffset, const QPoint &dstOffset, const QRect &originalRect, int lod) { if (originalRect.isEmpty()) return; const int srcStepSize = 1 << lod; KIS_ASSERT_RECOVER_RETURN(lod > 0); const QRect srcRect = KisLodTransform::alignedRect(originalRect, lod); const QRect dstRect = KisLodTransform::scaledRect(srcRect, lod); if (!srcRect.isValid() || !dstRect.isValid()) return; KIS_ASSERT_RECOVER_NOOP(srcRect.width() / srcStepSize == dstRect.width()); const int pixelSize = srcDataManager->pixelSize(); int rowsAccumulated = 0; int columnsAccumulated = 0; KoMixColorsOp *mixOp = colorSpace()->mixColorsOp(); QScopedArrayPointer blendData(new quint8[srcStepSize * srcRect.width() * pixelSize]); quint8 *blendDataPtr = blendData.data(); int blendDataOffset = 0; const int srcCellSize = srcStepSize * srcStepSize; const int srcCellStride = srcCellSize * pixelSize; const int srcStepStride = srcStepSize * pixelSize; const int srcColumnStride = (srcStepSize - 1) * srcStepStride; QScopedArrayPointer weights(new qint16[srcCellSize]); { const qint16 averageWeight = qCeil(255.0 / srcCellSize); const qint16 extraWeight = averageWeight * srcCellSize - 255; KIS_ASSERT_RECOVER_NOOP(extraWeight == 1); for (int i = 0; i < srcCellSize - 1; i++) { weights[i] = averageWeight; } weights[srcCellSize - 1] = averageWeight - extraWeight; } InternalSequentialConstIterator srcIntIt(StrategyPolicy(currentStrategy(), srcDataManager, srcOffset.x(), srcOffset.y()), srcRect); InternalSequentialIterator dstIntIt(StrategyPolicy(currentStrategy(), dstDataManager, dstOffset.x(), dstOffset.y()), dstRect); int rowsRemaining = srcRect.height(); while (rowsRemaining > 0) { int colsRemaining = srcRect.width(); while (colsRemaining > 0 && srcIntIt.nextPixel()) { memcpy(blendDataPtr, srcIntIt.rawDataConst(), pixelSize); blendDataPtr += pixelSize; columnsAccumulated++; if (columnsAccumulated >= srcStepSize) { blendDataPtr += srcColumnStride; columnsAccumulated = 0; } colsRemaining--; } rowsAccumulated++; if (rowsAccumulated >= srcStepSize) { // blend and write the final data blendDataPtr = blendData.data(); int colsRemaining = dstRect.width(); while (colsRemaining > 0 && dstIntIt.nextPixel()) { mixOp->mixColors(blendDataPtr, weights.data(), srcCellSize, dstIntIt.rawData()); blendDataPtr += srcCellStride; colsRemaining--; } // reset counters rowsAccumulated = 0; blendDataPtr = blendData.data(); blendDataOffset = 0; } else { blendDataOffset += srcStepStride; blendDataPtr = blendData.data() + blendDataOffset; } rowsRemaining--; } } void KisPaintDevice::Private::updateLodDataStruct(LodDataStruct *_dst, const QRect &originalRect) { LodDataStructImpl *dst = dynamic_cast(_dst); KIS_SAFE_ASSERT_RECOVER_RETURN(dst); Data *lodData = dst->lodData.data(); Data *srcData = currentNonLodData(); const int lod = lodData->levelOfDetail(); updateLodDataManager(srcData->dataManager().data(), lodData->dataManager().data(), QPoint(srcData->x(), srcData->y()), QPoint(lodData->x(), lodData->y()), originalRect, lod); } void KisPaintDevice::Private::generateLodCloneDevice(KisPaintDeviceSP dst, const QRect &originalRect, int lod) { KIS_SAFE_ASSERT_RECOVER_RETURN(fastBitBltPossible(dst)); Data *srcData = currentNonLodData(); updateLodDataManager(srcData->dataManager().data(), dst->dataManager().data(), QPoint(srcData->x(), srcData->y()), QPoint(dst->x(), dst->y()), originalRect, lod); } void KisPaintDevice::Private::uploadLodDataStruct(LodDataStruct *_dst) { LodDataStructImpl *dst = dynamic_cast(_dst); KIS_SAFE_ASSERT_RECOVER_RETURN(dst); KIS_SAFE_ASSERT_RECOVER_RETURN( dst->lodData->levelOfDetail() == defaultBounds->currentLevelOfDetail()); ensureLodDataPresent(); m_lodData->prepareClone(dst->lodData.data()); m_lodData->dataManager()->bitBltRough(dst->lodData->dataManager(), dst->lodData->dataManager()->extent()); } void KisPaintDevice::Private::transferFromData(Data *data, KisPaintDeviceSP targetDevice) { QRect extent = data->dataManager()->extent(); extent.translate(data->x(), data->y()); targetDevice->m_d->prepareCloneImpl(q, data); targetDevice->m_d->currentStrategy()->fastBitBltRough(data->dataManager(), extent); } void KisPaintDevice::Private::fetchFrame(int frameId, KisPaintDeviceSP targetDevice) { DataSP data = m_frames[frameId]; transferFromData(data.data(), targetDevice); } void KisPaintDevice::Private::uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice) { DataSP dstData = m_frames[dstFrameId]; KIS_ASSERT_RECOVER_RETURN(dstData); DataSP srcData = srcDevice->m_d->m_frames[srcFrameId]; KIS_ASSERT_RECOVER_RETURN(srcData); uploadFrameData(srcData, dstData); } void KisPaintDevice::Private::uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice) { DataSP dstData = m_frames[dstFrameId]; KIS_ASSERT_RECOVER_RETURN(dstData); DataSP srcData = srcDevice->m_d->m_data; KIS_ASSERT_RECOVER_RETURN(srcData); uploadFrameData(srcData, dstData); } void KisPaintDevice::Private::uploadFrameData(DataSP srcData, DataSP dstData) { if (srcData->colorSpace() != dstData->colorSpace() && *srcData->colorSpace() != *dstData->colorSpace()) { KUndo2Command tempCommand; srcData = toQShared(new Data(q, srcData.data(), true)); srcData->convertDataColorSpace(dstData->colorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags(), &tempCommand); } dstData->dataManager()->clear(); dstData->cache()->invalidate(); const QRect rect = srcData->dataManager()->extent(); dstData->dataManager()->bitBltRough(srcData->dataManager(), rect); dstData->setX(srcData->x()); dstData->setY(srcData->y()); } void KisPaintDevice::Private::tesingFetchLodDevice(KisPaintDeviceSP targetDevice) { Data *data = m_lodData.data(); Q_ASSERT(data); transferFromData(data, targetDevice); } -void KisPaintDevice::Private::convertColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, KUndo2Command *parentCommand) +class KisPaintDevice::Private::DeviceChangeProfileCommand : public KUndo2Command { +public: + DeviceChangeProfileCommand(KisPaintDeviceSP device, KUndo2Command *parent = 0) + : KUndo2Command(parent), + m_firstRun(true), + m_device(device) + { + } - class DeviceChangeColorSpaceCommand : public KUndo2Command + virtual void emitNotifications() { - public: - DeviceChangeColorSpaceCommand(KisPaintDeviceSP device, KUndo2Command *parent = 0) - : KUndo2Command(parent), - m_firstRun(true), - m_device(device) - { - } + m_device->emitProfileChanged(); + } - void emitNotifications() - { - m_device->emitColorSpaceChanged(); + void redo() override + { + KUndo2Command::redo(); + + if (!m_firstRun) { + m_firstRun = false; + return; } - void redo() override - { - KUndo2Command::redo(); + emitNotifications(); + } - if (!m_firstRun) { - m_firstRun = false; - return; - } + void undo() override + { + KUndo2Command::undo(); + emitNotifications(); + } - emitNotifications(); - } +protected: + KisPaintDeviceSP m_device; - void undo() override - { - KUndo2Command::undo(); - emitNotifications(); - } +private: + bool m_firstRun; +}; - private: - bool m_firstRun; - KisPaintDeviceSP m_device; - }; +class KisPaintDevice::Private::DeviceChangeColorSpaceCommand : public DeviceChangeProfileCommand +{ +public: + DeviceChangeColorSpaceCommand(KisPaintDeviceSP device, KUndo2Command *parent = 0) + : DeviceChangeProfileCommand(device, parent) + { + } + void emitNotifications() override + { + m_device->emitColorSpaceChanged(); + } +}; +void KisPaintDevice::Private::convertColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, KUndo2Command *parentCommand) +{ QList dataObjects = allDataObjects(); if (dataObjects.isEmpty()) return; KUndo2Command *mainCommand = parentCommand ? new DeviceChangeColorSpaceCommand(q, parentCommand) : 0; Q_FOREACH (Data *data, dataObjects) { if (!data) continue; data->convertDataColorSpace(dstColorSpace, renderingIntent, conversionFlags, mainCommand); } q->emitColorSpaceChanged(); } -bool KisPaintDevice::Private::assignProfile(const KoColorProfile * profile) +bool KisPaintDevice::Private::assignProfile(const KoColorProfile * profile, KUndo2Command *parentCommand) { if (!profile) return false; const KoColorSpace *dstColorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); if (!dstColorSpace) return false; + KUndo2Command *mainCommand = + parentCommand ? new DeviceChangeColorSpaceCommand(q, parentCommand) : 0; + + QList dataObjects = allDataObjects(); Q_FOREACH (Data *data, dataObjects) { if (!data) continue; - data->assignColorSpace(dstColorSpace); + data->assignColorSpace(dstColorSpace, mainCommand); } q->emitProfileChanged(); // no undo information is provided here return true; } void KisPaintDevice::Private::init(const KoColorSpace *cs, const quint8 *defaultPixel) { QList dataObjects = allDataObjects(); Q_FOREACH (Data *data, dataObjects) { if (!data) continue; KisDataManagerSP dataManager = new KisDataManager(cs->pixelSize(), defaultPixel); data->init(cs, dataManager); } } KisPaintDevice::KisPaintDevice(const KoColorSpace * colorSpace, const QString& name) : QObject(0) , m_d(new Private(this)) { init(colorSpace, new KisDefaultBounds(), 0, name); } KisPaintDevice::KisPaintDevice(KisNodeWSP parent, const KoColorSpace * colorSpace, KisDefaultBoundsBaseSP defaultBounds, const QString& name) : QObject(0) , m_d(new Private(this)) { init(colorSpace, defaultBounds, parent, name); } void KisPaintDevice::init(const KoColorSpace *colorSpace, KisDefaultBoundsBaseSP defaultBounds, KisNodeWSP parent, const QString& name) { Q_ASSERT(colorSpace); setObjectName(name); // temporary def. bounds object for the initialization phase only m_d->defaultBounds = m_d->transitionalDefaultBounds; if (!defaultBounds) { // Reuse transitionalDefaultBounds here. Change if you change // semantics of transitionalDefaultBounds defaultBounds = m_d->transitionalDefaultBounds; } QScopedArrayPointer defaultPixel(new quint8[colorSpace->pixelSize()]); colorSpace->fromQColor(Qt::transparent, defaultPixel.data()); m_d->init(colorSpace, defaultPixel.data()); Q_ASSERT(m_d->colorSpace()); setDefaultBounds(defaultBounds); setParentNode(parent); } KisPaintDevice::KisPaintDevice(const KisPaintDevice& rhs, KritaUtils::DeviceCopyMode copyMode, KisNode *newParentNode) : QObject() , KisShared() , m_d(new Private(this)) { if (this != &rhs) { makeFullCopyFrom(rhs, copyMode, newParentNode); } } void KisPaintDevice::makeFullCopyFrom(const KisPaintDevice &rhs, KritaUtils::DeviceCopyMode copyMode, KisNode *newParentNode) { // temporary def. bounds object for the initialization phase only m_d->defaultBounds = m_d->transitionalDefaultBounds; // copy data objects with or without frames m_d->cloneAllDataObjects(rhs.m_d, copyMode == KritaUtils::CopyAllFrames); if (copyMode == KritaUtils::CopyAllFrames && rhs.m_d->framesInterface) { KIS_ASSERT_RECOVER_RETURN(rhs.m_d->framesInterface); KIS_ASSERT_RECOVER_RETURN(rhs.m_d->contentChannel); m_d->framesInterface.reset(new KisPaintDeviceFramesInterface(this)); m_d->contentChannel.reset(new KisRasterKeyframeChannel(*rhs.m_d->contentChannel.data(), newParentNode, this)); } setDefaultBounds(rhs.m_d->defaultBounds); setParentNode(newParentNode); } KisPaintDevice::~KisPaintDevice() { delete m_d; } void KisPaintDevice::setProjectionDevice(bool value) { m_d->isProjectionDevice = value; } void KisPaintDevice::prepareClone(KisPaintDeviceSP src) { m_d->prepareClone(src); Q_ASSERT(fastBitBltPossible(src)); } void KisPaintDevice::makeCloneFrom(KisPaintDeviceSP src, const QRect &rect) { prepareClone(src); // we guarantee that *this is totally empty, so copy pixels that // are areally present on the source image only const QRect optimizedRect = rect & src->extent(); fastBitBlt(src, optimizedRect); } void KisPaintDevice::makeCloneFromRough(KisPaintDeviceSP src, const QRect &minimalRect) { prepareClone(src); // we guarantee that *this is totally empty, so copy pixels that // are areally present on the source image only const QRect optimizedRect = minimalRect & src->extent(); fastBitBltRough(src, optimizedRect); } void KisPaintDevice::setDirty() { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(); } void KisPaintDevice::setDirty(const QRect & rc) { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(rc); } void KisPaintDevice::setDirty(const QRegion & region) { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(region); } void KisPaintDevice::setDirty(const QVector rects) { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(rects); } void KisPaintDevice::requestTimeSwitch(int time) { if (m_d->parent.isValid()) { m_d->parent->requestTimeSwitch(time); } } int KisPaintDevice::sequenceNumber() const { return m_d->cache()->sequenceNumber(); } void KisPaintDevice::estimateMemoryStats(qint64 &imageData, qint64 &temporaryData, qint64 &lodData) const { m_d->estimateMemoryStats(imageData, temporaryData, lodData); } void KisPaintDevice::setParentNode(KisNodeWSP parent) { m_d->parent = parent; } // for testing purposes only KisNodeWSP KisPaintDevice::parentNode() const { return m_d->parent; } void KisPaintDevice::setDefaultBounds(KisDefaultBoundsBaseSP defaultBounds) { m_d->defaultBounds = defaultBounds; m_d->cache()->invalidate(); } KisDefaultBoundsBaseSP KisPaintDevice::defaultBounds() const { return m_d->defaultBounds; } void KisPaintDevice::moveTo(const QPoint &pt) { m_d->currentStrategy()->move(pt); m_d->cache()->invalidate(); } QPoint KisPaintDevice::offset() const { return QPoint(x(), y()); } void KisPaintDevice::moveTo(qint32 x, qint32 y) { moveTo(QPoint(x, y)); } void KisPaintDevice::setX(qint32 x) { moveTo(QPoint(x, m_d->y())); } void KisPaintDevice::setY(qint32 y) { moveTo(QPoint(m_d->x(), y)); } qint32 KisPaintDevice::x() const { return m_d->x(); } qint32 KisPaintDevice::y() const { return m_d->y(); } void KisPaintDevice::extent(qint32 &x, qint32 &y, qint32 &w, qint32 &h) const { QRect rc = extent(); x = rc.x(); y = rc.y(); w = rc.width(); h = rc.height(); } QRect KisPaintDevice::extent() const { return m_d->currentStrategy()->extent(); } QRegion KisPaintDevice::region() const { return m_d->currentStrategy()->region(); } QRect KisPaintDevice::nonDefaultPixelArea() const { return m_d->cache()->nonDefaultPixelArea(); } QRect KisPaintDevice::exactBounds() const { return m_d->cache()->exactBounds(); } QRect KisPaintDevice::exactBoundsAmortized() const { return m_d->cache()->exactBoundsAmortized(); } namespace Impl { struct CheckFullyTransparent { CheckFullyTransparent(const KoColorSpace *colorSpace) : m_colorSpace(colorSpace) { } bool isPixelEmpty(const quint8 *pixelData) { return m_colorSpace->opacityU8(pixelData) == OPACITY_TRANSPARENT_U8; } private: const KoColorSpace *m_colorSpace; }; struct CheckNonDefault { CheckNonDefault(int pixelSize, const quint8 *defaultPixel) : m_pixelSize(pixelSize), m_defaultPixel(defaultPixel) { } bool isPixelEmpty(const quint8 *pixelData) { return memcmp(m_defaultPixel, pixelData, m_pixelSize) == 0; } private: int m_pixelSize; const quint8 *m_defaultPixel; }; template QRect calculateExactBoundsImpl(const KisPaintDevice *device, const QRect &startRect, const QRect &endRect, ComparePixelOp compareOp) { if (startRect == endRect) return startRect; // the passed extent might have weird invalid structure that // can overflow integer precision when calling startRect.right() if (!startRect.isValid()) return QRect(); // Solution n°2 int x, y, w, h; int boundLeft, boundTop, boundRight, boundBottom; int endDirN, endDirE, endDirS, endDirW; startRect.getRect(&x, &y, &w, &h); if (endRect.isEmpty()) { endDirS = startRect.bottom(); endDirN = startRect.top(); endDirE = startRect.right(); endDirW = startRect.left(); startRect.getCoords(&boundLeft, &boundTop, &boundRight, &boundBottom); } else { endDirS = endRect.top() - 1; endDirN = endRect.bottom() + 1; endDirE = endRect.left() - 1; endDirW = endRect.right() + 1; endRect.getCoords(&boundLeft, &boundTop, &boundRight, &boundBottom); } // XXX: a small optimization is possible by using H/V line iterators in the first // and third cases, at the cost of making the code a bit more complex KisRandomConstAccessorSP accessor = device->createRandomConstAccessorNG(x, y); bool found = false; { for (qint32 y2 = y; y2 <= endDirS; ++y2) { for (qint32 x2 = x; x2 < x + w || found; ++ x2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundTop = y2; found = true; break; } } if (found) break; } } /** * If the first pass hasn't found any opaque pixel, there is no * reason to check that 3 more times. They will not appear in the * meantime. Just return an empty bounding rect. */ if (!found && endRect.isEmpty()) { return QRect(); } found = false; for (qint32 y2 = y + h - 1; y2 >= endDirN ; --y2) { for (qint32 x2 = x + w - 1; x2 >= x || found; --x2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundBottom = y2; found = true; break; } } if (found) break; } found = false; { for (qint32 x2 = x; x2 <= endDirE ; ++x2) { for (qint32 y2 = y; y2 < y + h || found; ++y2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundLeft = x2; found = true; break; } } if (found) break; } } found = false; // Look for right edge ) { for (qint32 x2 = x + w - 1; x2 >= endDirW; --x2) { for (qint32 y2 = y + h - 1; y2 >= y || found; --y2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundRight = x2; found = true; break; } } if (found) break; } } return QRect(boundLeft, boundTop, boundRight - boundLeft + 1, boundBottom - boundTop + 1); } } QRect KisPaintDevice::calculateExactBounds(bool nonDefaultOnly) const { QRect startRect = extent(); QRect endRect; quint8 defaultOpacity = defaultPixel().opacityU8(); if (defaultOpacity != OPACITY_TRANSPARENT_U8) { if (!nonDefaultOnly) { /** * We will calculate exact bounds only outside of the * image bounds, and that'll be nondefault area only. */ endRect = defaultBounds()->bounds(); nonDefaultOnly = true; } else { startRect = region().boundingRect(); } } if (nonDefaultOnly) { const KoColor defaultPixel = this->defaultPixel(); Impl::CheckNonDefault compareOp(pixelSize(), defaultPixel.data()); endRect = Impl::calculateExactBoundsImpl(this, startRect, endRect, compareOp); } else { Impl::CheckFullyTransparent compareOp(m_d->colorSpace()); endRect = Impl::calculateExactBoundsImpl(this, startRect, endRect, compareOp); } return endRect; } QRegion KisPaintDevice::regionExact() const { QRegion resultRegion; QVector rects = region().rects(); const KoColor defaultPixel = this->defaultPixel(); Impl::CheckNonDefault compareOp(pixelSize(), defaultPixel.data()); Q_FOREACH (const QRect &rc1, rects) { const int patchSize = 64; QVector smallerRects = KritaUtils::splitRectIntoPatches(rc1, QSize(patchSize, patchSize)); Q_FOREACH (const QRect &rc2, smallerRects) { const QRect result = Impl::calculateExactBoundsImpl(this, rc2, QRect(), compareOp); if (!result.isEmpty()) { resultRegion += result; } } } return resultRegion; } void KisPaintDevice::crop(qint32 x, qint32 y, qint32 w, qint32 h) { crop(QRect(x, y, w, h)); } void KisPaintDevice::crop(const QRect &rect) { m_d->currentStrategy()->crop(rect); } void KisPaintDevice::purgeDefaultPixels() { KisDataManagerSP dm = m_d->dataManager(); dm->purge(dm->extent()); } void KisPaintDevice::setDefaultPixel(const KoColor &defPixel) { KoColor color(defPixel); color.convertTo(colorSpace()); m_d->dataManager()->setDefaultPixel(color.data()); m_d->cache()->invalidate(); } KoColor KisPaintDevice::defaultPixel() const { return KoColor(m_d->dataManager()->defaultPixel(), colorSpace()); } void KisPaintDevice::clear() { m_d->dataManager()->clear(); m_d->cache()->invalidate(); } void KisPaintDevice::clear(const QRect & rc) { m_d->currentStrategy()->clear(rc); } void KisPaintDevice::fill(const QRect & rc, const KoColor &color) { KIS_ASSERT_RECOVER_RETURN(*color.colorSpace() == *colorSpace()); m_d->currentStrategy()->fill(rc, color.data()); } void KisPaintDevice::fill(qint32 x, qint32 y, qint32 w, qint32 h, const quint8 *fillPixel) { m_d->currentStrategy()->fill(QRect(x, y, w, h), fillPixel); } bool KisPaintDevice::write(KisPaintDeviceWriter &store) { return m_d->dataManager()->write(store); } bool KisPaintDevice::read(QIODevice *stream) { bool retval; retval = m_d->dataManager()->read(stream); m_d->cache()->invalidate(); return retval; } void KisPaintDevice::emitColorSpaceChanged() { emit colorSpaceChanged(m_d->colorSpace()); } void KisPaintDevice::emitProfileChanged() { emit profileChanged(m_d->colorSpace()->profile()); } void KisPaintDevice::convertTo(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, KUndo2Command *parentCommand) { m_d->convertColorSpace(dstColorSpace, renderingIntent, conversionFlags, parentCommand); } -bool KisPaintDevice::setProfile(const KoColorProfile * profile) +bool KisPaintDevice::setProfile(const KoColorProfile * profile, KUndo2Command *parentCommand) { - return m_d->assignProfile(profile); + return m_d->assignProfile(profile, parentCommand); } KisDataManagerSP KisPaintDevice::dataManager() const { return m_d->dataManager(); } void KisPaintDevice::convertFromQImage(const QImage& _image, const KoColorProfile *profile, qint32 offsetX, qint32 offsetY) { QImage image = _image; if (image.format() != QImage::Format_ARGB32) { image = image.convertToFormat(QImage::Format_ARGB32); } // Don't convert if not no profile is given and both paint dev and qimage are rgba. if (!profile && colorSpace()->id() == "RGBA") { writeBytes(image.constBits(), offsetX, offsetY, image.width(), image.height()); } else { try { quint8 * dstData = new quint8[image.width() * image.height() * pixelSize()]; KoColorSpaceRegistry::instance() ->colorSpace(RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), profile) ->convertPixelsTo(image.constBits(), dstData, colorSpace(), image.width() * image.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); writeBytes(dstData, offsetX, offsetY, image.width(), image.height()); delete[] dstData; } catch (const std::bad_alloc&) { warnKrita << "KisPaintDevice::convertFromQImage: Could not allocate" << image.width() * image.height() * pixelSize() << "bytes"; return; } } m_d->cache()->invalidate(); } QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { qint32 x1; qint32 y1; qint32 w; qint32 h; QRect rc = exactBounds(); x1 = rc.x(); y1 = rc.y(); w = rc.width(); h = rc.height(); return convertToQImage(dstProfile, x1, y1, w, h, renderingIntent, conversionFlags); } QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, const QRect &rc, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { return convertToQImage(dstProfile, rc.x(), rc.y(), rc.width(), rc.height(), renderingIntent, conversionFlags); } QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, qint32 x1, qint32 y1, qint32 w, qint32 h, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { if (w < 0) return QImage(); if (h < 0) return QImage(); quint8 *data = 0; try { data = new quint8 [w * h * pixelSize()]; } catch (const std::bad_alloc&) { warnKrita << "KisPaintDevice::convertToQImage std::bad_alloc for " << w << " * " << h << " * " << pixelSize(); //delete[] data; // data is not allocated, so don't free it return QImage(); } Q_CHECK_PTR(data); // XXX: Is this really faster than converting line by line and building the QImage directly? // This copies potentially a lot of data. readBytes(data, x1, y1, w, h); QImage image = colorSpace()->convertToQImage(data, w, h, dstProfile, renderingIntent, conversionFlags); delete[] data; return image; } inline bool moveBy(KisSequentialConstIterator& iter, int numPixels) { int pos = 0; while (pos < numPixels) { int step = std::min(iter.nConseqPixels(), numPixels - pos); if (!iter.nextPixels(step)) return false; pos += step; } return true; } static KisPaintDeviceSP createThumbnailDeviceInternal(const KisPaintDevice* srcDev, qint32 srcX0, qint32 srcY0, qint32 srcWidth, qint32 srcHeight, qint32 w, qint32 h, QRect outputRect) { KisPaintDeviceSP thumbnail = new KisPaintDevice(srcDev->colorSpace()); qint32 pixelSize = srcDev->pixelSize(); KisRandomConstAccessorSP srcIter = srcDev->createRandomConstAccessorNG(0, 0); KisRandomAccessorSP dstIter = thumbnail->createRandomAccessorNG(0, 0); for (qint32 y = outputRect.y(); y < outputRect.y() + outputRect.height(); ++y) { qint32 iY = srcY0 + (y * srcHeight) / h; for (qint32 x = outputRect.x(); x < outputRect.x() + outputRect.width(); ++x) { qint32 iX = srcX0 + (x * srcWidth) / w; srcIter->moveTo(iX, iY); dstIter->moveTo(x, y); memcpy(dstIter->rawData(), srcIter->rawDataConst(), pixelSize); } } return thumbnail; } QSize fixThumbnailSize(QSize size) { if (!size.width() && size.height()) { size.setWidth(1); } if (size.width() && !size.height()) { size.setHeight(1); } return size; } KisPaintDeviceSP KisPaintDevice::createThumbnailDevice(qint32 w, qint32 h, QRect rect, QRect outputRect) const { QSize thumbnailSize(w, h); QRect imageRect = rect.isValid() ? rect : extent(); if ((thumbnailSize.width() > imageRect.width()) || (thumbnailSize.height() > imageRect.height())) { thumbnailSize.scale(imageRect.size(), Qt::KeepAspectRatio); } thumbnailSize = fixThumbnailSize(thumbnailSize); //can't create thumbnail for an empty device, e.g. layer thumbnail for empty image if (imageRect.isEmpty() || thumbnailSize.isEmpty()) { return new KisPaintDevice(colorSpace()); } int srcWidth, srcHeight; int srcX0, srcY0; imageRect.getRect(&srcX0, &srcY0, &srcWidth, &srcHeight); if (!outputRect.isValid()) { outputRect = QRect(0, 0, w, h); } KisPaintDeviceSP thumbnail = createThumbnailDeviceInternal(this, imageRect.x(), imageRect.y(), imageRect.width(), imageRect.height(), thumbnailSize.width(), thumbnailSize.height(), outputRect); return thumbnail; } KisPaintDeviceSP KisPaintDevice::createThumbnailDeviceOversampled(qint32 w, qint32 h, qreal oversample, QRect rect, QRect outputTileRect) const { QSize thumbnailSize(w, h); qreal oversampleAdjusted = qMax(oversample, 1.); QSize thumbnailOversampledSize = oversampleAdjusted * thumbnailSize; QRect outputRect; QRect imageRect = rect.isValid() ? rect : extent(); qint32 hstart = thumbnailOversampledSize.height(); if ((thumbnailOversampledSize.width() > imageRect.width()) || (thumbnailOversampledSize.height() > imageRect.height())) { thumbnailOversampledSize.scale(imageRect.size(), Qt::KeepAspectRatio); } thumbnailOversampledSize = fixThumbnailSize(thumbnailOversampledSize); //can't create thumbnail for an empty device, e.g. layer thumbnail for empty image if (imageRect.isEmpty() || thumbnailSize.isEmpty() || thumbnailOversampledSize.isEmpty()) { return new KisPaintDevice(colorSpace()); } oversampleAdjusted *= (hstart > 0) ? ((qreal)thumbnailOversampledSize.height() / hstart) : 1.; //readjusting oversample ratio, given that we had to adjust thumbnail size outputRect = QRect(0, 0, thumbnailOversampledSize.width(), thumbnailOversampledSize.height()); if (outputTileRect.isValid()) { //compensating output rectangle for oversampling outputTileRect = QRect(oversampleAdjusted * outputTileRect.topLeft(), oversampleAdjusted * outputTileRect.bottomRight()); outputRect = outputRect.intersected(outputTileRect); } KisPaintDeviceSP thumbnail = createThumbnailDeviceInternal(this, imageRect.x(), imageRect.y(), imageRect.width(), imageRect.height(), thumbnailOversampledSize.width(), thumbnailOversampledSize.height(), outputRect); if (oversample != 1. && oversampleAdjusted != 1.) { KoDummyUpdater updater; KisTransformWorker worker(thumbnail, 1 / oversampleAdjusted, 1 / oversampleAdjusted, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, &updater, KisFilterStrategyRegistry::instance()->value("Bilinear")); worker.run(); } return thumbnail; } QImage KisPaintDevice::createThumbnail(qint32 w, qint32 h, QRect rect, qreal oversample, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { QSize size = fixThumbnailSize(QSize(w, h)); KisPaintDeviceSP dev = createThumbnailDeviceOversampled(size.width(), size.height(), oversample, rect); QImage thumbnail = dev->convertToQImage(KoColorSpaceRegistry::instance()->rgb8()->profile(), 0, 0, w, h, renderingIntent, conversionFlags); return thumbnail; } QImage KisPaintDevice::createThumbnail(qint32 w, qint32 h, qreal oversample, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { QSize size = fixThumbnailSize(QSize(w, h)); return m_d->cache()->createThumbnail(size.width(), size.height(), oversample, renderingIntent, conversionFlags); } KisHLineIteratorSP KisPaintDevice::createHLineIteratorNG(qint32 x, qint32 y, qint32 w) { m_d->cache()->invalidate(); return m_d->currentStrategy()->createHLineIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y()); } KisHLineConstIteratorSP KisPaintDevice::createHLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const { return m_d->currentStrategy()->createHLineConstIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y()); } KisVLineIteratorSP KisPaintDevice::createVLineIteratorNG(qint32 x, qint32 y, qint32 w) { m_d->cache()->invalidate(); return m_d->currentStrategy()->createVLineIteratorNG(x, y, w); } KisVLineConstIteratorSP KisPaintDevice::createVLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const { return m_d->currentStrategy()->createVLineConstIteratorNG(x, y, w); } KisRepeatHLineConstIteratorSP KisPaintDevice::createRepeatHLineConstIterator(qint32 x, qint32 y, qint32 w, const QRect& _dataWidth) const { return new KisRepeatHLineConstIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y(), _dataWidth, m_d->cacheInvalidator()); } KisRepeatVLineConstIteratorSP KisPaintDevice::createRepeatVLineConstIterator(qint32 x, qint32 y, qint32 h, const QRect& _dataWidth) const { return new KisRepeatVLineConstIteratorNG(m_d->dataManager().data(), x, y, h, m_d->x(), m_d->y(), _dataWidth, m_d->cacheInvalidator()); } KisRandomAccessorSP KisPaintDevice::createRandomAccessorNG(qint32 x, qint32 y) { m_d->cache()->invalidate(); return m_d->currentStrategy()->createRandomAccessorNG(x, y); } KisRandomConstAccessorSP KisPaintDevice::createRandomConstAccessorNG(qint32 x, qint32 y) const { return m_d->currentStrategy()->createRandomConstAccessorNG(x, y); } KisRandomSubAccessorSP KisPaintDevice::createRandomSubAccessor() const { KisPaintDevice* pd = const_cast(this); return new KisRandomSubAccessor(pd); } void KisPaintDevice::clearSelection(KisSelectionSP selection) { const KoColorSpace *colorSpace = m_d->colorSpace(); const QRect r = selection->selectedExactRect(); if (r.isValid()) { { KisHLineIteratorSP devIt = createHLineIteratorNG(r.x(), r.y(), r.width()); KisHLineConstIteratorSP selectionIt = selection->projection()->createHLineConstIteratorNG(r.x(), r.y(), r.width()); const KoColor defaultPixel = this->defaultPixel(); bool transparentDefault = (defaultPixel.opacityU8() == OPACITY_TRANSPARENT_U8); for (qint32 y = 0; y < r.height(); y++) { do { // XXX: Optimize by using stretches colorSpace->applyInverseAlphaU8Mask(devIt->rawData(), selectionIt->rawDataConst(), 1); if (transparentDefault && colorSpace->opacityU8(devIt->rawData()) == OPACITY_TRANSPARENT_U8) { memcpy(devIt->rawData(), defaultPixel.data(), colorSpace->pixelSize()); } } while (devIt->nextPixel() && selectionIt->nextPixel()); devIt->nextRow(); selectionIt->nextRow(); } } // purge() must be executed **after** all iterators have been destroyed! m_d->dataManager()->purge(r.translated(-m_d->x(), -m_d->y())); setDirty(r); } } bool KisPaintDevice::pixel(qint32 x, qint32 y, QColor *c) const { KisHLineConstIteratorSP iter = createHLineConstIteratorNG(x, y, 1); const quint8 *pix = iter->rawDataConst(); if (!pix) return false; colorSpace()->toQColor(pix, c); return true; } bool KisPaintDevice::pixel(qint32 x, qint32 y, KoColor * kc) const { KisHLineConstIteratorSP iter = createHLineConstIteratorNG(x, y, 1); const quint8 *pix = iter->rawDataConst(); if (!pix) return false; kc->setColor(pix, m_d->colorSpace()); return true; } bool KisPaintDevice::setPixel(qint32 x, qint32 y, const QColor& c) { KisHLineIteratorSP iter = createHLineIteratorNG(x, y, 1); colorSpace()->fromQColor(c, iter->rawData()); m_d->cache()->invalidate(); return true; } bool KisPaintDevice::setPixel(qint32 x, qint32 y, const KoColor& kc) { const quint8 * pix; KisHLineIteratorSP iter = createHLineIteratorNG(x, y, 1); if (kc.colorSpace() != m_d->colorSpace()) { KoColor kc2(kc, m_d->colorSpace()); pix = kc2.data(); memcpy(iter->rawData(), pix, m_d->colorSpace()->pixelSize()); } else { pix = kc.data(); memcpy(iter->rawData(), pix, m_d->colorSpace()->pixelSize()); } m_d->cache()->invalidate(); return true; } bool KisPaintDevice::fastBitBltPossible(KisPaintDeviceSP src) { return m_d->fastBitBltPossible(src); } void KisPaintDevice::fastBitBlt(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBlt(src, rect); } void KisPaintDevice::fastBitBltOldData(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBltOldData(src, rect); } void KisPaintDevice::fastBitBltRough(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBltRough(src, rect); } void KisPaintDevice::fastBitBltRoughOldData(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBltRoughOldData(src, rect); } void KisPaintDevice::readBytes(quint8 * data, qint32 x, qint32 y, qint32 w, qint32 h) const { readBytes(data, QRect(x, y, w, h)); } void KisPaintDevice::readBytes(quint8 *data, const QRect &rect) const { m_d->currentStrategy()->readBytes(data, rect); } void KisPaintDevice::writeBytes(const quint8 *data, qint32 x, qint32 y, qint32 w, qint32 h) { writeBytes(data, QRect(x, y, w, h)); } void KisPaintDevice::writeBytes(const quint8 *data, const QRect &rect) { m_d->currentStrategy()->writeBytes(data, rect); } QVector KisPaintDevice::readPlanarBytes(qint32 x, qint32 y, qint32 w, qint32 h) const { return m_d->currentStrategy()->readPlanarBytes(x, y, w, h); } void KisPaintDevice::writePlanarBytes(QVector planes, qint32 x, qint32 y, qint32 w, qint32 h) { m_d->currentStrategy()->writePlanarBytes(planes, x, y, w, h); } quint32 KisPaintDevice::pixelSize() const { quint32 _pixelSize = m_d->colorSpace()->pixelSize(); Q_ASSERT(_pixelSize > 0); return _pixelSize; } quint32 KisPaintDevice::channelCount() const { quint32 _channelCount = m_d->colorSpace()->channelCount(); Q_ASSERT(_channelCount > 0); return _channelCount; } KisRasterKeyframeChannel *KisPaintDevice::createKeyframeChannel(const KoID &id) { Q_ASSERT(!m_d->framesInterface); m_d->framesInterface.reset(new KisPaintDeviceFramesInterface(this)); Q_ASSERT(!m_d->contentChannel); m_d->contentChannel.reset(new KisRasterKeyframeChannel(id, this, m_d->defaultBounds)); // Raster channels always have at least one frame (representing a static image) KUndo2Command tempParentCommand; m_d->contentChannel->addKeyframe(0, &tempParentCommand); return m_d->contentChannel.data(); } KisRasterKeyframeChannel* KisPaintDevice::keyframeChannel() const { if (m_d->contentChannel) { return m_d->contentChannel.data(); } return 0; } const KoColorSpace* KisPaintDevice::colorSpace() const { Q_ASSERT(m_d->colorSpace() != 0); return m_d->colorSpace(); } KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice() const { KisPaintDeviceSP device = new KisPaintDevice(compositionSourceColorSpace()); device->setDefaultBounds(defaultBounds()); return device; } KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice(KisPaintDeviceSP cloneSource) const { KisPaintDeviceSP clone = new KisPaintDevice(*cloneSource); clone->setDefaultBounds(defaultBounds()); clone->convertTo(compositionSourceColorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return clone; } KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice(KisPaintDeviceSP cloneSource, const QRect roughRect) const { KisPaintDeviceSP clone = new KisPaintDevice(colorSpace()); clone->setDefaultBounds(defaultBounds()); clone->makeCloneFromRough(cloneSource, roughRect); clone->convertTo(compositionSourceColorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return clone; } KisFixedPaintDeviceSP KisPaintDevice::createCompositionSourceDeviceFixed() const { return new KisFixedPaintDevice(compositionSourceColorSpace()); } const KoColorSpace* KisPaintDevice::compositionSourceColorSpace() const { return colorSpace(); } QVector KisPaintDevice::channelSizes() const { QVector sizes; QList channels = colorSpace()->channels(); std::sort(channels.begin(), channels.end()); Q_FOREACH (KoChannelInfo * channelInfo, channels) { sizes.append(channelInfo->size()); } return sizes; } KisPaintDevice::MemoryReleaseObject::~MemoryReleaseObject() { KisDataManager::releaseInternalPools(); } KisPaintDevice::MemoryReleaseObject* KisPaintDevice::createMemoryReleaseObject() { return new MemoryReleaseObject(); } KisPaintDevice::LodDataStruct::~LodDataStruct() { } QRegion KisPaintDevice::regionForLodSyncing() const { return m_d->regionForLodSyncing(); } KisPaintDevice::LodDataStruct* KisPaintDevice::createLodDataStruct(int lod) { return m_d->createLodDataStruct(lod); } void KisPaintDevice::updateLodDataStruct(LodDataStruct *dst, const QRect &srcRect) { m_d->updateLodDataStruct(dst, srcRect); } void KisPaintDevice::uploadLodDataStruct(LodDataStruct *dst) { m_d->uploadLodDataStruct(dst); } void KisPaintDevice::generateLodCloneDevice(KisPaintDeviceSP dst, const QRect &originalRect, int lod) { m_d->generateLodCloneDevice(dst, originalRect, lod); } KisPaintDeviceFramesInterface* KisPaintDevice::framesInterface() { return m_d->framesInterface.data(); } /******************************************************************/ /* KisPaintDeviceFramesInterface */ /******************************************************************/ KisPaintDeviceFramesInterface::KisPaintDeviceFramesInterface(KisPaintDevice *parentDevice) : q(parentDevice) { } QList KisPaintDeviceFramesInterface::frames() { return q->m_d->frameIds(); } int KisPaintDeviceFramesInterface::createFrame(bool copy, int copySrc, const QPoint &offset, KUndo2Command *parentCommand) { return q->m_d->createFrame(copy, copySrc, offset, parentCommand); } void KisPaintDeviceFramesInterface::deleteFrame(int frame, KUndo2Command *parentCommand) { return q->m_d->deleteFrame(frame, parentCommand); } void KisPaintDeviceFramesInterface::fetchFrame(int frameId, KisPaintDeviceSP targetDevice) { q->m_d->fetchFrame(frameId, targetDevice); } void KisPaintDeviceFramesInterface::uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice) { q->m_d->uploadFrame(srcFrameId, dstFrameId, srcDevice); } void KisPaintDeviceFramesInterface::uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice) { q->m_d->uploadFrame(dstFrameId, srcDevice); } QRect KisPaintDeviceFramesInterface::frameBounds(int frameId) { return q->m_d->frameBounds(frameId); } QPoint KisPaintDeviceFramesInterface::frameOffset(int frameId) const { return q->m_d->frameOffset(frameId); } void KisPaintDeviceFramesInterface::setFrameDefaultPixel(const KoColor &defPixel, int frameId) { KIS_ASSERT_RECOVER_RETURN(frameId >= 0); q->m_d->setFrameDefaultPixel(defPixel, frameId); } KoColor KisPaintDeviceFramesInterface::frameDefaultPixel(int frameId) const { KIS_ASSERT_RECOVER(frameId >= 0) { return KoColor(Qt::red, q->m_d->colorSpace()); } return q->m_d->frameDefaultPixel(frameId); } bool KisPaintDeviceFramesInterface::writeFrame(KisPaintDeviceWriter &store, int frameId) { KIS_ASSERT_RECOVER(frameId >= 0) { return false; } return q->m_d->writeFrame(store, frameId); } bool KisPaintDeviceFramesInterface::readFrame(QIODevice *stream, int frameId) { KIS_ASSERT_RECOVER(frameId >= 0) { return false; } return q->m_d->readFrame(stream, frameId); } int KisPaintDeviceFramesInterface::currentFrameId() const { return q->m_d->currentFrameId(); } KisDataManagerSP KisPaintDeviceFramesInterface::frameDataManager(int frameId) const { KIS_ASSERT_RECOVER(frameId >= 0) { return q->m_d->dataManager(); } return q->m_d->frameDataManager(frameId); } void KisPaintDeviceFramesInterface::invalidateFrameCache(int frameId) { KIS_ASSERT_RECOVER_RETURN(frameId >= 0); return q->m_d->invalidateFrameCache(frameId); } void KisPaintDeviceFramesInterface::setFrameOffset(int frameId, const QPoint &offset) { KIS_ASSERT_RECOVER_RETURN(frameId >= 0); return q->m_d->setFrameOffset(frameId, offset); } KisPaintDeviceFramesInterface::TestingDataObjects KisPaintDeviceFramesInterface::testingGetDataObjects() const { TestingDataObjects objects; objects.m_data = q->m_d->m_data.data(); objects.m_lodData = q->m_d->m_lodData.data(); objects.m_externalFrameData = q->m_d->m_externalFrameData.data(); typedef KisPaintDevice::Private::FramesHash FramesHash; FramesHash::const_iterator it = q->m_d->m_frames.constBegin(); FramesHash::const_iterator end = q->m_d->m_frames.constEnd(); for (; it != end; ++it) { objects.m_frames.insert(it.key(), it.value().data()); } objects.m_currentData = q->m_d->currentData(); return objects; } QList KisPaintDeviceFramesInterface::testingGetDataObjectsList() const { return q->m_d->allDataObjects(); } void KisPaintDevice::tesingFetchLodDevice(KisPaintDeviceSP targetDevice) { m_d->tesingFetchLodDevice(targetDevice); } diff --git a/libs/image/kis_paint_device.h b/libs/image/kis_paint_device.h index ab325b6158..e89242b5b8 100644 --- a/libs/image/kis_paint_device.h +++ b/libs/image/kis_paint_device.h @@ -1,895 +1,895 @@ /* * Copyright (c) 2002 patrick julien * Copyright (c) 2006 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_PAINT_DEVICE_IMPL_H_ #define KIS_PAINT_DEVICE_IMPL_H_ #include #include #include #include "kis_debug.h" #include #include "kis_types.h" #include "kis_shared.h" #include "kis_default_bounds_base.h" #include class KUndo2Command; class QRect; class QImage; class QPoint; class QString; class QColor; class QIODevice; class KoColor; class KoColorSpace; class KoColorProfile; class KisDataManager; class KisPaintDeviceWriter; class KisKeyframe; class KisRasterKeyframeChannel; class KisPaintDeviceFramesInterface; typedef KisSharedPtr KisDataManagerSP; namespace KritaUtils { enum DeviceCopyMode { CopySnapshot = 0, CopyAllFrames }; } /** * A paint device contains the actual pixel data and offers methods * to read and write pixels. A paint device has an integer x, y position * (it is not positioned on the image with sub-pixel accuracy). * A KisPaintDevice doesn't have any fixed size, the size changes dynamically * when pixels are accessed by an iterator. */ class KRITAIMAGE_EXPORT KisPaintDevice : public QObject , public KisShared { Q_OBJECT public: /** * Create a new paint device with the specified colorspace. * * @param colorSpace the colorspace of this paint device * @param name for debugging purposes */ explicit KisPaintDevice(const KoColorSpace * colorSpace, const QString& name = QString()); /** * Create a new paint device with the specified colorspace. The * parent node will be notified of changes to this paint device. * * @param parent the node that contains this paint device * @param colorSpace the colorspace of this paint device * @param defaultBounds boundaries of the device in case it is empty * @param name for debugging purposes */ KisPaintDevice(KisNodeWSP parent, const KoColorSpace * colorSpace, KisDefaultBoundsBaseSP defaultBounds = KisDefaultBoundsBaseSP(), const QString& name = QString()); /** * Creates a copy of this device. * * If \p copyMode is CopySnapshot, the newly created device clones the * current frame of \p rhs only (default and efficient * behavior). If \p copyFrames is CopyAllFrames, the new device is a deep * copy of the source with all the frames included. */ KisPaintDevice(const KisPaintDevice& rhs, KritaUtils::DeviceCopyMode copyMode = KritaUtils::CopySnapshot, KisNode *newParentNode = 0); ~KisPaintDevice() override; void makeFullCopyFrom(const KisPaintDevice& rhs, KritaUtils::DeviceCopyMode copyMode = KritaUtils::CopySnapshot, KisNode *newParentNode = 0); protected: /** * A special constructor for usage in KisPixelSelection. It allows * two paint devices to share a data manager. * * @param explicitDataManager data manager to use inside paint device * @param src source paint device to copy parameters from * @param name for debugging purposes */ KisPaintDevice(KisDataManagerSP explicitDataManager, KisPaintDeviceSP src, const QString& name = QString()); public: /** * Write the pixels of this paint device into the specified file store. */ bool write(KisPaintDeviceWriter &store); /** * Fill this paint device with the pixels from the specified file store. */ bool read(QIODevice *stream); public: /** * set the parent node of the paint device */ void setParentNode(KisNodeWSP parent); /** * set the default bounds for the paint device when * the default pixel is not completely transparent */ void setDefaultBounds(KisDefaultBoundsBaseSP bounds); /** * the default bounds rect of the paint device */ KisDefaultBoundsBaseSP defaultBounds() const; /** * Moves the device to these new coordinates (no incremental move) */ void moveTo(qint32 x, qint32 y); /** * Convenience method for the above. */ virtual void moveTo(const QPoint& pt); /** * Return an X,Y offset of the device in a convenient form */ QPoint offset() const; /** * The X offset of the paint device */ qint32 x() const; /** * The Y offset of the paint device */ qint32 y() const; /** * set the X offset of the paint device */ void setX(qint32 x); /** * set the Y offset of the paint device */ void setY(qint32 y); /** * Retrieve the bounds of the paint device. The size is not exact, * but may be larger if the underlying datamanager works that way. * For instance, the tiled datamanager keeps the extent to the nearest * multiple of 64. * * If default pixel is not transparent, then the actual extent * rect is united with the defaultBounds()->bounds() value * (the size of the image, usually). */ QRect extent() const; /// Convenience method for the above void extent(qint32 &x, qint32 &y, qint32 &w, qint32 &h) const; /** * Get the exact bounds of this paint device. The real solution is * very slow because it does a linear scanline search, but it * uses caching, so calling to this function without changing * the device is quite cheap. * * Exactbounds follows these rules: * *
    *
  • if default pixel is transparent, then exact bounds * of actual pixel data are returned *
  • if default pixel is not transparent, then the union * (defaultBounds()->bounds() | nonDefaultPixelArea()) is * returned *
* \see calculateExactBounds() */ QRect exactBounds() const; /** * Relaxed version of the exactBounds() that can be used in tight * loops. If the exact bounds value is present in the paint * device cache, returns this value. If the cache is invalidated, * returns extent() and tries to recalculate the exact bounds not * faster than once in 1000 ms. */ QRect exactBoundsAmortized() const; /** * Returns exact rectangle of the paint device that contains * non-default pixels. For paint devices with fully transparent * default pixel is equivalent to exactBounds(). * * nonDefaultPixelArea() follows these rules: * *
    *
  • if default pixel is transparent, then exact bounds * of actual pixel data are returned. The same as exactBounds() *
  • if default pixel is not transparent, then calculates the * rectangle of non-default pixels. May be smaller or greater * than image bounds *
* \see calculateExactBounds() */ QRect nonDefaultPixelArea() const; /** * Returns a rough approximation of region covered by device. * For tiled data manager, it region will consist of a number * of rects each corresponding to a tile. */ QRegion region() const; /** * The slow version of region() that searches for exact bounds of * each rectangle in the region */ QRegion regionExact() const; /** * Cut the paint device down to the specified rect. If the crop * area is bigger than the paint device, nothing will happen. */ void crop(qint32 x, qint32 y, qint32 w, qint32 h); /// Convenience method for the above void crop(const QRect & r); /** * Complete erase the current paint device. Its size will become 0. This * does not take the selection into account. */ virtual void clear(); /** * Clear the given rectangle to transparent black. The paint device will expand to * contain the given rect. */ void clear(const QRect & rc); /** * Frees the memory occupied by the pixels containing default * values. The extents() and exactBounds() of the image will * probably also shrink */ void purgeDefaultPixels(); /** * Sets the default pixel. New data will be initialised with this pixel. The pixel is copied: the * caller still owns the pointer and needs to delete it to avoid memory leaks. * If frame ID is given, set default pixel for that frame. Otherwise use active frame. */ void setDefaultPixel(const KoColor &defPixel); /** * Get a pointer to the default pixel. * If the frame parameter is given, get the default pixel of * specified frame. Otherwise use currently active frame. */ KoColor defaultPixel() const; /** * Fill the given rectangle with the given pixel. The paint device will expand to * contain the given rect. */ void fill(const QRect & rc, const KoColor &color); /** * Overloaded function. For legacy purposes only. * Please use fill(const QRect & rc, const KoColor &color) instead */ void fill(qint32 x, qint32 y, qint32 w, qint32 h, const quint8 *fillPixel); public: /** * Prepares the device for fastBitBlt operation. It clears * the device, switches x,y shifts and colorspace if needed. * After this call fastBitBltPossible will return true. * May be used for initialization of temporary devices. */ void prepareClone(KisPaintDeviceSP src); /** * Make this device to become a clone of \a src. It will have the same * x,y shifts, colorspace and will share pixels inside \a rect. * After calling this function: * (this->extent() >= this->exactBounds() == rect). * * Rule of thumb: * * "Use makeCloneFrom() or makeCloneFromRough() if and only if you * are the only owner of the destination paint device and you are * 100% sure no other thread has access to it" */ void makeCloneFrom(KisPaintDeviceSP src, const QRect &rect); /** * Make this device to become a clone of \a src. It will have the same * x,y shifts, colorspace and will share pixels inside \a rect. * Be careful, this function will copy *at least* \a rect * of pixels. Actual copy area will be a bigger - it will * be aligned by tiles borders. So after calling this function: * (this->extent() == this->exactBounds() >= rect). * * Rule of thumb: * * "Use makeCloneFrom() or makeCloneFromRough() if and only if you * are the only owner of the destination paint device and you are * 100% sure no other thread has access to it" */ void makeCloneFromRough(KisPaintDeviceSP src, const QRect &minimalRect); protected: friend class KisPaintDeviceTest; friend class DataReaderThread; /** * Checks whether a src paint device can be used as source * of fast bitBlt operation. The result of the check may * depend on whether color spaces coincide, whether there is * any shift of tiles between the devices and etc. * * WARNING: This check must be done before performing any * fast bitBlt operation! * * \see fastBitBlt * \see fastBitBltRough */ bool fastBitBltPossible(KisPaintDeviceSP src); /** * Clones rect from another paint device. The cloned area will be * shared between both paint devices as much as possible using * copy-on-write. Parts of the rect that cannot be shared * (cross tiles) are deep-copied, * * \see fastBitBltPossible * \see fastBitBltRough */ void fastBitBlt(KisPaintDeviceSP src, const QRect &rect); /** * The same as \ref fastBitBlt() but reads old data */ void fastBitBltOldData(KisPaintDeviceSP src, const QRect &rect); /** * Clones rect from another paint device in a rough and fast way. * All the tiles touched by rect will be shared, between both * devices, that means it will copy a bigger area than was * requested. This method is supposed to be used for bitBlt'ing * into temporary paint devices. * * \see fastBitBltPossible * \see fastBitBlt */ void fastBitBltRough(KisPaintDeviceSP src, const QRect &rect); /** * The same as \ref fastBitBltRough() but reads old data */ void fastBitBltRoughOldData(KisPaintDeviceSP src, const QRect &rect); public: /** * Read the bytes representing the rectangle described by x, y, w, h into * data. If data is not big enough, Krita will gladly overwrite the rest * of your precious memory. * * Since this is a copy, you need to make sure you have enough memory. * * Reading from areas not previously initialized will read the default * pixel value into data but not initialize that region. */ void readBytes(quint8 * data, qint32 x, qint32 y, qint32 w, qint32 h) const; /** * Read the bytes representing the rectangle rect into * data. If data is not big enough, Krita will gladly overwrite the rest * of your precious memory. * * Since this is a copy, you need to make sure you have enough memory. * * Reading from areas not previously initialized will read the default * pixel value into data but not initialize that region. * @param data The address of the memory to receive the bytes read * @param rect The rectangle in the paint device to read from */ void readBytes(quint8 * data, const QRect &rect) const; /** * Copy the bytes in data into the rect specified by x, y, w, h. If the * data is too small or uninitialized, Krita will happily read parts of * memory you never wanted to be read. * * If the data is written to areas of the paint device not previously initialized, * the paint device will grow. */ void writeBytes(const quint8 * data, qint32 x, qint32 y, qint32 w, qint32 h); /** * Copy the bytes in data into the rectangle rect. If the * data is too small or uninitialized, Krita will happily read parts of * memory you never wanted to be read. * * If the data is written to areas of the paint device not previously initialized, * the paint device will grow. * @param data The address of the memory to write bytes from * @param rect The rectangle in the paint device to write to */ void writeBytes(const quint8 * data, const QRect &rect); /** * Copy the bytes in the paint device into a vector of arrays of bytes, * where the number of arrays is the number of channels in the * paint device. If the specified area is larger than the paint * device's extent, the default pixel will be read. */ QVector readPlanarBytes(qint32 x, qint32 y, qint32 w, qint32 h) const; /** * Write the data in the separate arrays to the channes. If there * are less vectors than channels, the remaining channels will not * be copied. If any of the arrays points to 0, the channel in * that location will not be touched. If the specified area is * larger than the paint device, the paint device will be * extended. There are no guards: if the area covers more pixels * than there are bytes in the arrays, krita will happily fill * your paint device with areas of memory you never wanted to be * read. Krita may also crash. * * XXX: what about undo? */ void writePlanarBytes(QVector planes, qint32 x, qint32 y, qint32 w, qint32 h); /** * Converts the paint device to a different colorspace */ void convertTo(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags(), KUndo2Command *parentCommand = 0); /** * Changes the profile of the colorspace of this paint device to the given * profile. If the given profile is 0, nothing happens. */ - bool setProfile(const KoColorProfile * profile); + bool setProfile(const KoColorProfile * profile, KUndo2Command *parentCommand); /** * Fill this paint device with the data from image; starting at (offsetX, offsetY) * @param image the image * @param profile name of the RGB profile to interpret the image as. 0 is interpreted as sRGB * @param offsetX x offset * @param offsetY y offset */ void convertFromQImage(const QImage& image, const KoColorProfile *profile, qint32 offsetX = 0, qint32 offsetY = 0); /** * Create an RGBA QImage from a rectangle in the paint device. * * @param dstProfile RGB profile to use in conversion. May be 0, in which * case it's up to the color strategy to choose a profile (most * like sRGB). * @param x Left coordinate of the rectangle * @param y Top coordinate of the rectangle * @param w Width of the rectangle in pixels * @param h Height of the rectangle in pixels * @param renderingIntent Rendering intent * @param conversionFlags Conversion flags */ QImage convertToQImage(const KoColorProfile *dstProfile, qint32 x, qint32 y, qint32 w, qint32 h, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) const; /** * Overridden method for convenience */ QImage convertToQImage(const KoColorProfile *dstProfile, const QRect &rc, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) const; /** * Create an RGBA QImage from a rectangle in the paint device. The * rectangle is defined by the parent image's bounds. * * @param dstProfile RGB profile to use in conversion. May be 0, in which * case it's up to the color strategy to choose a profile (most * like sRGB). * @param renderingIntent Rendering intent * @param conversionFlags Conversion flags */ QImage convertToQImage(const KoColorProfile * dstProfile, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) const; /** * Creates a paint device thumbnail of the paint device, retaining * the aspect ratio. The width and height of the returned device * won't exceed \p maxw and \p maxw, but they may be smaller. * * @param w maximum width * @param h maximum height * @param rect only this rect will be used for the thumbnail * @param outputRect output rectangle * */ KisPaintDeviceSP createThumbnailDevice(qint32 w, qint32 h, QRect rect = QRect(), QRect outputRect = QRect()) const; KisPaintDeviceSP createThumbnailDeviceOversampled(qint32 w, qint32 h, qreal oversample, QRect rect = QRect(), QRect outputRect = QRect()) const; /** * Creates a thumbnail of the paint device, retaining the aspect ratio. * The width and height of the returned QImage won't exceed \p maxw and \p maxw, but they may be smaller. * The colors are not corrected for display! * * @param maxw: maximum width * @param maxh: maximum height * @param rect: only this rect will be used for the thumbnail * @param oversample: ratio used for antialiasing * @param renderingIntent Rendering intent * @param conversionFlags Conversion flags */ QImage createThumbnail(qint32 maxw, qint32 maxh, QRect rect, qreal oversample = 1, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()); /** * Cached version of createThumbnail(qint32 maxw, qint32 maxh, const KisSelection *selection, QRect rect) */ QImage createThumbnail(qint32 maxw, qint32 maxh, qreal oversample = 1, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()); /** * Fill c and opacity with the values found at x and y. * * The color values will be transformed from the profile of * this paint device to the display profile. * * @return true if the operation was successful. */ bool pixel(qint32 x, qint32 y, QColor *c) const; /** * Fill kc with the values found at x and y. This method differs * from the above in using KoColor, which can be of any colorspace * * The color values will be transformed from the profile of * this paint device to the display profile. * * @return true if the operation was successful. */ bool pixel(qint32 x, qint32 y, KoColor * kc) const; /** * Set the specified pixel to the specified color. Note that this * bypasses KisPainter. the PaintDevice is here used as an equivalent * to QImage, not QPixmap. This means that this is not undoable; also, * there is no compositing with an existing value at this location. * * The color values will be transformed from the display profile to * the paint device profile. * * Note that this will use 8-bit values and may cause a significant * degradation when used on 16-bit or hdr quality images. * * @return true if the operation was successful */ bool setPixel(qint32 x, qint32 y, const QColor& c); /// Convenience method for the above bool setPixel(qint32 x, qint32 y, const KoColor& kc); /** * @return the colorspace of the pixels in this paint device */ const KoColorSpace* colorSpace() const; /** * There is quite a common technique in Krita. It is used in * cases, when we want to paint something over a paint device * using the composition, opacity or selection. E.g. painting a * dab in a paint op, filling the selection in the Fill Tool. * Such work is usually done in the following way: * * 1) Create a paint device * * 2) Fill it with the desired color or data * * 3) Create a KisPainter and set all the properties of the * transaction: selection, compositeOp, opacity and etc. * * 4) Paint a newly created paint device over the destination * device. * * The following two methods (createCompositionSourceDevice() or * createCompositionSourceDeviceFixed())should be used for the * accomplishing the step 1). The point is that the desired color * space of the temporary device may not coincide with the color * space of the destination. That is the case, for example, for * the alpha8() colorspace used in the selections. So for such * devices the temporary target would have a different (grayscale) * color space. * * So there are two rules of thumb: * * 1) If you need a temporary device which you are going to fill * with some data and then paint over the paint device, create * it with either createCompositionSourceDevice() or * createCompositionSourceDeviceFixed(). * * 2) Do *not* expect that the color spaces of the destination and * the temporary device would coincide. If you need to copy a * single pixel from one device to another, you can use * KisCrossDeviceColorPicker class, that will handle all the * necessary conversions for you. * * \see createCompositionSourceDeviceFixed() * \see compositionSourceColorSpace() * \see KisCrossDeviceColorPicker * \see KisCrossDeviceColorPickerInt */ KisPaintDeviceSP createCompositionSourceDevice() const; /** * The same as createCompositionSourceDevice(), but initializes the * newly created device with the content of \p cloneSource * * \see createCompositionSourceDevice() */ KisPaintDeviceSP createCompositionSourceDevice(KisPaintDeviceSP cloneSource) const; /** * The same as createCompositionSourceDevice(), but initializes * the newly created device with the *rough* \p roughRect of * \p cloneSource. * * "Rough rect" means that it may copy a bit more than * requested. It is expected that the caller will not use the area * outside \p roughRect. * * \see createCompositionSourceDevice() */ KisPaintDeviceSP createCompositionSourceDevice(KisPaintDeviceSP cloneSource, const QRect roughRect) const; /** * This is a convenience method for createCompositionSourceDevice() * * \see createCompositionSourceDevice() */ KisFixedPaintDeviceSP createCompositionSourceDeviceFixed() const; /** * This is a lowlevel method for the principle used in * createCompositionSourceDevice(). In most of the cases the paint * device creation methods should be used instead of this function. * * \see createCompositionSourceDevice() * \see createCompositionSourceDeviceFixed() */ virtual const KoColorSpace* compositionSourceColorSpace() const; /** * @return the internal datamanager that keeps the pixels. */ KisDataManagerSP dataManager() const; /** * Replace the pixel data, color strategy, and profile. */ void setDataManager(KisDataManagerSP data, const KoColorSpace * colorSpace = 0); /** * Return the number of bytes a pixel takes. */ quint32 pixelSize() const; /** * Return the number of channels a pixel takes */ quint32 channelCount() const; /** * Create a keyframe channel for the content on this device. * @param id identifier for the channel * @return keyframe channel or 0 if there is not one */ KisRasterKeyframeChannel *createKeyframeChannel(const KoID &id); KisRasterKeyframeChannel* keyframeChannel() const; /** * An interface to modify/load/save frames stored inside this device */ KisPaintDeviceFramesInterface* framesInterface(); public: /** * Add the specified rect to the parent layer's set of dirty rects * (if there is a parent layer) */ void setDirty(const QRect & rc); /** * Add the specified region to the parent layer's dirty region * (if there is a parent layer) */ void setDirty(const QRegion & region); /** * Set the parent layer completely dirty, if this paint device has * as parent layer. */ void setDirty(); void setDirty(const QVector rects); /** * Called by KisTransactionData when it thinks current time should * be changed. And the requests is forwarded to the image if * needed. */ void requestTimeSwitch(int time); /** * \return a sequence number corresponding to the current paint * device state. Every time the paint device is changed, * the sequence number is increased */ int sequenceNumber() const; void estimateMemoryStats(qint64 &imageData, qint64 &temporaryData, qint64 &lodData) const; public: KisHLineIteratorSP createHLineIteratorNG(qint32 x, qint32 y, qint32 w); KisHLineConstIteratorSP createHLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const; KisVLineIteratorSP createVLineIteratorNG(qint32 x, qint32 y, qint32 h); KisVLineConstIteratorSP createVLineConstIteratorNG(qint32 x, qint32 y, qint32 h) const; KisRandomAccessorSP createRandomAccessorNG(qint32 x, qint32 y); KisRandomConstAccessorSP createRandomConstAccessorNG(qint32 x, qint32 y) const; /** * Create an iterator that will "artificially" extend the paint device with the * value of the border when trying to access values outside the range of data. * * @param x x of top left corner * @param y y of top left corner * @param w width of the border * @param _dataWidth indicates the rectangle that truly contains data */ KisRepeatHLineConstIteratorSP createRepeatHLineConstIterator(qint32 x, qint32 y, qint32 w, const QRect& _dataWidth) const; /** * Create an iterator that will "artificially" extend the paint device with the * value of the border when trying to access values outside the range of data. * * @param x x of top left corner * @param y y of top left corner * @param h height of the border * @param _dataWidth indicates the rectangle that truly contains data */ KisRepeatVLineConstIteratorSP createRepeatVLineConstIterator(qint32 x, qint32 y, qint32 h, const QRect& _dataWidth) const; /** * This function create a random accessor which can easily access to sub pixel values. */ KisRandomSubAccessorSP createRandomSubAccessor() const; /** Clear the selected pixels from the paint device */ void clearSelection(KisSelectionSP selection); Q_SIGNALS: void profileChanged(const KoColorProfile * profile); void colorSpaceChanged(const KoColorSpace *colorspace); public: friend class PaintDeviceCache; /** * Caclculates exact bounds of the device. Used internally * by a transparent caching system. The solution is very slow * because it does a linear scanline search. So the complexity * is n*n at worst. * * \see exactBounds(), nonDefaultPixelArea() */ QRect calculateExactBounds(bool nonDefaultOnly) const; public: struct MemoryReleaseObject : public QObject { ~MemoryReleaseObject() override; }; static MemoryReleaseObject* createMemoryReleaseObject(); public: struct LodDataStruct { virtual ~LodDataStruct(); }; QRegion regionForLodSyncing() const; LodDataStruct* createLodDataStruct(int lod); void updateLodDataStruct(LodDataStruct *dst, const QRect &srcRect); void uploadLodDataStruct(LodDataStruct *dst); void generateLodCloneDevice(KisPaintDeviceSP dst, const QRect &originalRect, int lod); void setProjectionDevice(bool value); void tesingFetchLodDevice(KisPaintDeviceSP targetDevice); private: KisPaintDevice& operator=(const KisPaintDevice&); void init(const KoColorSpace *colorSpace, KisDefaultBoundsBaseSP defaultBounds, KisNodeWSP parent, const QString& name); // Only KisPainter is allowed to have access to these low-level methods friend class KisPainter; /** * Return a vector with in order the size in bytes of the channels * in the colorspace of this paint device. */ QVector channelSizes() const; void emitColorSpaceChanged(); void emitProfileChanged(); private: friend class KisPaintDeviceFramesInterface; protected: friend class KisSelectionTest; KisNodeWSP parentNode() const; private: struct Private; Private * const m_d; }; #endif // KIS_PAINT_DEVICE_IMPL_H_ diff --git a/libs/image/kis_paint_device_data.h b/libs/image/kis_paint_device_data.h index 8b97cae212..e175f61d11 100644 --- a/libs/image/kis_paint_device_data.h +++ b/libs/image/kis_paint_device_data.h @@ -1,289 +1,319 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_PAINT_DEVICE_DATA_H #define __KIS_PAINT_DEVICE_DATA_H #include "KoAlwaysInline.h" #include "kundo2command.h" struct DirectDataAccessPolicy { DirectDataAccessPolicy(KisDataManager *dataManager, KisIteratorCompleteListener *completionListener) : m_dataManager(dataManager), m_completionListener(completionListener){} KisHLineConstIteratorSP createConstIterator(const QRect &rect) { const int xOffset = 0; const int yOffset = 0; return new KisHLineIterator2(m_dataManager, rect.x(), rect.y(), rect.width(), xOffset, yOffset, false, m_completionListener); } KisHLineIteratorSP createIterator(const QRect &rect) { const int xOffset = 0; const int yOffset = 0; return new KisHLineIterator2(m_dataManager, rect.x(), rect.y(), rect.width(), xOffset, yOffset, true, m_completionListener); } int pixelSize() const { return m_dataManager->pixelSize(); } KisDataManager *m_dataManager; KisIteratorCompleteListener *m_completionListener; }; class KisPaintDeviceData { public: KisPaintDeviceData(KisPaintDevice *paintDevice) : m_cache(paintDevice), m_x(0), m_y(0), m_colorSpace(0), m_levelOfDetail(0), m_cacheInvalidator(this) { } KisPaintDeviceData(KisPaintDevice *paintDevice, const KisPaintDeviceData *rhs, bool cloneContent) : m_dataManager(cloneContent ? new KisDataManager(*rhs->m_dataManager) : new KisDataManager(rhs->m_dataManager->pixelSize(), rhs->m_dataManager->defaultPixel())), m_cache(paintDevice), m_x(rhs->m_x), m_y(rhs->m_y), m_colorSpace(rhs->m_colorSpace), m_levelOfDetail(rhs->m_levelOfDetail), m_cacheInvalidator(this) { m_cache.setupCache(); } void init(const KoColorSpace *cs, KisDataManagerSP dataManager) { m_colorSpace = cs; m_dataManager = dataManager; m_cache.setupCache(); } - class ChangeColorSpaceCommand : public KUndo2Command { + class ChangeProfileCommand : public KUndo2Command { public: - ChangeColorSpaceCommand(KisPaintDeviceData *data, - KisDataManagerSP oldDm, KisDataManagerSP newDm, - const KoColorSpace *oldCs, const KoColorSpace *newCs, - KUndo2Command *parent) + ChangeProfileCommand(KisPaintDeviceData *data, + const KoColorSpace *oldCs, const KoColorSpace *newCs, + KUndo2Command *parent) : KUndo2Command(parent), m_firstRun(true), m_data(data), - m_oldDm(oldDm), - m_newDm(newDm), m_oldCs(oldCs), m_newCs(newCs) { } - void forcedRedo() { - m_data->m_dataManager = m_newDm; + virtual void forcedRedo() { m_data->m_colorSpace = m_newCs; m_data->m_cache.setupCache(); } void redo() override { KUndo2Command::redo(); if (!m_firstRun) { m_firstRun = false; return; } forcedRedo(); } void undo() override { - m_data->m_dataManager = m_oldDm; m_data->m_colorSpace = m_oldCs; m_data->m_cache.setupCache(); KUndo2Command::undo(); } + protected: + KisPaintDeviceData *m_data; + private: bool m_firstRun; + const KoColorSpace *m_oldCs; + const KoColorSpace *m_newCs; + }; - KisPaintDeviceData *m_data; + + class ChangeColorSpaceCommand : public ChangeProfileCommand { + public: + ChangeColorSpaceCommand(KisPaintDeviceData *data, + KisDataManagerSP oldDm, KisDataManagerSP newDm, + const KoColorSpace *oldCs, const KoColorSpace *newCs, + KUndo2Command *parent) + : ChangeProfileCommand(data, oldCs, newCs, parent), + m_oldDm(oldDm), + m_newDm(newDm) + { + } + + void forcedRedo() override { + m_data->m_dataManager = m_newDm; + ChangeProfileCommand::forcedRedo(); + } + + void undo() override { + m_data->m_dataManager = m_oldDm; + ChangeProfileCommand::undo(); + } + + private: KisDataManagerSP m_oldDm; KisDataManagerSP m_newDm; - const KoColorSpace *m_oldCs; - const KoColorSpace *m_newCs; }; - void assignColorSpace(const KoColorSpace *dstColorSpace) { + void assignColorSpace(const KoColorSpace *dstColorSpace, KUndo2Command *parentCommand) { + if (*m_colorSpace->profile() == *dstColorSpace->profile()) return; + KIS_ASSERT_RECOVER_RETURN(m_colorSpace->pixelSize() == dstColorSpace->pixelSize()); - m_colorSpace = dstColorSpace; - m_cache.invalidate(); + ChangeProfileCommand *cmd = + new ChangeProfileCommand(this, + m_colorSpace, dstColorSpace, + parentCommand); + cmd->forcedRedo(); + if (!parentCommand) { + delete cmd; + } } void convertDataColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, KUndo2Command *parentCommand) { typedef KisSequentialIteratorBase, DirectDataAccessPolicy> InternalSequentialConstIterator; typedef KisSequentialIteratorBase, DirectDataAccessPolicy> InternalSequentialIterator; if (m_colorSpace == dstColorSpace || *m_colorSpace == *dstColorSpace) { return; } QRect rc = m_dataManager->region().boundingRect(); const int dstPixelSize = dstColorSpace->pixelSize(); QScopedArrayPointer dstDefaultPixel(new quint8[dstPixelSize]); memset(dstDefaultPixel.data(), 0, dstPixelSize); m_colorSpace->convertPixelsTo(m_dataManager->defaultPixel(), dstDefaultPixel.data(), dstColorSpace, 1, renderingIntent, conversionFlags); KisDataManagerSP dstDataManager = new KisDataManager(dstPixelSize, dstDefaultPixel.data()); if (!rc.isEmpty()) { InternalSequentialConstIterator srcIt(DirectDataAccessPolicy(m_dataManager.data(), cacheInvalidator()), rc); InternalSequentialIterator dstIt(DirectDataAccessPolicy(dstDataManager.data(), cacheInvalidator()), rc); int nConseqPixels = srcIt.nConseqPixels(); // since we are accessing data managers directly, the columns are always aligned KIS_SAFE_ASSERT_RECOVER_NOOP(srcIt.nConseqPixels() == dstIt.nConseqPixels()); while(srcIt.nextPixels(nConseqPixels) && dstIt.nextPixels(nConseqPixels)) { nConseqPixels = srcIt.nConseqPixels(); const quint8 *srcData = srcIt.rawDataConst(); quint8 *dstData = dstIt.rawData(); m_colorSpace->convertPixelsTo(srcData, dstData, dstColorSpace, nConseqPixels, renderingIntent, conversionFlags); } } // becomes owned by the parent ChangeColorSpaceCommand *cmd = new ChangeColorSpaceCommand(this, m_dataManager, dstDataManager, m_colorSpace, dstColorSpace, parentCommand); cmd->forcedRedo(); if (!parentCommand) { delete cmd; } } void prepareClone(const KisPaintDeviceData *srcData, bool copyContent = false) { m_x = srcData->x(); m_y = srcData->y(); if (copyContent) { m_dataManager = new KisDataManager(*srcData->dataManager()); } else if (m_dataManager->pixelSize() != srcData->dataManager()->pixelSize()) { // NOTE: we don't check default pixel value! it is the task of // the higher level! m_dataManager = new KisDataManager(srcData->dataManager()->pixelSize(), srcData->dataManager()->defaultPixel()); m_cache.setupCache(); } else { m_dataManager->clear(); const quint8 *srcDefPixel = srcData->dataManager()->defaultPixel(); const int cmp = memcmp(srcDefPixel, m_dataManager->defaultPixel(), m_dataManager->pixelSize()); if (cmp != 0) { m_dataManager->setDefaultPixel(srcDefPixel); } } m_levelOfDetail = srcData->levelOfDetail(); m_colorSpace = srcData->colorSpace(); m_cache.invalidate(); } ALWAYS_INLINE KisDataManagerSP dataManager() const { return m_dataManager; } ALWAYS_INLINE KisPaintDeviceCache* cache() { return &m_cache; } ALWAYS_INLINE qint32 x() const { return m_x; } ALWAYS_INLINE void setX(qint32 value) { m_x = value; } ALWAYS_INLINE qint32 y() const { return m_y; } ALWAYS_INLINE void setY(qint32 value) { m_y = value; } ALWAYS_INLINE const KoColorSpace* colorSpace() const { return m_colorSpace; } ALWAYS_INLINE qint32 levelOfDetail() const { return m_levelOfDetail; } ALWAYS_INLINE void setLevelOfDetail(qint32 value) { m_levelOfDetail = value; } ALWAYS_INLINE KisIteratorCompleteListener* cacheInvalidator() { return &m_cacheInvalidator; } private: struct CacheInvalidator : public KisIteratorCompleteListener { CacheInvalidator(KisPaintDeviceData *_q) : q(_q) {} void notifyWritableIteratorCompleted() override { q->cache()->invalidate(); } private: KisPaintDeviceData *q; }; private: KisDataManagerSP m_dataManager; KisPaintDeviceCache m_cache; qint32 m_x; qint32 m_y; const KoColorSpace* m_colorSpace; qint32 m_levelOfDetail; CacheInvalidator m_cacheInvalidator; }; #endif /* __KIS_PAINT_DEVICE_DATA_H */ diff --git a/libs/image/lazybrush/kis_colorize_mask.cpp b/libs/image/lazybrush/kis_colorize_mask.cpp index 0cb5acd23a..65a4a9b32b 100644 --- a/libs/image/lazybrush/kis_colorize_mask.cpp +++ b/libs/image/lazybrush/kis_colorize_mask.cpp @@ -1,1174 +1,1172 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_colorize_mask.h" #include #include #include #include "kis_pixel_selection.h" #include "kis_icon_utils.h" #include "kis_node_visitor.h" #include "kis_processing_visitor.h" #include "kis_painter.h" #include "kis_fill_painter.h" #include "kis_lazy_fill_tools.h" #include "kis_cached_paint_device.h" #include "kis_paint_device_debug_utils.h" #include "kis_layer_properties_icons.h" #include "kis_thread_safe_signal_compressor.h" #include "kis_colorize_stroke_strategy.h" #include "kis_multiway_cut.h" #include "kis_image.h" #include "kis_layer.h" #include "kis_macro_based_undo_store.h" #include "kis_post_execution_undo_adapter.h" #include "kis_command_utils.h" #include "kis_processing_applicator.h" #include "krita_utils.h" using namespace KisLazyFillTools; struct KisColorizeMask::Private { Private(KisColorizeMask *_q) : q(_q), coloringProjection(new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8())), fakePaintDevice(new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8())), filteredSource(new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8())), needAddCurrentKeyStroke(false), showKeyStrokes(true), showColoring(true), needsUpdate(true), originalSequenceNumber(-1), updateCompressor(1000, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT), dirtyParentUpdateCompressor(200, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT), prefilterRecalculationCompressor(1000, KisSignalCompressor::POSTPONE), updateIsRunning(false), filteringOptions(false, 4.0, 15, 0.7), limitToDeviceBounds(false) { } Private(const Private &rhs, KisColorizeMask *_q) : q(_q), coloringProjection(new KisPaintDevice(*rhs.coloringProjection)), fakePaintDevice(new KisPaintDevice(*rhs.fakePaintDevice)), filteredSource(new KisPaintDevice(*rhs.filteredSource)), filteredDeviceBounds(rhs.filteredDeviceBounds), needAddCurrentKeyStroke(rhs.needAddCurrentKeyStroke), showKeyStrokes(rhs.showKeyStrokes), showColoring(rhs.showColoring), needsUpdate(false), originalSequenceNumber(-1), updateCompressor(1000, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT), dirtyParentUpdateCompressor(200, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT), prefilterRecalculationCompressor(1000, KisSignalCompressor::POSTPONE), offset(rhs.offset), updateIsRunning(false), filteringOptions(rhs.filteringOptions), limitToDeviceBounds(rhs.limitToDeviceBounds) { Q_FOREACH (const KeyStroke &stroke, rhs.keyStrokes) { keyStrokes << KeyStroke(KisPaintDeviceSP(new KisPaintDevice(*stroke.dev)), stroke.color, stroke.isTransparent); } } KisColorizeMask *q = 0; QList keyStrokes; KisPaintDeviceSP coloringProjection; KisPaintDeviceSP fakePaintDevice; KisPaintDeviceSP filteredSource; QRect filteredDeviceBounds; KoColor currentColor; KisPaintDeviceSP currentKeyStrokeDevice; bool needAddCurrentKeyStroke; bool showKeyStrokes; bool showColoring; KisCachedSelection cachedSelection; bool needsUpdate; int originalSequenceNumber; KisThreadSafeSignalCompressor updateCompressor; KisThreadSafeSignalCompressor dirtyParentUpdateCompressor; KisThreadSafeSignalCompressor prefilterRecalculationCompressor; QPoint offset; bool updateIsRunning; QStack extentBeforeUpdateStart; FilteringOptions filteringOptions; bool filteringDirty = true; bool limitToDeviceBounds = false; bool filteredSourceValid(KisPaintDeviceSP parentDevice) { return !filteringDirty && originalSequenceNumber == parentDevice->sequenceNumber(); } void setNeedsUpdateImpl(bool value, bool requestedByUser); bool shouldShowFilteredSource() const; bool shouldShowColoring() const; }; KisColorizeMask::KisColorizeMask() : m_d(new Private(this)) { connect(&m_d->updateCompressor, SIGNAL(timeout()), SLOT(slotUpdateRegenerateFilling())); connect(this, SIGNAL(sigUpdateOnDirtyParent()), &m_d->dirtyParentUpdateCompressor, SLOT(start())); connect(&m_d->dirtyParentUpdateCompressor, SIGNAL(timeout()), SLOT(slotUpdateOnDirtyParent())); connect(&m_d->prefilterRecalculationCompressor, SIGNAL(timeout()), SLOT(slotRecalculatePrefilteredImage())); m_d->updateCompressor.moveToThread(qApp->thread()); } KisColorizeMask::~KisColorizeMask() { } KisColorizeMask::KisColorizeMask(const KisColorizeMask& rhs) : KisEffectMask(rhs), m_d(new Private(*rhs.m_d, this)) { connect(&m_d->updateCompressor, SIGNAL(timeout()), SLOT(slotUpdateRegenerateFilling())); connect(this, SIGNAL(sigUpdateOnDirtyParent()), &m_d->dirtyParentUpdateCompressor, SLOT(start())); connect(&m_d->dirtyParentUpdateCompressor, SIGNAL(timeout()), SLOT(slotUpdateOnDirtyParent())); m_d->updateCompressor.moveToThread(qApp->thread()); } void KisColorizeMask::initializeCompositeOp() { KisLayerSP parentLayer(qobject_cast(parent().data())); if (!parentLayer || !parentLayer->original()) return; KisImageSP image = parentLayer->image(); if (!image) return; const qreal samplePortion = 0.1; const qreal alphaPortion = KritaUtils::estimatePortionOfTransparentPixels(parentLayer->original(), image->bounds(), samplePortion); setCompositeOpId(alphaPortion > 0.3 ? COMPOSITE_BEHIND : COMPOSITE_MULT); } const KoColorSpace* KisColorizeMask::colorSpace() const { return m_d->fakePaintDevice->colorSpace(); } struct SetKeyStrokesColorSpaceCommand : public KUndo2Command { SetKeyStrokesColorSpaceCommand(const KoColorSpace *dstCS, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, QList *list, KisColorizeMaskSP node) : m_dstCS(dstCS), m_renderingIntent(renderingIntent), m_conversionFlags(conversionFlags), m_list(list), m_node(node) {} void undo() override { KIS_ASSERT_RECOVER_RETURN(m_list->size() == m_oldColors.size()); for (int i = 0; i < m_list->size(); i++) { (*m_list)[i].color = m_oldColors[i]; } m_node->setNeedsUpdate(true); emit m_node->sigKeyStrokesListChanged(); } void redo() override { if (m_oldColors.isEmpty()) { Q_FOREACH(const KeyStroke &stroke, *m_list) { m_oldColors << stroke.color; m_newColors << stroke.color; m_newColors.last().convertTo(m_dstCS, m_renderingIntent, m_conversionFlags); } } KIS_ASSERT_RECOVER_RETURN(m_list->size() == m_newColors.size()); for (int i = 0; i < m_list->size(); i++) { (*m_list)[i].color = m_newColors[i]; } m_node->setNeedsUpdate(true); emit m_node->sigKeyStrokesListChanged(); } private: QVector m_oldColors; QVector m_newColors; const KoColorSpace *m_dstCS; KoColorConversionTransformation::Intent m_renderingIntent; KoColorConversionTransformation::ConversionFlags m_conversionFlags; QList *m_list; KisColorizeMaskSP m_node; }; -void KisColorizeMask::setProfile(const KoColorProfile *profile) +void KisColorizeMask::setProfile(const KoColorProfile *profile, KUndo2Command *parentCommand) { - // WARNING: there is no undo information, used only while loading! - - m_d->fakePaintDevice->setProfile(profile); - m_d->coloringProjection->setProfile(profile); + m_d->fakePaintDevice->setProfile(profile, parentCommand); + m_d->coloringProjection->setProfile(profile, parentCommand); for (auto stroke : m_d->keyStrokes) { stroke.color.setProfile(profile); } } KUndo2Command* KisColorizeMask::setColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { using namespace KisCommandUtils; CompositeCommand *composite = new CompositeCommand(); m_d->fakePaintDevice->convertTo(dstColorSpace, renderingIntent, conversionFlags, composite); m_d->coloringProjection->convertTo(dstColorSpace, renderingIntent, conversionFlags, composite); KUndo2Command *strokesConversionCommand = new SetKeyStrokesColorSpaceCommand( dstColorSpace, renderingIntent, conversionFlags, &m_d->keyStrokes, KisColorizeMaskSP(this)); strokesConversionCommand->redo(); composite->addCommand(new SkipFirstRedoWrapper(strokesConversionCommand)); return composite; } bool KisColorizeMask::needsUpdate() const { return m_d->needsUpdate; } void KisColorizeMask::setNeedsUpdate(bool value) { m_d->setNeedsUpdateImpl(value, true); } void KisColorizeMask::Private::setNeedsUpdateImpl(bool value, bool requestedByUser) { if (value != needsUpdate) { needsUpdate = value; q->baseNodeChangedCallback(); if (!value && requestedByUser) { updateCompressor.start(); } } } void KisColorizeMask::slotUpdateRegenerateFilling(bool prefilterOnly) { KisPaintDeviceSP src = parent()->original(); KIS_ASSERT_RECOVER_RETURN(src); const bool filteredSourceValid = m_d->filteredSourceValid(src); m_d->originalSequenceNumber = src->sequenceNumber(); m_d->filteringDirty = false; if (!prefilterOnly) { m_d->coloringProjection->clear(); } KisLayerSP parentLayer(qobject_cast(parent().data())); if (!parentLayer) return; KisImageSP image = parentLayer->image(); if (image) { m_d->updateIsRunning = true; QRect fillBounds; if (m_d->limitToDeviceBounds) { fillBounds |= src->exactBounds(); Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) { fillBounds |= stroke.dev->exactBounds(); } fillBounds &= image->bounds(); } else { fillBounds = image->bounds(); } m_d->filteredDeviceBounds = fillBounds; KisColorizeStrokeStrategy *strategy = new KisColorizeStrokeStrategy(src, m_d->coloringProjection, m_d->filteredSource, filteredSourceValid, fillBounds, this, prefilterOnly); strategy->setFilteringOptions(m_d->filteringOptions); Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) { const KoColor color = !stroke.isTransparent ? stroke.color : KoColor(Qt::transparent, stroke.color.colorSpace()); strategy->addKeyStroke(stroke.dev, color); } m_d->extentBeforeUpdateStart.push(extent()); connect(strategy, SIGNAL(sigFinished(bool)), SLOT(slotRegenerationFinished(bool))); connect(strategy, SIGNAL(sigCancelled()), SLOT(slotRegenerationCancelled())); KisStrokeId id = image->startStroke(strategy); image->endStroke(id); } } void KisColorizeMask::slotUpdateOnDirtyParent() { if (!parent()) { // When the colorize mask is being merged, // the update is performed for all the layers, // so the invisible areas around the canvas are included in the merged layer. // Colorize Mask gets the info that its parent is "dirty" (needs updating), // but when it arrives, the parent doesn't exists anymore and is set to null. // Colorize Mask doesn't work outside of the canvas anyway (at least in time of writing). return; } KisPaintDeviceSP src = parent()->original(); KIS_ASSERT_RECOVER_RETURN(src); if (!m_d->filteredSourceValid(src)) { const QRect &oldExtent = extent(); m_d->setNeedsUpdateImpl(true, false); m_d->filteringDirty = true; setDirty(oldExtent | extent()); } } void KisColorizeMask::slotRecalculatePrefilteredImage() { slotUpdateRegenerateFilling(true); } void KisColorizeMask::slotRegenerationFinished(bool prefilterOnly) { m_d->updateIsRunning = false; if (!prefilterOnly) { m_d->setNeedsUpdateImpl(false, false); } QRect oldExtent; if (!m_d->extentBeforeUpdateStart.isEmpty()) { oldExtent = m_d->extentBeforeUpdateStart.pop(); } else { KIS_SAFE_ASSERT_RECOVER_NOOP(!m_d->extentBeforeUpdateStart.isEmpty()); // always fail! } setDirty(oldExtent | extent()); } void KisColorizeMask::slotRegenerationCancelled() { slotRegenerationFinished(true); } KisBaseNode::PropertyList KisColorizeMask::sectionModelProperties() const { KisBaseNode::PropertyList l = KisMask::sectionModelProperties(); l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::colorizeNeedsUpdate, needsUpdate()); l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::colorizeEditKeyStrokes, showKeyStrokes()); l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::colorizeShowColoring, showColoring()); return l; } void KisColorizeMask::setSectionModelProperties(const KisBaseNode::PropertyList &properties) { KisMask::setSectionModelProperties(properties); Q_FOREACH (const KisBaseNode::Property &property, properties) { if (property.id == KisLayerPropertiesIcons::colorizeNeedsUpdate.id()) { if (m_d->needsUpdate && m_d->needsUpdate != property.state.toBool()) { setNeedsUpdate(property.state.toBool()); } } if (property.id == KisLayerPropertiesIcons::colorizeEditKeyStrokes.id()) { if (m_d->showKeyStrokes != property.state.toBool()) { setShowKeyStrokes(property.state.toBool()); } } if (property.id == KisLayerPropertiesIcons::colorizeShowColoring.id()) { if (m_d->showColoring != property.state.toBool()) { setShowColoring(property.state.toBool()); } } } } KisPaintDeviceSP KisColorizeMask::paintDevice() const { return m_d->showKeyStrokes && !m_d->updateIsRunning ? m_d->fakePaintDevice : KisPaintDeviceSP(); } KisPaintDeviceSP KisColorizeMask::coloringProjection() const { return m_d->coloringProjection; } KisPaintDeviceSP KisColorizeMask::colorPickSourceDevice() const { return m_d->shouldShowColoring() && !m_d->coloringProjection->extent().isEmpty() ? m_d->coloringProjection : projection(); } QIcon KisColorizeMask::icon() const { return KisIconUtils::loadIcon("colorizeMask"); } bool KisColorizeMask::accept(KisNodeVisitor &v) { return v.visit(this); } void KisColorizeMask::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { return visitor.visit(this, undoAdapter); } bool KisColorizeMask::Private::shouldShowFilteredSource() const { return !updateIsRunning && showKeyStrokes && !filteringDirty && filteredSource && !filteredSource->extent().isEmpty(); } bool KisColorizeMask::Private::shouldShowColoring() const { return !updateIsRunning && showColoring && coloringProjection; } QRect KisColorizeMask::decorateRect(KisPaintDeviceSP &src, KisPaintDeviceSP &dst, const QRect &rect, PositionToFilthy maskPos) const { Q_UNUSED(maskPos); if (maskPos == N_ABOVE_FILTHY) { // the source layer has changed, we should update the filtered cache! if (!m_d->filteringDirty) { emit sigUpdateOnDirtyParent(); } } KIS_ASSERT(dst != src); // Draw the filling and the original layer { KisPainter gc(dst); if (m_d->shouldShowFilteredSource()) { const QRect drawRect = m_d->limitToDeviceBounds ? rect & m_d->filteredDeviceBounds : rect; gc.setOpacity(128); gc.bitBlt(drawRect.topLeft(), m_d->filteredSource, drawRect); } else { gc.setOpacity(255); gc.bitBlt(rect.topLeft(), src, rect); } if (m_d->shouldShowColoring()) { gc.setOpacity(opacity()); gc.setCompositeOp(compositeOpId()); gc.bitBlt(rect.topLeft(), m_d->coloringProjection, rect); } } // Draw the key strokes if (m_d->showKeyStrokes) { KisIndirectPaintingSupport::ReadLocker locker(this); KisCachedSelection::Guard s1(m_d->cachedSelection); KisCachedSelection::Guard s2(m_d->cachedSelection); KisSelectionSP selection = s1.selection(); KisPixelSelectionSP tempSelection = s2.selection()->pixelSelection(); KisPaintDeviceSP temporaryTarget = this->temporaryTarget(); const bool isTemporaryTargetErasing = temporaryCompositeOp() == COMPOSITE_ERASE; const QRect temporaryExtent = temporaryTarget ? temporaryTarget->extent() : QRect(); KisFillPainter gc(dst); QList extendedStrokes = m_d->keyStrokes; if (m_d->currentKeyStrokeDevice && m_d->needAddCurrentKeyStroke && !isTemporaryTargetErasing) { extendedStrokes << KeyStroke(m_d->currentKeyStrokeDevice, m_d->currentColor); } Q_FOREACH (const KeyStroke &stroke, extendedStrokes) { selection->pixelSelection()->makeCloneFromRough(stroke.dev, rect); gc.setSelection(selection); if (stroke.color == m_d->currentColor || (isTemporaryTargetErasing && temporaryExtent.intersects(selection->pixelSelection()->selectedRect()))) { if (temporaryTarget) { tempSelection->copyAlphaFrom(temporaryTarget, rect); KisPainter selectionPainter(selection->pixelSelection()); setupTemporaryPainter(&selectionPainter); selectionPainter.bitBlt(rect.topLeft(), tempSelection, rect); } } gc.fillSelection(rect, stroke.color); } } return rect; } struct DeviceExtentPolicy { inline QRect operator() (const KisPaintDevice *dev) { return dev->extent(); } }; struct DeviceExactBoundsPolicy { inline QRect operator() (const KisPaintDevice *dev) { return dev->exactBounds(); } }; template QRect KisColorizeMask::calculateMaskBounds(DeviceMetricPolicy boundsPolicy) const { QRect rc; if (m_d->shouldShowFilteredSource()) { rc |= boundsPolicy(m_d->filteredSource); } if (m_d->shouldShowColoring()) { rc |= boundsPolicy(m_d->coloringProjection); } if (m_d->showKeyStrokes) { Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) { rc |= boundsPolicy(stroke.dev); } KisIndirectPaintingSupport::ReadLocker locker(this); KisPaintDeviceSP temporaryTarget = this->temporaryTarget(); if (temporaryTarget) { rc |= boundsPolicy(temporaryTarget); } } return rc; } QRect KisColorizeMask::extent() const { return calculateMaskBounds(DeviceExtentPolicy()); } QRect KisColorizeMask::exactBounds() const { return calculateMaskBounds(DeviceExactBoundsPolicy()); } QRect KisColorizeMask::nonDependentExtent() const { return extent(); } KisImageSP KisColorizeMask::fetchImage() const { KisLayerSP parentLayer(qobject_cast(parent().data())); if (!parentLayer) return KisImageSP(); return parentLayer->image(); } void KisColorizeMask::setImage(KisImageWSP image) { KisDefaultBoundsSP bounds(new KisDefaultBounds(image)); auto it = m_d->keyStrokes.begin(); for(; it != m_d->keyStrokes.end(); ++it) { it->dev->setDefaultBounds(bounds); } m_d->coloringProjection->setDefaultBounds(bounds); m_d->fakePaintDevice->setDefaultBounds(bounds); m_d->filteredSource->setDefaultBounds(bounds); } void KisColorizeMask::setCurrentColor(const KoColor &_color) { KoColor color = _color; color.convertTo(colorSpace()); WriteLocker locker(this); m_d->setNeedsUpdateImpl(true, false); QList::const_iterator it = std::find_if(m_d->keyStrokes.constBegin(), m_d->keyStrokes.constEnd(), [color] (const KeyStroke &s) { return s.color == color; }); KisPaintDeviceSP activeDevice; bool newKeyStroke = false; if (it == m_d->keyStrokes.constEnd()) { activeDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); activeDevice->setParentNode(this); activeDevice->setDefaultBounds(KisDefaultBoundsBaseSP(new KisDefaultBounds(fetchImage()))); newKeyStroke = true; } else { activeDevice = it->dev; } m_d->currentColor = color; m_d->currentKeyStrokeDevice = activeDevice; m_d->needAddCurrentKeyStroke = newKeyStroke; } struct KeyStrokeAddRemoveCommand : public KisCommandUtils::FlipFlopCommand { KeyStrokeAddRemoveCommand(bool add, int index, KeyStroke stroke, QList *list, KisColorizeMaskSP node) : FlipFlopCommand(!add), m_index(index), m_stroke(stroke), m_list(list), m_node(node) {} void partA() override { m_list->insert(m_index, m_stroke); m_node->setNeedsUpdate(true); emit m_node->sigKeyStrokesListChanged(); } void partB() override { KIS_ASSERT_RECOVER_RETURN((*m_list)[m_index] == m_stroke); m_list->removeAt(m_index); m_node->setNeedsUpdate(true); emit m_node->sigKeyStrokesListChanged(); } private: int m_index; KeyStroke m_stroke; QList *m_list; KisColorizeMaskSP m_node; }; void KisColorizeMask::mergeToLayer(KisNodeSP layer, KisPostExecutionUndoAdapter *undoAdapter, const KUndo2MagicString &transactionText,int timedID) { Q_UNUSED(layer); WriteLocker locker(this); KisPaintDeviceSP temporaryTarget = this->temporaryTarget(); const bool isTemporaryTargetErasing = temporaryCompositeOp() == COMPOSITE_ERASE; const QRect temporaryExtent = temporaryTarget ? temporaryTarget->extent() : QRect(); KisSavedMacroCommand *macro = undoAdapter->createMacro(transactionText); KisMacroBasedUndoStore store(macro); KisPostExecutionUndoAdapter fakeUndoAdapter(&store, undoAdapter->strokesFacade()); /** * Add a new key stroke plane */ if (m_d->needAddCurrentKeyStroke && !isTemporaryTargetErasing) { KeyStroke key(m_d->currentKeyStrokeDevice, m_d->currentColor); KUndo2Command *cmd = new KeyStrokeAddRemoveCommand( true, m_d->keyStrokes.size(), key, &m_d->keyStrokes, KisColorizeMaskSP(this)); cmd->redo(); fakeUndoAdapter.addCommand(toQShared(cmd)); } /** * When erasing, the brush affects all the key strokes, not only * the current one. */ if (!isTemporaryTargetErasing) { mergeToLayerImpl(m_d->currentKeyStrokeDevice, &fakeUndoAdapter, transactionText, timedID, false); } else { Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) { if (temporaryExtent.intersects(stroke.dev->extent())) { mergeToLayerImpl(stroke.dev, &fakeUndoAdapter, transactionText, timedID, false); } } } mergeToLayerImpl(m_d->fakePaintDevice, &fakeUndoAdapter, transactionText, timedID, false); m_d->currentKeyStrokeDevice = 0; m_d->currentColor = KoColor(); releaseResources(); /** * Try removing the key strokes that has been completely erased */ if (isTemporaryTargetErasing) { for (int index = 0; index < m_d->keyStrokes.size(); /*noop*/) { const KeyStroke &stroke = m_d->keyStrokes[index]; if (stroke.dev->exactBounds().isEmpty()) { KUndo2Command *cmd = new KeyStrokeAddRemoveCommand( false, index, stroke, &m_d->keyStrokes, KisColorizeMaskSP(this)); cmd->redo(); fakeUndoAdapter.addCommand(toQShared(cmd)); } else { index++; } } } undoAdapter->addMacro(macro); } void KisColorizeMask::writeMergeData(KisPainter *painter, KisPaintDeviceSP src) { const KoColorSpace *alpha8 = KoColorSpaceRegistry::instance()->alpha8(); const bool nonAlphaDst = !(*painter->device()->colorSpace() == *alpha8); if (nonAlphaDst) { Q_FOREACH (const QRect &rc, src->region().rects()) { painter->bitBlt(rc.topLeft(), src, rc); } } else { KisCachedSelection::Guard s1(m_d->cachedSelection); KisPixelSelectionSP tempSelection = s1.selection()->pixelSelection(); Q_FOREACH (const QRect &rc, src->region().rects()) { tempSelection->copyAlphaFrom(src, rc); painter->bitBlt(rc.topLeft(), tempSelection, rc); } } } bool KisColorizeMask::supportsNonIndirectPainting() const { return false; } bool KisColorizeMask::showColoring() const { return m_d->showColoring; } void KisColorizeMask::setShowColoring(bool value) { QRect savedExtent; if (m_d->showColoring && !value) { savedExtent = extent(); } m_d->showColoring = value; baseNodeChangedCallback(); if (!savedExtent.isEmpty()) { setDirty(savedExtent); } } bool KisColorizeMask::showKeyStrokes() const { return m_d->showKeyStrokes; } void KisColorizeMask::setShowKeyStrokes(bool value) { QRect savedExtent; if (m_d->showKeyStrokes && !value) { savedExtent = extent(); } m_d->showKeyStrokes = value; baseNodeChangedCallback(); if (!savedExtent.isEmpty()) { setDirty(savedExtent); } regeneratePrefilteredDeviceIfNeeded(); } KisColorizeMask::KeyStrokeColors KisColorizeMask::keyStrokesColors() const { KeyStrokeColors colors; // TODO: thread safety! for (int i = 0; i < m_d->keyStrokes.size(); i++) { colors.colors << m_d->keyStrokes[i].color; if (m_d->keyStrokes[i].isTransparent) { colors.transparentIndex = i; } } return colors; } struct SetKeyStrokeColorsCommand : public KUndo2Command { SetKeyStrokeColorsCommand(const QList newList, QList *list, KisColorizeMaskSP node) : m_newList(newList), m_oldList(*list), m_list(list), m_node(node) {} void redo() override { *m_list = m_newList; m_node->setNeedsUpdate(true); emit m_node->sigKeyStrokesListChanged(); m_node->setDirty(); } void undo() override { *m_list = m_oldList; m_node->setNeedsUpdate(true); emit m_node->sigKeyStrokesListChanged(); m_node->setDirty(); } private: QList m_newList; QList m_oldList; QList *m_list; KisColorizeMaskSP m_node; }; void KisColorizeMask::setKeyStrokesColors(KeyStrokeColors colors) { KIS_ASSERT_RECOVER_RETURN(colors.colors.size() == m_d->keyStrokes.size()); QList newList = m_d->keyStrokes; for (int i = 0; i < newList.size(); i++) { newList[i].color = colors.colors[i]; newList[i].color.convertTo(colorSpace()); newList[i].isTransparent = colors.transparentIndex == i; } KisProcessingApplicator applicator(fetchImage(), KisNodeSP(this), KisProcessingApplicator::NONE, KisImageSignalVector(), kundo2_i18n("Change Key Stroke Color")); applicator.applyCommand( new SetKeyStrokeColorsCommand( newList, &m_d->keyStrokes, KisColorizeMaskSP(this))); applicator.end(); } void KisColorizeMask::removeKeyStroke(const KoColor &_color) { KoColor color = _color; color.convertTo(colorSpace()); QList::iterator it = std::find_if(m_d->keyStrokes.begin(), m_d->keyStrokes.end(), [color] (const KeyStroke &s) { return s.color == color; }); KIS_SAFE_ASSERT_RECOVER_RETURN(it != m_d->keyStrokes.end()); const int index = it - m_d->keyStrokes.begin(); KisProcessingApplicator applicator(KisImageWSP(fetchImage()), KisNodeSP(this), KisProcessingApplicator::NONE, KisImageSignalVector(), kundo2_i18n("Remove Key Stroke")); applicator.applyCommand( new KeyStrokeAddRemoveCommand( false, index, *it, &m_d->keyStrokes, KisColorizeMaskSP(this))); applicator.end(); } QVector KisColorizeMask::allPaintDevices() const { QVector devices; Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) { devices << stroke.dev; } devices << m_d->coloringProjection; devices << m_d->fakePaintDevice; return devices; } void KisColorizeMask::resetCache() { m_d->filteredSource->clear(); m_d->originalSequenceNumber = -1; m_d->filteringDirty = true; rerenderFakePaintDevice(); slotUpdateRegenerateFilling(true); } void KisColorizeMask::setUseEdgeDetection(bool value) { m_d->filteringOptions.useEdgeDetection = value; m_d->filteringDirty = true; setNeedsUpdate(true); } bool KisColorizeMask::useEdgeDetection() const { return m_d->filteringOptions.useEdgeDetection; } void KisColorizeMask::setEdgeDetectionSize(qreal value) { m_d->filteringOptions.edgeDetectionSize = value; m_d->filteringDirty = true; setNeedsUpdate(true); } qreal KisColorizeMask::edgeDetectionSize() const { return m_d->filteringOptions.edgeDetectionSize; } void KisColorizeMask::setFuzzyRadius(qreal value) { m_d->filteringOptions.fuzzyRadius = value; m_d->filteringDirty = true; setNeedsUpdate(true); } qreal KisColorizeMask::fuzzyRadius() const { return m_d->filteringOptions.fuzzyRadius; } void KisColorizeMask::setCleanUpAmount(qreal value) { m_d->filteringOptions.cleanUpAmount = value; setNeedsUpdate(true); } qreal KisColorizeMask::cleanUpAmount() const { return m_d->filteringOptions.cleanUpAmount; } void KisColorizeMask::setLimitToDeviceBounds(bool value) { m_d->limitToDeviceBounds = value; m_d->filteringDirty = true; setNeedsUpdate(true); } bool KisColorizeMask::limitToDeviceBounds() const { return m_d->limitToDeviceBounds; } void KisColorizeMask::rerenderFakePaintDevice() { m_d->fakePaintDevice->clear(); KisFillPainter gc(m_d->fakePaintDevice); KisCachedSelection::Guard s1(m_d->cachedSelection); KisSelectionSP selection = s1.selection(); Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) { const QRect rect = stroke.dev->extent(); selection->pixelSelection()->makeCloneFromRough(stroke.dev, rect); gc.setSelection(selection); gc.fillSelection(rect, stroke.color); } } void KisColorizeMask::testingAddKeyStroke(KisPaintDeviceSP dev, const KoColor &color, bool isTransparent) { m_d->keyStrokes << KeyStroke(dev, color, isTransparent); } void KisColorizeMask::testingRegenerateMask() { slotUpdateRegenerateFilling(); m_d->updateIsRunning = false; } KisPaintDeviceSP KisColorizeMask::testingFilteredSource() const { return m_d->filteredSource; } QList KisColorizeMask::fetchKeyStrokesDirect() const { return m_d->keyStrokes; } void KisColorizeMask::setKeyStrokesDirect(const QList &strokes) { m_d->keyStrokes = strokes; for (auto it = m_d->keyStrokes.begin(); it != m_d->keyStrokes.end(); ++it) { it->dev->setParentNode(this); } KisImageSP image = fetchImage(); KIS_SAFE_ASSERT_RECOVER_RETURN(image); setImage(image); } qint32 KisColorizeMask::x() const { return m_d->offset.x(); } qint32 KisColorizeMask::y() const { return m_d->offset.y(); } void KisColorizeMask::setX(qint32 x) { const QPoint oldOffset = m_d->offset; m_d->offset.rx() = x; moveAllInternalDevices(m_d->offset - oldOffset); } void KisColorizeMask::setY(qint32 y) { const QPoint oldOffset = m_d->offset; m_d->offset.ry() = y; moveAllInternalDevices(m_d->offset - oldOffset); } KisPaintDeviceList KisColorizeMask::getLodCapableDevices() const { KisPaintDeviceList list; auto it = m_d->keyStrokes.begin(); for(; it != m_d->keyStrokes.end(); ++it) { list << it->dev; } list << m_d->coloringProjection; list << m_d->fakePaintDevice; list << m_d->filteredSource; return list; } void KisColorizeMask::regeneratePrefilteredDeviceIfNeeded() { if (!parent()) return; KisPaintDeviceSP src = parent()->original(); KIS_ASSERT_RECOVER_RETURN(src); if (!m_d->filteredSourceValid(src)) { // update the prefiltered source if needed slotUpdateRegenerateFilling(true); } } void KisColorizeMask::moveAllInternalDevices(const QPoint &diff) { QVector devices = allPaintDevices(); Q_FOREACH (KisPaintDeviceSP dev, devices) { dev->moveTo(dev->offset() + diff); } } diff --git a/libs/image/lazybrush/kis_colorize_mask.h b/libs/image/lazybrush/kis_colorize_mask.h index 46479ef140..5ba43ae090 100644 --- a/libs/image/lazybrush/kis_colorize_mask.h +++ b/libs/image/lazybrush/kis_colorize_mask.h @@ -1,179 +1,179 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_COLORIZE_MASK_H #define __KIS_COLORIZE_MASK_H #include #include "kis_types.h" #include "kis_effect_mask.h" #include "kritaimage_export.h" class KoColor; class KUndo2Command; namespace KisLazyFillTools { struct KeyStroke; } class KRITAIMAGE_EXPORT KisColorizeMask : public KisEffectMask { Q_OBJECT public: struct KeyStrokeColors { QVector colors; int transparentIndex = -1; }; public: KisColorizeMask(); ~KisColorizeMask() override; KisColorizeMask(const KisColorizeMask& rhs); void initializeCompositeOp(); const KoColorSpace* colorSpace() const override; // assign color profile without conversion of pixel data - void setProfile(const KoColorProfile *profile); + void setProfile(const KoColorProfile *profile, KUndo2Command *parentCommand); KUndo2Command* setColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()); KisPaintDeviceSP paintDevice() const override; KisPaintDeviceSP coloringProjection() const; KisPaintDeviceSP colorPickSourceDevice() const override; KisNodeSP clone() const override { return KisNodeSP(new KisColorizeMask(*this)); } QIcon icon() const override; void setImage(KisImageWSP image) override; bool accept(KisNodeVisitor &v) override; void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) override; QRect decorateRect(KisPaintDeviceSP &src, KisPaintDeviceSP &dst, const QRect & rc, PositionToFilthy maskPos) const override; void setCurrentColor(const KoColor &color) override; void mergeToLayer(KisNodeSP layer, KisPostExecutionUndoAdapter *undoAdapter, const KUndo2MagicString &transactionText,int timedID) override; void writeMergeData(KisPainter *painter, KisPaintDeviceSP src) override; bool supportsNonIndirectPainting() const override; QRect exactBounds() const override; QRect extent() const override; /** * Colorize mask has its own "projection", so it should report it * to the parent layer using non-dependent-extent property */ QRect nonDependentExtent() const override; void setSectionModelProperties(const KisBaseNode::PropertyList &properties) override; KisBaseNode::PropertyList sectionModelProperties() const override; KeyStrokeColors keyStrokesColors() const; void setKeyStrokesColors(KeyStrokeColors colors); void removeKeyStroke(const KoColor &color); QVector allPaintDevices() const; void resetCache(); void setUseEdgeDetection(bool value); bool useEdgeDetection() const; void setEdgeDetectionSize(qreal value); qreal edgeDetectionSize() const; void setFuzzyRadius(qreal value); qreal fuzzyRadius() const; void setCleanUpAmount(qreal value); qreal cleanUpAmount() const; void setLimitToDeviceBounds(bool value); bool limitToDeviceBounds() const; void testingAddKeyStroke(KisPaintDeviceSP dev, const KoColor &color, bool isTransparent = false); void testingRegenerateMask(); KisPaintDeviceSP testingFilteredSource() const; QList fetchKeyStrokesDirect() const; void setKeyStrokesDirect(const QList &strokes); qint32 x() const override; qint32 y() const override; void setX(qint32 x) override; void setY(qint32 y) override; KisPaintDeviceList getLodCapableDevices() const override; void regeneratePrefilteredDeviceIfNeeded(); private Q_SLOTS: void slotUpdateRegenerateFilling(bool prefilterOnly = false); void slotRegenerationFinished(bool prefilterOnly); void slotRegenerationCancelled(); void slotUpdateOnDirtyParent(); void slotRecalculatePrefilteredImage(); Q_SIGNALS: void sigKeyStrokesListChanged(); void sigUpdateOnDirtyParent() const; private: // NOTE: please access this methods using model properties only! bool needsUpdate() const; void setNeedsUpdate(bool value); bool showColoring() const; void setShowColoring(bool value); bool showKeyStrokes() const; void setShowKeyStrokes(bool value); private: void rerenderFakePaintDevice(); KisImageSP fetchImage() const; void moveAllInternalDevices(const QPoint &diff); template QRect calculateMaskBounds(DeviceMetricPolicy policy) const; friend struct SetKeyStrokesColorSpaceCommand; friend struct KeyStrokeAddRemoveCommand; friend struct SetKeyStrokeColorsCommand; private: struct Private; const QScopedPointer m_d; }; #endif /* __KIS_COLORIZE_MASK_H */ diff --git a/libs/image/processing/kis_assign_profile_processing_visitor.cpp b/libs/image/processing/kis_assign_profile_processing_visitor.cpp new file mode 100644 index 0000000000..3f9713aae4 --- /dev/null +++ b/libs/image/processing/kis_assign_profile_processing_visitor.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2019 Dmitry Kazakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "kis_assign_profile_processing_visitor.h" + +#include "kis_external_layer_iface.h" + +#include "kis_paint_device.h" +#include "kis_transaction.h" +#include "kis_undo_adapter.h" +#include "kis_transform_mask.h" +#include "lazybrush/kis_colorize_mask.h" + +#include +#include "kis_projection_leaf.h" +#include "kis_paint_layer.h" +#include "kis_time_range.h" +#include + + +KisAssignProfileProcessingVisitor::KisAssignProfileProcessingVisitor(const KoColorSpace *srcColorSpace, + const KoColorSpace *dstColorSpace) + : m_srcColorSpace(srcColorSpace) + , m_dstColorSpace(dstColorSpace) + +{ +} + + +void KisAssignProfileProcessingVisitor::visitExternalLayer(KisExternalLayer *layer, KisUndoAdapter *undoAdapter) +{ + Q_UNUSED(layer); + Q_UNUSED(undoAdapter); +} + +void KisAssignProfileProcessingVisitor::visitNodeWithPaintDevice(KisNode *node, KisUndoAdapter *undoAdapter) +{ + if (!node->projectionLeaf()->isLayer()) return; + if (*m_dstColorSpace == *node->colorSpace()) return; + + QSet paintDevices; + paintDevices.insert(node->paintDevice()); + paintDevices.insert(node->original()); + paintDevices.insert(node->projection()); + + KUndo2Command *parentConversionCommand = new KUndo2Command(); + + Q_FOREACH (KisPaintDeviceSP dev, paintDevices) { + if (dev->colorSpace()->colorModelId() == m_srcColorSpace->colorModelId()) { + dev->setProfile(m_dstColorSpace->profile(), parentConversionCommand); + } + } + + undoAdapter->addCommand(parentConversionCommand); + node->invalidateFrames(KisTimeRange::infinite(0), node->extent()); +} + +void KisAssignProfileProcessingVisitor::visit(KisTransformMask *mask, KisUndoAdapter *undoAdapter) +{ + mask->threadSafeForceStaticImageUpdate(); + KisSimpleProcessingVisitor::visit(mask, undoAdapter); +} + +void KisAssignProfileProcessingVisitor::visitColorizeMask(KisColorizeMask *mask, KisUndoAdapter *undoAdapter) +{ + if (mask->colorSpace()->colorModelId() == m_srcColorSpace->colorModelId()) { + KUndo2Command *parentConversionCommand = new KUndo2Command(); + mask->setProfile(m_dstColorSpace->profile(), parentConversionCommand); + undoAdapter->addCommand(parentConversionCommand); + + mask->invalidateFrames(KisTimeRange::infinite(0), mask->extent()); + } + KisSimpleProcessingVisitor::visit(mask, undoAdapter); +} diff --git a/libs/image/processing/kis_assign_profile_processing_visitor.h b/libs/image/processing/kis_assign_profile_processing_visitor.h new file mode 100644 index 0000000000..20c16b7a0c --- /dev/null +++ b/libs/image/processing/kis_assign_profile_processing_visitor.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2019 Dmitry Kazakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __KIS_ASSIGN_PROFILE_PROCESSING_VISITOR_H +#define __KIS_ASSIGN_PROFILE_PROCESSING_VISITOR_H + +#include "kis_simple_processing_visitor.h" +#include +#include "kis_types.h" + +class KoColorSpace; + +class KRITAIMAGE_EXPORT KisAssignProfileProcessingVisitor : public KisSimpleProcessingVisitor +{ +public: + KisAssignProfileProcessingVisitor(const KoColorSpace *srcColorSpace, + const KoColorSpace *dstColorSpace); + +private: + void visitNodeWithPaintDevice(KisNode *node, KisUndoAdapter *undoAdapter) override; + void visitExternalLayer(KisExternalLayer *layer, KisUndoAdapter *undoAdapter) override; + +public: + + void visit(KisTransformMask *mask, KisUndoAdapter *undoAdapter) override; + void visitColorizeMask(KisColorizeMask *mask, KisUndoAdapter *undoAdapter) override; + using KisSimpleProcessingVisitor::visit; + +private: + const KoColorSpace *m_srcColorSpace; + const KoColorSpace *m_dstColorSpace; +}; + +#endif /* __KIS_ASSIGN_PROFILE_PROCESSING_VISITOR_H */ diff --git a/libs/image/tests/CMakeLists.txt b/libs/image/tests/CMakeLists.txt index a92e83148e..0fc7893122 100644 --- a/libs/image/tests/CMakeLists.txt +++ b/libs/image/tests/CMakeLists.txt @@ -1,156 +1,155 @@ # cmake in some versions for some not yet known reasons fails to run automoc # on random targets (changing target names already has an effect) # As temporary workaround skipping build of tests on these versions for now # See https://mail.kde.org/pipermail/kde-buildsystem/2015-June/010819.html # extend range of affected cmake versions as needed if(NOT ${CMAKE_VERSION} VERSION_LESS 3.1.3 AND NOT ${CMAKE_VERSION} VERSION_GREATER 3.2.3) message(WARNING "Skipping krita/image/tests, CMake in at least versions 3.1.3 - 3.2.3 seems to have a problem with automoc. \n(FRIENDLY REMINDER: PLEASE DON'T BREAK THE TESTS!)") set (HAVE_FAILING_CMAKE TRUE) else() set (HAVE_FAILING_CMAKE FALSE) endif() include_directories( ${CMAKE_BINARY_DIR}/libs/image/ ${CMAKE_SOURCE_DIR}/libs/image/ ${CMAKE_SOURCE_DIR}/libs/image/brushengine ${CMAKE_SOURCE_DIR}/libs/image/tiles3 ${CMAKE_SOURCE_DIR}/libs/image/tiles3/swap ${CMAKE_SOURCE_DIR}/sdk/tests ) include_Directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ) if(HAVE_VC) include_directories(${Vc_INCLUDE_DIR}) endif() include(ECMAddTests) include(KritaAddBrokenUnitTest) macro_add_unittest_definitions() set(KisRandomGeneratorDemoSources kis_random_generator_demo.cpp kimageframe.cpp) ki18n_wrap_ui(KisRandomGeneratorDemoSources kis_random_generator_demo.ui) add_executable(KisRandomGeneratorDemo ${KisRandomGeneratorDemoSources}) target_link_libraries(KisRandomGeneratorDemo kritaimage) ecm_mark_as_test(KisRandomGeneratorDemo) ecm_add_tests( kis_base_node_test.cpp kis_fast_math_test.cpp kis_node_test.cpp kis_node_facade_test.cpp kis_fixed_paint_device_test.cpp kis_layer_test.cpp kis_effect_mask_test.cpp kis_iterator_test.cpp kis_painter_test.cpp kis_selection_test.cpp kis_count_visitor_test.cpp kis_projection_test.cpp kis_properties_configuration_test.cpp kis_transaction_test.cpp kis_pixel_selection_test.cpp kis_group_layer_test.cpp kis_paint_layer_test.cpp kis_adjustment_layer_test.cpp kis_annotation_test.cpp - kis_change_profile_visitor_test.cpp kis_clone_layer_test.cpp kis_convolution_painter_test.cpp kis_crop_processing_visitor_test.cpp kis_processing_applicator_test.cpp kis_datamanager_test.cpp kis_fill_painter_test.cpp kis_filter_configuration_test.cpp kis_filter_test.cpp kis_filter_processing_information_test.cpp kis_filter_registry_test.cpp kis_filter_strategy_test.cpp kis_gradient_painter_test.cpp kis_image_commands_test.cpp kis_image_test.cpp kis_image_signal_router_test.cpp kis_iterators_ng_test.cpp kis_iterator_benchmark.cpp kis_updater_context_test.cpp kis_simple_update_queue_test.cpp kis_stroke_test.cpp kis_simple_stroke_strategy_test.cpp kis_stroke_strategy_undo_command_based_test.cpp kis_strokes_queue_test.cpp kis_mask_test.cpp kis_math_toolbox_test.cpp kis_name_server_test.cpp kis_node_commands_test.cpp kis_node_graph_listener_test.cpp kis_node_visitor_test.cpp kis_paint_information_test.cpp kis_distance_information_test.cpp kis_paintop_test.cpp kis_pattern_test.cpp kis_selection_mask_test.cpp kis_shared_ptr_test.cpp kis_bsplines_test.cpp kis_warp_transform_worker_test.cpp kis_liquify_transform_worker_test.cpp kis_transparency_mask_test.cpp kis_types_test.cpp kis_vec_test.cpp kis_filter_config_widget_test.cpp kis_mask_generator_test.cpp kis_cubic_curve_test.cpp kis_fixed_point_maths_test.cpp kis_node_query_path_test.cpp kis_filter_weights_buffer_test.cpp kis_filter_weights_applicator_test.cpp kis_fill_interval_test.cpp kis_fill_interval_map_test.cpp kis_scanline_fill_test.cpp kis_psd_layer_style_test.cpp kis_layer_style_projection_plane_test.cpp kis_lod_capable_layer_offset_test.cpp kis_algebra_2d_test.cpp kis_marker_painter_test.cpp kis_lazy_brush_test.cpp kis_colorize_mask_test.cpp kis_mask_similarity_test.cpp KisMaskGeneratorTest.cpp kis_layer_style_filter_environment_test.cpp kis_asl_parser_test.cpp KisPerStrokeRandomSourceTest.cpp KisWatershedWorkerTest.cpp kis_dom_utils_test.cpp kis_transform_worker_test.cpp kis_perspective_transform_worker_test.cpp kis_cs_conversion_test.cpp kis_processings_test.cpp kis_projection_leaf_test.cpp kis_histogram_test.cpp kis_onion_skin_compositor_test.cpp kis_paint_device_test.cpp kis_queues_progress_updater_test.cpp kis_image_animation_interface_test.cpp kis_walkers_test.cpp kis_async_merger_test.cpp kis_cage_transform_worker_test.cpp kis_random_generator_test.cpp kis_keyframing_test.cpp kis_filter_mask_test.cpp LINK_LIBRARIES kritaimage Qt5::Test NAME_PREFIX "libs-image-" ) krita_add_broken_unit_tests( kis_transform_mask_test.cpp kis_layer_styles_test.cpp kis_update_scheduler_test.cpp LINK_LIBRARIES kritaimage Qt5::Test NAME_PREFIX "libs-image-" ) diff --git a/libs/image/tests/kis_change_profile_visitor_test.cpp b/libs/image/tests/kis_change_profile_visitor_test.cpp deleted file mode 100644 index 2bff4289bc..0000000000 --- a/libs/image/tests/kis_change_profile_visitor_test.cpp +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include "kis_change_profile_visitor_test.h" - -#include -#include "kis_change_profile_visitor.h" -#include "KoColorSpace.h" -#include "KoColorSpaceRegistry.h" -#include "kis_image.h" - -void KisChangeProfileVisitorTest::testCreation() -{ - const KoColorSpace * a = KoColorSpaceRegistry::instance()->rgb8(); - const KoColorSpace * b = KoColorSpaceRegistry::instance()->rgb8(); - KisChangeProfileVisitor test(a, b); -} - - -QTEST_MAIN(KisChangeProfileVisitorTest) diff --git a/libs/image/tests/kis_change_profile_visitor_test.h b/libs/image/tests/kis_change_profile_visitor_test.h deleted file mode 100644 index a8c9d9fe3a..0000000000 --- a/libs/image/tests/kis_change_profile_visitor_test.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef KIS_CHANGE_PROFILE_VISITOR_TEST_H -#define KIS_CHANGE_PROFILE_VISITOR_TEST_H - -#include - -class KisChangeProfileVisitorTest : public QObject -{ - Q_OBJECT -private Q_SLOTS: - - void testCreation(); - -}; - -#endif diff --git a/libs/image/tests/kis_image_test.cpp b/libs/image/tests/kis_image_test.cpp index d935a72840..d9145a3696 100644 --- a/libs/image/tests/kis_image_test.cpp +++ b/libs/image/tests/kis_image_test.cpp @@ -1,1238 +1,1287 @@ /* * Copyright (c) 2005 Adrian Page * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_image_test.h" #include #include #include #include #include "filter/kis_filter.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "kis_image.h" #include "kis_paint_layer.h" #include "kis_group_layer.h" #include "kis_adjustment_layer.h" #include "kis_selection.h" #include #include #include "kis_keyframe_channel.h" #include "kis_selection_mask.h" #include "kis_layer_utils.h" #include "kis_annotation.h" #include "KisProofingConfiguration.h" #include "kis_undo_stores.h" #define IMAGE_WIDTH 128 #define IMAGE_HEIGHT 128 void KisImageTest::layerTests() { KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests"); QVERIFY(image->rootLayer() != 0); QVERIFY(image->rootLayer()->firstChild() == 0); KisLayerSP layer = new KisPaintLayer(image, "layer 1", OPACITY_OPAQUE_U8); image->addNode(layer); QVERIFY(image->rootLayer()->firstChild()->objectName() == layer->objectName()); } void KisImageTest::benchmarkCreation() { const QRect imageRect(0,0,3000,2000); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); QList images; QList stores; QBENCHMARK { for (int i = 0; i < 10; i++) { stores << new KisSurrogateUndoStore(); } for (int i = 0; i < 10; i++) { KisImageSP image = new KisImage(stores.takeLast(), imageRect.width(), imageRect.height(), cs, "test image"); images << image; } } } #include "testutil.h" #include "kis_stroke_strategy.h" #include class ForbiddenLodStrokeStrategy : public KisStrokeStrategy { public: ForbiddenLodStrokeStrategy(std::function lodCallback) : m_lodCallback(lodCallback) { } KisStrokeStrategy* createLodClone(int levelOfDetail) override { Q_UNUSED(levelOfDetail); m_lodCallback(); return 0; } private: std::function m_lodCallback; }; void notifyVar(bool *value) { *value = true; } void KisImageTest::testBlockLevelOfDetail() { TestUtil::MaskParent p; QCOMPARE(p.image->currentLevelOfDetail(), 0); p.image->setDesiredLevelOfDetail(1); p.image->waitForDone(); QCOMPARE(p.image->currentLevelOfDetail(), 0); { bool lodCreated = false; KisStrokeId id = p.image->startStroke( new ForbiddenLodStrokeStrategy( std::bind(¬ifyVar, &lodCreated))); p.image->endStroke(id); p.image->waitForDone(); QVERIFY(lodCreated); } p.image->setLevelOfDetailBlocked(true); { bool lodCreated = false; KisStrokeId id = p.image->startStroke( new ForbiddenLodStrokeStrategy( std::bind(¬ifyVar, &lodCreated))); p.image->endStroke(id); p.image->waitForDone(); QVERIFY(!lodCreated); } p.image->setLevelOfDetailBlocked(false); p.image->setDesiredLevelOfDetail(1); { bool lodCreated = false; KisStrokeId id = p.image->startStroke( new ForbiddenLodStrokeStrategy( std::bind(¬ifyVar, &lodCreated))); p.image->endStroke(id); p.image->waitForDone(); QVERIFY(lodCreated); } } void KisImageTest::testConvertImageColorSpace() { const KoColorSpace *cs8 = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 1000, 1000, cs8, "stest"); KisPaintDeviceSP device1 = new KisPaintDevice(cs8); KisLayerSP paint1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8, device1); KisFilterSP filter = KisFilterRegistry::instance()->value("blur"); Q_ASSERT(filter); KisFilterConfigurationSP configuration = filter->defaultConfiguration(); Q_ASSERT(configuration); KisLayerSP blur1 = new KisAdjustmentLayer(image, "blur1", configuration, 0); image->addNode(paint1, image->root()); image->addNode(blur1, image->root()); image->refreshGraph(); const KoColorSpace *cs16 = KoColorSpaceRegistry::instance()->rgb16(); image->convertImageColorSpace(cs16, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); image->waitForDone(); QVERIFY(*cs16 == *image->colorSpace()); QVERIFY(*cs16 == *image->root()->colorSpace()); QVERIFY(*cs16 == *paint1->colorSpace()); QVERIFY(*cs16 == *blur1->colorSpace()); QVERIFY(!image->root()->compositeOp()); QVERIFY(*cs16 == *paint1->compositeOp()->colorSpace()); QVERIFY(*cs16 == *blur1->compositeOp()->colorSpace()); image->refreshGraph(); } +void KisImageTest::testAssignImageProfile() +{ + const KoColorSpace *rgb8 = KoColorSpaceRegistry::instance()->rgb8(); + const KoColorSpace *gray8 = KoColorSpaceRegistry::instance()->graya8(); + KisImageSP image = new KisImage(0, 1000, 1000, rgb8, "stest"); + + KisPaintDeviceSP device1 = new KisPaintDevice(rgb8); + KisLayerSP paint1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8, device1); + + KisPaintDeviceSP device2 = new KisPaintDevice(gray8); + KisLayerSP paint2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8, device2); + + + KisFilterSP filter = KisFilterRegistry::instance()->value("blur"); + Q_ASSERT(filter); + KisFilterConfigurationSP configuration = filter->defaultConfiguration(); + Q_ASSERT(configuration); + + KisLayerSP blur1 = new KisAdjustmentLayer(image, "blur1", configuration, 0); + + image->addNode(paint1, image->root()); + image->addNode(paint2, image->root()); + image->addNode(blur1, image->root()); + + QCOMPARE(*image->colorSpace(), *rgb8); + QCOMPARE(*image->colorSpace()->profile(), *KoColorSpaceRegistry::instance()->p709SRGBProfile()); + + QCOMPARE(*paint1->colorSpace(), *rgb8); + QCOMPARE(*paint1->colorSpace()->profile(), *KoColorSpaceRegistry::instance()->p709SRGBProfile()); + + QCOMPARE(*paint2->colorSpace(), *gray8); + + QCOMPARE(*blur1->colorSpace(), *rgb8); + QCOMPARE(*blur1->colorSpace()->profile(), *KoColorSpaceRegistry::instance()->p709SRGBProfile()); + + + image->assignImageProfile(KoColorSpaceRegistry::instance()->p2020G10Profile()); + image->waitForDone(); + + QVERIFY(*image->colorSpace() != *rgb8); + QCOMPARE(*image->colorSpace()->profile(), *KoColorSpaceRegistry::instance()->p2020G10Profile()); + + QVERIFY(*paint1->colorSpace() != *rgb8); + QCOMPARE(*paint1->colorSpace()->profile(), *KoColorSpaceRegistry::instance()->p2020G10Profile()); + + QCOMPARE(*paint2->colorSpace(), *gray8); + + QVERIFY(*blur1->colorSpace() != *rgb8); + QCOMPARE(*blur1->colorSpace()->profile(), *KoColorSpaceRegistry::instance()->p2020G10Profile()); +} + void KisImageTest::testGlobalSelection() { const KoColorSpace *cs8 = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 1000, 1000, cs8, "stest"); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 0U); KisSelectionSP selection1 = new KisSelection(new KisDefaultBounds(image)); KisSelectionSP selection2 = new KisSelection(new KisDefaultBounds(image)); image->setGlobalSelection(selection1); QCOMPARE(image->globalSelection(), selection1); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); image->setGlobalSelection(selection2); QCOMPARE(image->globalSelection(), selection2); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); image->deselectGlobalSelection(); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), true); QCOMPARE(image->root()->childCount(), 0U); image->reselectGlobalSelection(); QCOMPARE(image->globalSelection(), selection2); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); // mixed deselecting/setting/reselecting image->deselectGlobalSelection(); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), true); QCOMPARE(image->root()->childCount(), 0U); image->setGlobalSelection(selection1); QCOMPARE(image->globalSelection(), selection1); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); } void KisImageTest::testCloneImage() { KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests"); QVERIFY(image->rootLayer() != 0); QVERIFY(image->rootLayer()->firstChild() == 0); KisAnnotationSP annotation = new KisAnnotation("mytype", "mydescription", QByteArray()); image->addAnnotation(annotation); QVERIFY(image->annotation("mytype")); KisProofingConfigurationSP proofing = toQShared(new KisProofingConfiguration()); image->setProofingConfiguration(proofing); QVERIFY(image->proofingConfiguration()); const KoColor defaultColor(Qt::green, image->colorSpace()); image->setDefaultProjectionColor(defaultColor); QCOMPARE(image->defaultProjectionColor(), defaultColor); KisLayerSP layer = new KisPaintLayer(image, "layer1", OPACITY_OPAQUE_U8); image->addNode(layer); KisLayerSP layer2 = new KisPaintLayer(image, "layer2", OPACITY_OPAQUE_U8); image->addNode(layer2); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); QVERIFY(TestUtil::findNode(image->root(), "layer1")); QVERIFY(TestUtil::findNode(image->root(), "layer2")); QUuid uuid1 = layer->uuid(); QUuid uuid2 = layer2->uuid(); { KisImageSP newImage = image->clone(); KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1"); KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2"); QVERIFY(newLayer1); QVERIFY(newLayer2); QVERIFY(newLayer1->uuid() != uuid1); QVERIFY(newLayer2->uuid() != uuid2); KisAnnotationSP newAnnotation = newImage->annotation("mytype"); QVERIFY(newAnnotation); QVERIFY(newAnnotation != annotation); KisProofingConfigurationSP newProofing = newImage->proofingConfiguration(); QVERIFY(newProofing); QVERIFY(newProofing != proofing); QCOMPARE(newImage->defaultProjectionColor(), defaultColor); } { KisImageSP newImage = image->clone(true); KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1"); KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2"); QVERIFY(newLayer1); QVERIFY(newLayer2); QVERIFY(newLayer1->uuid() == uuid1); QVERIFY(newLayer2->uuid() == uuid2); } } void KisImageTest::testLayerComposition() { KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests"); QVERIFY(image->rootLayer() != 0); QVERIFY(image->rootLayer()->firstChild() == 0); KisLayerSP layer = new KisPaintLayer(image, "layer1", OPACITY_OPAQUE_U8); image->addNode(layer); KisLayerSP layer2 = new KisPaintLayer(image, "layer2", OPACITY_OPAQUE_U8); image->addNode(layer2); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); KisLayerComposition comp(image, "comp 1"); comp.store(); layer2->setVisible(false); QVERIFY(layer->visible()); QVERIFY(!layer2->visible()); KisLayerComposition comp2(image, "comp 2"); comp2.store(); KisLayerCompositionSP comp3 = toQShared(new KisLayerComposition(image, "comp 3")); comp3->store(); image->addComposition(comp3); comp.apply(); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); comp2.apply(); QVERIFY(layer->visible()); QVERIFY(!layer2->visible()); comp.apply(); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); KisImageSP newImage = image->clone(); KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1"); KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2"); QVERIFY(newLayer1); QVERIFY(newLayer2); QVERIFY(newLayer1->visible()); QVERIFY(newLayer2->visible()); KisLayerComposition newComp1(comp, newImage); newComp1.apply(); QVERIFY(newLayer1->visible()); QVERIFY(newLayer2->visible()); KisLayerComposition newComp2(comp2, newImage); newComp2.apply(); QVERIFY(newLayer1->visible()); QVERIFY(!newLayer2->visible()); newComp1.apply(); QVERIFY(newLayer1->visible()); QVERIFY(newLayer2->visible()); QVERIFY(!newImage->compositions().isEmpty()); KisLayerCompositionSP newComp3 = newImage->compositions().first(); newComp3->apply(); QVERIFY(newLayer1->visible()); QVERIFY(!newLayer2->visible()); } #include "kis_transparency_mask.h" #include "kis_psd_layer_style.h" struct FlattenTestImage { FlattenTestImage() : refRect(0,0,512,512) , p(refRect) { image = p.image; undoStore = p.undoStore; layer1 = p.layer; layer5 = new KisPaintLayer(p.image, "paint5", 0.4 * OPACITY_OPAQUE_U8); layer5->disableAlphaChannel(true); layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8); tmask = new KisTransparencyMask(); // check channel flags // make addition composite op group1 = new KisGroupLayer(p.image, "group1", OPACITY_OPAQUE_U8); layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8); layer4 = new KisPaintLayer(p.image, "paint4", OPACITY_OPAQUE_U8); layer6 = new KisPaintLayer(p.image, "paint6", OPACITY_OPAQUE_U8); layer7 = new KisPaintLayer(p.image, "paint7", OPACITY_OPAQUE_U8); layer8 = new KisPaintLayer(p.image, "paint8", OPACITY_OPAQUE_U8); layer7->setCompositeOpId(COMPOSITE_ADD); layer8->setCompositeOpId(COMPOSITE_ADD); QRect rect1(100, 100, 100, 100); QRect rect2(150, 150, 150, 150); QRect tmaskRect(200,200,100,100); QRect rect3(400, 100, 100, 100); QRect rect4(500, 100, 100, 100); QRect rect5(50, 50, 100, 100); QRect rect6(50, 250, 100, 100); QRect rect7(50, 350, 50, 50); QRect rect8(50, 400, 50, 50); layer1->paintDevice()->fill(rect1, KoColor(Qt::red, p.image->colorSpace())); layer2->paintDevice()->fill(rect2, KoColor(Qt::green, p.image->colorSpace())); tmask->testingInitSelection(tmaskRect, layer2); layer3->paintDevice()->fill(rect3, KoColor(Qt::blue, p.image->colorSpace())); layer4->paintDevice()->fill(rect4, KoColor(Qt::yellow, p.image->colorSpace())); layer5->paintDevice()->fill(rect5, KoColor(Qt::green, p.image->colorSpace())); layer6->paintDevice()->fill(rect6, KoColor(Qt::cyan, p.image->colorSpace())); layer7->paintDevice()->fill(rect7, KoColor(Qt::red, p.image->colorSpace())); layer8->paintDevice()->fill(rect8, KoColor(Qt::green, p.image->colorSpace())); KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->dropShadow()->setEffectEnabled(true); style->dropShadow()->setDistance(10.0); style->dropShadow()->setSpread(80.0); style->dropShadow()->setSize(10); style->dropShadow()->setNoise(0); style->dropShadow()->setKnocksOut(false); style->dropShadow()->setOpacity(80.0); layer2->setLayerStyle(style); layer2->setCompositeOpId(COMPOSITE_ADD); group1->setCompositeOpId(COMPOSITE_ADD); p.image->addNode(layer5); p.image->addNode(layer2); p.image->addNode(tmask, layer2); p.image->addNode(group1); p.image->addNode(layer3, group1); p.image->addNode(layer4, group1); p.image->addNode(layer6); p.image->addNode(layer7); p.image->addNode(layer8); p.image->initialRefreshGraph(); // dbgKrita << ppVar(layer1->exactBounds()); // dbgKrita << ppVar(layer5->exactBounds()); // dbgKrita << ppVar(layer2->exactBounds()); // dbgKrita << ppVar(group1->exactBounds()); // dbgKrita << ppVar(layer3->exactBounds()); // dbgKrita << ppVar(layer4->exactBounds()); TestUtil::ReferenceImageChecker chk("flatten", "imagetest"); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); } QRect refRect; TestUtil::MaskParent p; KisImageSP image; KisSurrogateUndoStore *undoStore; KisPaintLayerSP layer1; KisPaintLayerSP layer2; KisTransparencyMaskSP tmask; KisGroupLayerSP group1; KisPaintLayerSP layer3; KisPaintLayerSP layer4; KisPaintLayerSP layer5; KisPaintLayerSP layer6; KisPaintLayerSP layer7; KisPaintLayerSP layer8; }; template KisLayerSP flattenLayerHelper(ContainerTest &p, KisLayerSP layer, bool nothingHappens = false) { QSignalSpy spy(p.image.data(), SIGNAL(sigNodeAddedAsync(KisNodeSP))); //p.image->flattenLayer(layer); KisLayerUtils::flattenLayer(p.image, layer); p.image->waitForDone(); if (nothingHappens) { Q_ASSERT(!spy.count()); return layer; } Q_ASSERT(spy.count() == 1); QList arguments = spy.takeFirst(); KisNodeSP newNode = arguments.first().value(); KisLayerSP newLayer = qobject_cast(newNode.data()); return newLayer; } void KisImageTest::testFlattenLayer() { FlattenTestImage p; TestUtil::ReferenceImageChecker chk("flatten", "imagetest"); { QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); KisLayerSP newLayer = flattenLayerHelper(p, p.layer2); //KisLayerSP newLayer = p.image->flattenLayer(p.layer2); //p.image->waitForDone(); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer2_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); } { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); KisLayerSP newLayer = flattenLayerHelper(p, p.group1); //KisLayerSP newLayer = p.image->flattenLayer(p.group1); //p.image->waitForDone(); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "02_group1_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_ADD); QCOMPARE(newLayer->exactBounds(), QRect(400, 100, 200, 100)); } { QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer5->alphaChannelDisabled(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.layer5, true); //KisLayerSP newLayer = p.image->flattenLayer(p.layer5); //p.image->waitForDone(); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "03_layer5_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 50, 100, 100)); QCOMPARE(newLayer->alphaChannelDisabled(), true); } } #include template KisLayerSP mergeHelper(ContainerTest &p, KisLayerSP layer) { KisNodeSP parent = layer->parent(); const int newIndex = parent->index(layer) - 1; p.image->mergeDown(layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); //KisLayerUtils::mergeDown(p.image, layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); p.image->waitForDone(); KisLayerSP newLayer = qobject_cast(parent->at(newIndex).data()); return newLayer; } void KisImageTest::testMergeDown() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergedown_simple", "imagetest"); { QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer5->alphaChannelDisabled(), true); KisLayerSP newLayer = mergeHelper(p, p.layer5); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer5_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->alphaChannelDisabled(), false); } { QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer2->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer2); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "02_layer2_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(100, 100, 213, 217)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.group1->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.group1); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "03_group1_mergedown_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(100, 100, 500, 217)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationInheritsAlpha() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergedown_dst_inheritsalpha", "imagetest"); { QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer2->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer2); // WARN: this check is suspicious! QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_proj_merged_layer2_over_layer5_IA")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer2_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50,50, 263, 267)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationCustomCompositeOp() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergedown_dst_customop", "imagetest"); { QCOMPARE(p.layer6->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer6->alphaChannelDisabled(), false); QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.group1->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer6); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer6_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationSameCompositeOpLayerStyle() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergedown_sameop_ls", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.group1->alphaChannelDisabled(), false); QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer2->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.group1); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_group1_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(197, 100, 403, 217)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationSameCompositeOp() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergedown_sameop_fastpath", "imagetest"); { QCOMPARE(p.layer8->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer8->alphaChannelDisabled(), false); QCOMPARE(p.layer7->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer7->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer8); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_ADD); QCOMPARE(newLayer->exactBounds(), QRect(50, 350, 50, 100)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } #include "kis_image_animation_interface.h" void KisImageTest::testMergeDownMultipleFrames() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergedown_simple", "imagetest"); QSet initialFrames; { KisLayerSP l = p.layer5; l->enableAnimation(); KisKeyframeChannel *channel = l->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); channel->addKeyframe(10); channel->addKeyframe(20); channel->addKeyframe(30); QCOMPARE(channel->keyframeCount(), 4); initialFrames = KisLayerUtils::fetchLayerFramesRecursive(l); QCOMPARE(initialFrames.size(), 4); } { QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer5->alphaChannelDisabled(), true); KisLayerSP newLayer = mergeHelper(p, p.layer5); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer5_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->alphaChannelDisabled(), false); QVERIFY(newLayer->isAnimated()); QSet newFrames = KisLayerUtils::fetchLayerFramesRecursive(newLayer); QCOMPARE(newFrames, initialFrames); foreach (int frame, newFrames) { KisImageAnimationInterface *interface = p.image->animationInterface(); int savedSwitchedTime = 0; interface->saveAndResetCurrentTime(frame, &savedSwitchedTime); QCOMPARE(newLayer->exactBounds(), QRect(100,100,100,100)); interface->restoreCurrentTime(&savedSwitchedTime); } p.undoStore->undo(); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); } } template KisNodeSP mergeMultipleHelper(ContainerTest &p, QList selectedNodes, KisNodeSP putAfter) { QSignalSpy spy(p.image.data(), SIGNAL(sigNodeAddedAsync(KisNodeSP))); p.image->mergeMultipleLayers(selectedNodes, putAfter); //KisLayerUtils::mergeMultipleLayers(p.image, selectedNodes, putAfter); p.image->waitForDone(); Q_ASSERT(spy.count() == 1); QList arguments = spy.takeFirst(); KisNodeSP newNode = arguments.first().value(); return newNode; } void KisImageTest::testMergeMultiple() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergemultiple", "imagetest"); { QList selectedNodes; selectedNodes << p.layer2 << p.group1 << p.layer6; { KisNodeSP newLayer = mergeMultipleHelper(p, selectedNodes, 0); //KisNodeSP newLayer = p.image->mergeMultipleLayers(selectedNodes, 0); //p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250)); } } p.p.undoStore->undo(); p.image->waitForDone(); // Test reversed order, the result must be the same { QList selectedNodes; selectedNodes << p.layer6 << p.group1 << p.layer2; { KisNodeSP newLayer = mergeMultipleHelper(p, selectedNodes, 0); //KisNodeSP newLayer = p.image->mergeMultipleLayers(selectedNodes, 0); //p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250)); } } } void testMergeCrossColorSpaceImpl(bool useProjectionColorSpace, bool swapSpaces) { TestUtil::MaskParent p; KisPaintLayerSP layer1; KisPaintLayerSP layer2; KisPaintLayerSP layer3; const KoColorSpace *cs2 = useProjectionColorSpace ? p.image->colorSpace() : KoColorSpaceRegistry::instance()->lab16(); const KoColorSpace *cs3 = KoColorSpaceRegistry::instance()->rgb16(); if (swapSpaces) { std::swap(cs2, cs3); } dbgKrita << "Testing testMergeCrossColorSpaceImpl:"; dbgKrita << " " << ppVar(cs2); dbgKrita << " " << ppVar(cs3); layer1 = p.layer; layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8, cs2); layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8, cs3); QRect rect1(100, 100, 100, 100); QRect rect2(150, 150, 150, 150); QRect rect3(250, 250, 200, 200); layer1->paintDevice()->fill(rect1, KoColor(Qt::red, layer1->colorSpace())); layer2->paintDevice()->fill(rect2, KoColor(Qt::green, layer2->colorSpace())); layer3->paintDevice()->fill(rect3, KoColor(Qt::blue, layer3->colorSpace())); p.image->addNode(layer2); p.image->addNode(layer3); p.image->initialRefreshGraph(); { KisLayerSP newLayer = mergeHelper(p, layer3); QCOMPARE(newLayer->colorSpace(), p.image->colorSpace()); p.undoStore->undo(); p.image->waitForDone(); } { layer2->disableAlphaChannel(true); KisLayerSP newLayer = mergeHelper(p, layer3); QCOMPARE(newLayer->colorSpace(), p.image->colorSpace()); p.undoStore->undo(); p.image->waitForDone(); } } void KisImageTest::testMergeCrossColorSpace() { testMergeCrossColorSpaceImpl(true, false); testMergeCrossColorSpaceImpl(true, true); testMergeCrossColorSpaceImpl(false, false); testMergeCrossColorSpaceImpl(false, true); } void KisImageTest::testMergeSelectionMasks() { TestUtil::MaskParent p; QRect rect1(100, 100, 100, 100); QRect rect2(150, 150, 150, 150); QRect rect3(50, 50, 100, 100); KisPaintLayerSP layer1 = p.layer; layer1->paintDevice()->fill(rect1, KoColor(Qt::red, layer1->colorSpace())); p.image->initialRefreshGraph(); KisSelectionSP sel = new KisSelection(layer1->paintDevice()->defaultBounds()); sel->pixelSelection()->select(rect2, MAX_SELECTED); KisSelectionMaskSP mask1 = new KisSelectionMask(p.image); mask1->initSelection(sel, layer1); p.image->addNode(mask1, layer1); QVERIFY(!layer1->selection()); mask1->setActive(true); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(150,150,150,150)); sel->pixelSelection()->select(rect3, MAX_SELECTED); KisSelectionMaskSP mask2 = new KisSelectionMask(p.image); mask2->initSelection(sel, layer1); p.image->addNode(mask2, layer1); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(150,150,150,150)); mask2->setActive(true); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(50,50,250,250)); QList selectedNodes; selectedNodes << mask2 << mask1; { KisNodeSP newLayer = mergeMultipleHelper(p, selectedNodes, 0); QCOMPARE(newLayer->parent(), KisNodeSP(layer1)); QCOMPARE((int)layer1->childCount(), 1); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(50,50,250,250)); } } void KisImageTest::testFlattenImage() { FlattenTestImage p; KisImageSP image = p.image; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); { KisLayerUtils::flattenImage(p.image, 0); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); p.undoStore->undo(); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); } { KisLayerUtils::flattenImage(p.image, p.layer5); // flatten with active layer just under the root (not inside any group) p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); p.undoStore->undo(); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); } { KisLayerUtils::flattenImage(p.image, p.layer2); // flatten with active layer just under the root (not inside any group), but with a mask p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); p.undoStore->undo(); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); } { KisLayerUtils::flattenImage(p.image, p.layer3); // flatten with active layer inside of a group p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); p.undoStore->undo(); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); } } struct FlattenPassThroughTestImage { FlattenPassThroughTestImage() : refRect(0,0,512,512) , p(refRect) { image = p.image; undoStore = p.undoStore; group1 = new KisGroupLayer(p.image, "group1", OPACITY_OPAQUE_U8); layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8); layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8); group4 = new KisGroupLayer(p.image, "group4", OPACITY_OPAQUE_U8); layer5 = new KisPaintLayer(p.image, "paint5", OPACITY_OPAQUE_U8); layer6 = new KisPaintLayer(p.image, "paint6", OPACITY_OPAQUE_U8); QRect rect2(100, 100, 100, 100); QRect rect3(150, 150, 100, 100); QRect rect5(200, 200, 100, 100); QRect rect6(250, 250, 100, 100); group1->setPassThroughMode(true); layer2->paintDevice()->fill(rect2, KoColor(Qt::red, p.image->colorSpace())); layer3->paintDevice()->fill(rect3, KoColor(Qt::green, p.image->colorSpace())); group4->setPassThroughMode(true); layer5->paintDevice()->fill(rect5, KoColor(Qt::blue, p.image->colorSpace())); layer6->paintDevice()->fill(rect6, KoColor(Qt::yellow, p.image->colorSpace())); p.image->addNode(group1); p.image->addNode(layer2, group1); p.image->addNode(layer3, group1); p.image->addNode(group4); p.image->addNode(layer5, group4); p.image->addNode(layer6, group4); p.image->initialRefreshGraph(); TestUtil::ReferenceImageChecker chk("passthrough", "imagetest"); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); } QRect refRect; TestUtil::MaskParent p; KisImageSP image; KisSurrogateUndoStore *undoStore; KisGroupLayerSP group1; KisPaintLayerSP layer2; KisPaintLayerSP layer3; KisGroupLayerSP group4; KisPaintLayerSP layer5; KisPaintLayerSP layer6; }; void KisImageTest::testFlattenPassThroughLayer() { FlattenPassThroughTestImage p; TestUtil::ReferenceImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.group1); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_group1_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QVERIFY(newLayer->inherits("KisPaintLayer")); } } void KisImageTest::testMergeTwoPassThroughLayers() { FlattenPassThroughTestImage p; TestUtil::ReferenceImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = mergeHelper(p, p.group4); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QVERIFY(newLayer->inherits("KisGroupLayer")); } } void KisImageTest::testMergePaintOverPassThroughLayer() { FlattenPassThroughTestImage p; TestUtil::ReferenceImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.group4); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); newLayer = mergeHelper(p, newLayer); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); } } void KisImageTest::testMergePassThroughOverPaintLayer() { FlattenPassThroughTestImage p; TestUtil::ReferenceImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.group1); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); newLayer = mergeHelper(p, p.group4); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); } } #include "kis_paint_device_debug_utils.h" #include "kis_algebra_2d.h" void KisImageTest::testPaintOverlayMask() { QRect refRect(0, 0, 512, 512); TestUtil::MaskParent p(refRect); QRect fillRect(50, 50, 412, 412); QRect selectionRect(200, 200, 100, 50); KisPaintLayerSP layer1 = p.layer; layer1->paintDevice()->fill(fillRect, KoColor(Qt::yellow, layer1->colorSpace())); KisSelectionMaskSP mask = new KisSelectionMask(p.image); KisSelectionSP selection = new KisSelection(new KisSelectionDefaultBounds(layer1->paintDevice())); selection->pixelSelection()->select(selectionRect, 128); selection->pixelSelection()->select(KisAlgebra2D::blowRect(selectionRect,-0.3), 255); mask->setSelection(selection); //mask->setVisible(false); //mask->setActive(false); p.image->addNode(mask, layer1); // a simple layer to disable oblidge child mechanism KisPaintLayerSP layer2 = new KisPaintLayer(p.image, "layer2", OPACITY_OPAQUE_U8); p.image->addNode(layer2); p.image->initialRefreshGraph(); KIS_DUMP_DEVICE_2(p.image->projection(), refRect, "00_initial", "dd"); p.image->setOverlaySelectionMask(mask); p.image->waitForDone(); KIS_DUMP_DEVICE_2(p.image->projection(), refRect, "01_activated", "dd"); p.image->setOverlaySelectionMask(0); p.image->waitForDone(); KIS_DUMP_DEVICE_2(p.image->projection(), refRect, "02_deactivated", "dd"); } - - -QTEST_MAIN(KisImageTest) +KISTEST_MAIN(KisImageTest) diff --git a/libs/image/tests/kis_image_test.h b/libs/image/tests/kis_image_test.h index d5ec9d1112..2e7a0b4d8a 100644 --- a/libs/image/tests/kis_image_test.h +++ b/libs/image/tests/kis_image_test.h @@ -1,64 +1,65 @@ /* * Copyright (c) 2005 Adrian Page * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_IMAGE_TESTER_H #define KIS_IMAGE_TESTER_H #include class KisImageTest : public QObject { Q_OBJECT private Q_SLOTS: void layerTests(); void benchmarkCreation(); void testBlockLevelOfDetail(); void testConvertImageColorSpace(); + void testAssignImageProfile(); void testGlobalSelection(); void testCloneImage(); void testLayerComposition(); void testFlattenLayer(); void testMergeDown(); void testMergeDownDestinationInheritsAlpha(); void testMergeDownDestinationCustomCompositeOp(); void testMergeDownDestinationSameCompositeOpLayerStyle(); void testMergeDownDestinationSameCompositeOp(); void testMergeDownMultipleFrames(); void testMergeMultiple(); void testMergeCrossColorSpace(); void testMergeSelectionMasks(); void testFlattenImage(); void testFlattenPassThroughLayer(); void testMergeTwoPassThroughLayers(); void testMergePaintOverPassThroughLayer(); void testMergePassThroughOverPaintLayer(); void testPaintOverlayMask(); }; #endif diff --git a/libs/libkis/Document.cpp b/libs/libkis/Document.cpp index 885e7da949..07e16e6765 100644 --- a/libs/libkis/Document.cpp +++ b/libs/libkis/Document.cpp @@ -1,993 +1,992 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser 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 Lesser 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 "Document.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_animation_importer.h" #include #include #include #include struct Document::Private { Private() {} QPointer document; }; Document::Document(KisDocument *document, QObject *parent) : QObject(parent) , d(new Private) { d->document = document; } Document::~Document() { delete d; } bool Document::operator==(const Document &other) const { return (d->document == other.d->document); } bool Document::operator!=(const Document &other) const { return !(operator==(other)); } bool Document::batchmode() const { if (!d->document) return false; return d->document->fileBatchMode(); } void Document::setBatchmode(bool value) { if (!d->document) return; d->document->setFileBatchMode(value); } Node *Document::activeNode() const { QList activeNodes; Q_FOREACH(QPointer view, KisPart::instance()->views()) { if (view && view->document() == d->document) { activeNodes << view->currentNode(); } } if (activeNodes.size() > 0) { QList nodes = LibKisUtils::createNodeList(activeNodes, d->document->image()); return nodes.first(); } return 0; } void Document::setActiveNode(Node* value) { if (!value->node()) return; KisMainWindow *mainWin = KisPart::instance()->currentMainwindow(); if (!mainWin) return; KisViewManager *viewManager = mainWin->viewManager(); if (!viewManager) return; if (viewManager->document() != d->document) return; KisNodeManager *nodeManager = viewManager->nodeManager(); if (!nodeManager) return; KisNodeSelectionAdapter *selectionAdapter = nodeManager->nodeSelectionAdapter(); if (!selectionAdapter) return; selectionAdapter->setActiveNode(value->node()); } QList Document::topLevelNodes() const { if (!d->document) return QList(); Node n(d->document->image(), d->document->image()->rootLayer()); return n.childNodes(); } Node *Document::nodeByName(const QString &name) const { if (!d->document) return 0; KisNodeSP node = d->document->image()->rootLayer()->findChildByName(name); if (node.isNull()) return 0; return Node::createNode(d->document->image(), node); } QString Document::colorDepth() const { if (!d->document) return ""; return d->document->image()->colorSpace()->colorDepthId().id(); } QString Document::colorModel() const { if (!d->document) return ""; return d->document->image()->colorSpace()->colorModelId().id(); } QString Document::colorProfile() const { if (!d->document) return ""; return d->document->image()->colorSpace()->profile()->name(); } bool Document::setColorProfile(const QString &value) { if (!d->document) return false; if (!d->document->image()) return false; const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileByName(value); if (!profile) return false; bool retval = d->document->image()->assignImageProfile(profile); - d->document->image()->setModified(); - d->document->image()->initialRefreshGraph(); + d->document->image()->waitForDone(); return retval; } bool Document::setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile) { if (!d->document) return false; if (!d->document->image()) return false; const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, colorProfile); if (!colorSpace) return false; d->document->image()->convertImageColorSpace(colorSpace, KoColorConversionTransformation::IntentPerceptual, KoColorConversionTransformation::HighQuality | KoColorConversionTransformation::NoOptimization); d->document->image()->waitForDone(); return true; } QColor Document::backgroundColor() { if (!d->document) return QColor(); if (!d->document->image()) return QColor(); const KoColor color = d->document->image()->defaultProjectionColor(); return color.toQColor(); } bool Document::setBackgroundColor(const QColor &color) { if (!d->document) return false; if (!d->document->image()) return false; KoColor background = KoColor(color, d->document->image()->colorSpace()); d->document->image()->setDefaultProjectionColor(background); d->document->image()->setModified(); d->document->image()->initialRefreshGraph(); return true; } QString Document::documentInfo() const { QDomDocument doc = KisDocument::createDomDocument("document-info" /*DTD name*/, "document-info" /*tag name*/, "1.1"); doc = d->document->documentInfo()->save(doc); return doc.toString(); } void Document::setDocumentInfo(const QString &document) { KoXmlDocument doc; QString errorMsg; int errorLine, errorColumn; doc.setContent(document, &errorMsg, &errorLine, &errorColumn); d->document->documentInfo()->load(doc); } QString Document::fileName() const { if (!d->document) return QString(); return d->document->url().toLocalFile(); } void Document::setFileName(QString value) { if (!d->document) return; QString mimeType = KisMimeDatabase::mimeTypeForFile(value, false); d->document->setMimeType(mimeType.toLatin1()); d->document->setUrl(QUrl::fromLocalFile(value)); } int Document::height() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return image->height(); } void Document::setHeight(int value) { if (!d->document) return; if (!d->document->image()) return; resizeImage(d->document->image()->bounds().x(), d->document->image()->bounds().y(), d->document->image()->width(), value); } QString Document::name() const { if (!d->document) return ""; return d->document->documentInfo()->aboutInfo("title"); } void Document::setName(QString value) { if (!d->document) return; d->document->documentInfo()->setAboutInfo("title", value); } int Document::resolution() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return qRound(d->document->image()->xRes() * 72); } void Document::setResolution(int value) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; d->document->image()->setResolution(value / 72.0, value / 72.0); } Node *Document::rootNode() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return Node::createNode(image, image->root()); } Selection *Document::selection() const { if (!d->document) return 0; if (!d->document->image()) return 0; if (!d->document->image()->globalSelection()) return 0; return new Selection(d->document->image()->globalSelection()); } void Document::setSelection(Selection* value) { if (!d->document) return; if (!d->document->image()) return; if (value) { d->document->image()->setGlobalSelection(value->selection()); } else { d->document->image()->setGlobalSelection(0); } } int Document::width() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return image->width(); } void Document::setWidth(int value) { if (!d->document) return; if (!d->document->image()) return; resizeImage(d->document->image()->bounds().x(), d->document->image()->bounds().y(), value, d->document->image()->height()); } int Document::xOffset() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return image->bounds().x(); } void Document::setXOffset(int x) { if (!d->document) return; if (!d->document->image()) return; resizeImage(x, d->document->image()->bounds().y(), d->document->image()->width(), d->document->image()->height()); } int Document::yOffset() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return image->bounds().y(); } void Document::setYOffset(int y) { if (!d->document) return; if (!d->document->image()) return; resizeImage(d->document->image()->bounds().x(), y, d->document->image()->width(), d->document->image()->height()); } double Document::xRes() const { if (!d->document) return 0.0; if (!d->document->image()) return 0.0; return d->document->image()->xRes()*72.0; } void Document::setXRes(double xRes) const { if (!d->document) return; if (!d->document->image()) return; d->document->image()->setResolution(xRes/72.0, d->document->image()->yRes()); } double Document::yRes() const { if (!d->document) return 0.0; if (!d->document->image()) return 0.0; return d->document->image()->yRes()*72.0; } void Document::setYRes(double yRes) const { if (!d->document) return; if (!d->document->image()) return; d->document->image()->setResolution(d->document->image()->xRes(), yRes/72.0); } QByteArray Document::pixelData(int x, int y, int w, int h) const { QByteArray ba; if (!d->document) return ba; KisImageSP image = d->document->image(); if (!image) return ba; KisPaintDeviceSP dev = image->projection(); ba.resize(w * h * dev->pixelSize()); dev->readBytes(reinterpret_cast(ba.data()), x, y, w, h); return ba; } bool Document::close() { bool retval = d->document->closeUrl(false); Q_FOREACH(KisView *view, KisPart::instance()->views()) { if (view->document() == d->document) { view->close(); view->closeView(); view->deleteLater(); } } KisPart::instance()->removeDocument(d->document); d->document = 0; return retval; } void Document::crop(int x, int y, int w, int h) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; QRect rc(x, y, w, h); image->cropImage(rc); } bool Document::exportImage(const QString &filename, const InfoObject &exportConfiguration) { if (!d->document) return false; const QString outputFormatString = KisMimeDatabase::mimeTypeForFile(filename, false); const QByteArray outputFormat = outputFormatString.toLatin1(); return d->document->exportDocumentSync(QUrl::fromLocalFile(filename), outputFormat, exportConfiguration.configuration()); } void Document::flatten() { if (!d->document) return; if (!d->document->image()) return; d->document->image()->flatten(0); } void Document::resizeImage(int x, int y, int w, int h) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; QRect rc; rc.setX(x); rc.setY(y); rc.setWidth(w); rc.setHeight(h); image->resizeImage(rc); } void Document::scaleImage(int w, int h, int xres, int yres, QString strategy) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; QRect rc = image->bounds(); rc.setWidth(w); rc.setHeight(h); KisFilterStrategy *actualStrategy = KisFilterStrategyRegistry::instance()->get(strategy); if (!actualStrategy) actualStrategy = KisFilterStrategyRegistry::instance()->get("Bicubic"); image->scaleImage(rc.size(), xres/72, yres/72, actualStrategy); } void Document::rotateImage(double radians) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; image->rotateImage(radians); } void Document::shearImage(double angleX, double angleY) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; image->shear(angleX, angleY); } bool Document::save() { if (!d->document) return false; if (d->document->url().isEmpty()) return false; bool retval = d->document->save(true, 0); d->document->waitForSavingToComplete(); return retval; } bool Document::saveAs(const QString &filename) { if (!d->document) return false; const QString outputFormatString = KisMimeDatabase::mimeTypeForFile(filename, false); const QByteArray outputFormat = outputFormatString.toLatin1(); QUrl oldUrl = d->document->url(); d->document->setUrl(QUrl::fromLocalFile(filename)); bool retval = d->document->saveAs(QUrl::fromLocalFile(filename), outputFormat, true); d->document->waitForSavingToComplete(); d->document->setUrl(oldUrl); return retval; } Node* Document::createNode(const QString &name, const QString &nodeType) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); Node *node = 0; if (nodeType.toLower()== "paintlayer") { node = new Node(image, new KisPaintLayer(image, name, OPACITY_OPAQUE_U8)); } else if (nodeType.toLower() == "grouplayer") { node = new Node(image, new KisGroupLayer(image, name, OPACITY_OPAQUE_U8)); } else if (nodeType.toLower() == "filelayer") { node = new Node(image, new KisFileLayer(image, name, OPACITY_OPAQUE_U8)); } else if (nodeType.toLower() == "filterlayer") { node = new Node(image, new KisAdjustmentLayer(image, name, 0, 0)); } else if (nodeType.toLower() == "filllayer") { node = new Node(image, new KisGeneratorLayer(image, name, 0, 0)); } else if (nodeType.toLower() == "clonelayer") { node = new Node(image, new KisCloneLayer(0, image, name, OPACITY_OPAQUE_U8)); } else if (nodeType.toLower() == "vectorlayer") { node = new Node(image, new KisShapeLayer(d->document->shapeController(), image, name, OPACITY_OPAQUE_U8)); } else if (nodeType.toLower() == "transparencymask") { node = new Node(image, new KisTransparencyMask()); } else if (nodeType.toLower() == "filtermask") { node = new Node(image, new KisFilterMask()); } else if (nodeType.toLower() == "transformmask") { node = new Node(image, new KisTransformMask()); } else if (nodeType.toLower() == "selectionmask") { node = new Node(image, new KisSelectionMask(image)); } return node; } GroupLayer *Document::createGroupLayer(const QString &name) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); return new GroupLayer(image, name); } FileLayer *Document::createFileLayer(const QString &name, const QString fileName, const QString scalingMethod) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); return new FileLayer(image, name, this->fileName(), fileName, scalingMethod); } FilterLayer *Document::createFilterLayer(const QString &name, Filter &filter, Selection &selection) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); return new FilterLayer(image, name, filter, selection); } FillLayer *Document::createFillLayer(const QString &name, const QString generatorName, InfoObject &configuration, Selection &selection) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); KisGeneratorSP generator = KisGeneratorRegistry::instance()->value(generatorName); if (generator) { KisFilterConfigurationSP config = generator->factoryConfiguration(); Q_FOREACH(const QString property, configuration.properties().keys()) { config->setProperty(property, configuration.property(property)); } return new FillLayer(image, name, config, selection); } return 0; } CloneLayer *Document::createCloneLayer(const QString &name, const Node *source) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); KisLayerSP layer = qobject_cast(source->node().data()); return new CloneLayer(image, name, layer); } VectorLayer *Document::createVectorLayer(const QString &name) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); return new VectorLayer(d->document->shapeController(), image, name); } FilterMask *Document::createFilterMask(const QString &name, Filter &filter, const Node *selection_source) { if (!d->document) return 0; if (!d->document->image()) return 0; if(!selection_source) return 0; KisLayerSP layer = qobject_cast(selection_source->node().data()); if(layer.isNull()) return 0; KisImageSP image = d->document->image(); FilterMask* mask = new FilterMask(image, name, filter); qobject_cast(mask->node().data())->initSelection(layer); return mask; } FilterMask *Document::createFilterMask(const QString &name, Filter &filter, Selection &selection) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); FilterMask* mask = new FilterMask(image, name, filter); qobject_cast(mask->node().data())->setSelection(selection.selection()); return mask; } SelectionMask *Document::createSelectionMask(const QString &name) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); return new SelectionMask(image, name); } QImage Document::projection(int x, int y, int w, int h) const { if (!d->document || !d->document->image()) return QImage(); return d->document->image()->convertToQImage(x, y, w, h, 0); } QImage Document::thumbnail(int w, int h) const { if (!d->document || !d->document->image()) return QImage(); return d->document->generatePreview(QSize(w, h)).toImage(); } void Document::lock() { if (!d->document || !d->document->image()) return; d->document->image()->barrierLock(); } void Document::unlock() { if (!d->document || !d->document->image()) return; d->document->image()->unlock(); } void Document::waitForDone() { if (!d->document || !d->document->image()) return; d->document->image()->waitForDone(); } bool Document::tryBarrierLock() { if (!d->document || !d->document->image()) return false; return d->document->image()->tryBarrierLock(); } bool Document::isIdle() { if (!d->document || !d->document->image()) return false; return d->document->image()->isIdle(); } void Document::refreshProjection() { if (!d->document || !d->document->image()) return; d->document->image()->refreshGraph(); } QList Document::horizontalGuides() const { QList lines; if (!d->document || !d->document->image()) return lines; KisCoordinatesConverter converter; converter.setImage(d->document->image()); QTransform transform = converter.imageToDocumentTransform().inverted(); QList untransformedLines = d->document->guidesConfig().horizontalGuideLines(); for (int i = 0; i< untransformedLines.size(); i++) { qreal line = untransformedLines[i]; lines.append(transform.map(QPointF(line, line)).x()); } return lines; } QList Document::verticalGuides() const { QList lines; if (!d->document || !d->document->image()) return lines; KisCoordinatesConverter converter; converter.setImage(d->document->image()); QTransform transform = converter.imageToDocumentTransform().inverted(); QList untransformedLines = d->document->guidesConfig().verticalGuideLines(); for (int i = 0; i< untransformedLines.size(); i++) { qreal line = untransformedLines[i]; lines.append(transform.map(QPointF(line, line)).y()); } return lines; } bool Document::guidesVisible() const { return d->document->guidesConfig().showGuides(); } bool Document::guidesLocked() const { return d->document->guidesConfig().lockGuides(); } Document *Document::clone() const { if (!d->document) return 0; QPointer clone = d->document->clone(); Document * d = new Document(clone); clone->setParent(d); // It's owned by the document, not KisPart return d; } void Document::setHorizontalGuides(const QList &lines) { if (!d->document) return; KisGuidesConfig config = d->document->guidesConfig(); KisCoordinatesConverter converter; converter.setImage(d->document->image()); QTransform transform = converter.imageToDocumentTransform(); QList transformedLines; for (int i = 0; i< lines.size(); i++) { qreal line = lines[i]; transformedLines.append(transform.map(QPointF(line, line)).x()); } config.setHorizontalGuideLines(transformedLines); d->document->setGuidesConfig(config); } void Document::setVerticalGuides(const QList &lines) { if (!d->document) return; KisGuidesConfig config = d->document->guidesConfig(); KisCoordinatesConverter converter; converter.setImage(d->document->image()); QTransform transform = converter.imageToDocumentTransform(); QList transformedLines; for (int i = 0; i< lines.size(); i++) { qreal line = lines[i]; transformedLines.append(transform.map(QPointF(line, line)).y()); } config.setVerticalGuideLines(transformedLines); d->document->setGuidesConfig(config); } void Document::setGuidesVisible(bool visible) { if (!d->document) return; KisGuidesConfig config = d->document->guidesConfig(); config.setShowGuides(visible); d->document->setGuidesConfig(config); } void Document::setGuidesLocked(bool locked) { if (!d->document) return; KisGuidesConfig config = d->document->guidesConfig(); config.setLockGuides(locked); d->document->setGuidesConfig(config); } bool Document::modified() const { if (!d->document) return false; return d->document->isModified(); } QRect Document::bounds() const { if (!d->document) return QRect(); return d->document->image()->bounds(); } QPointer Document::document() const { return d->document; } /* Animation related function */ bool Document::importAnimation(const QList &files, int firstFrame, int step) { KisView *activeView = KisPart::instance()->currentMainwindow()->activeView(); KoUpdaterPtr updater = 0; if (activeView && d->document->fileBatchMode()) { updater = activeView->viewManager()->createUnthreadedUpdater(i18n("Import frames")); } KisAnimationImporter importer(d->document->image(), updater); KisImportExportErrorCode status = importer.import(files, firstFrame, step); return status.isOk(); } int Document::framesPerSecond() { if (!d->document) return false; if (!d->document->image()) return false; return d->document->image()->animationInterface()->framerate(); } void Document::setFramesPerSecond(int fps) { if (!d->document) return; if (!d->document->image()) return; d->document->image()->animationInterface()->setFramerate(fps); } void Document::setFullClipRangeStartTime(int startTime) { if (!d->document) return; if (!d->document->image()) return; d->document->image()->animationInterface()->setFullClipRangeStartTime(startTime); } int Document::fullClipRangeStartTime() { if (!d->document) return false; if (!d->document->image()) return false; return d->document->image()->animationInterface()->fullClipRange().start(); } void Document::setFullClipRangeEndTime(int endTime) { if (!d->document) return; if (!d->document->image()) return; d->document->image()->animationInterface()->setFullClipRangeEndTime(endTime); } int Document::fullClipRangeEndTime() { if (!d->document) return false; if (!d->document->image()) return false; return d->document->image()->animationInterface()->fullClipRange().end(); } int Document::animationLength() { if (!d->document) return false; if (!d->document->image()) return false; return d->document->image()->animationInterface()->totalLength(); } void Document::setPlayBackRange(int start, int stop) { if (!d->document) return; if (!d->document->image()) return; const KisTimeRange newTimeRange = KisTimeRange(start, (stop-start)); d->document->image()->animationInterface()->setPlaybackRange(newTimeRange); } int Document::playBackStartTime() { if (!d->document) return false; if (!d->document->image()) return false; return d->document->image()->animationInterface()->playbackRange().start(); } int Document::playBackEndTime() { if (!d->document) return false; if (!d->document->image()) return false; return d->document->image()->animationInterface()->playbackRange().end(); } int Document::currentTime() { if (!d->document) return false; if (!d->document->image()) return false; return d->document->image()->animationInterface()->currentTime(); } void Document::setCurrentTime(int time) { if (!d->document) return; if (!d->document->image()) return; return d->document->image()->animationInterface()->requestTimeSwitchWithUndo(time); } diff --git a/libs/libkis/Node.cpp b/libs/libkis/Node.cpp index a92f354b5c..6dd8956430 100644 --- a/libs/libkis/Node.cpp +++ b/libs/libkis/Node.cpp @@ -1,681 +1,677 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser 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 Lesser 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 #include #include #include #include #include #include #include -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_selection.h" #include "InfoObject.h" #include "Krita.h" #include "Node.h" #include "Channel.h" #include "Filter.h" #include "Selection.h" #include "GroupLayer.h" #include "CloneLayer.h" #include "FilterLayer.h" #include "FillLayer.h" #include "FileLayer.h" #include "VectorLayer.h" #include "FilterMask.h" #include "SelectionMask.h" #include "LibKisUtils.h" struct Node::Private { Private() {} KisImageWSP image; KisNodeSP node; }; Node::Node(KisImageSP image, KisNodeSP node, QObject *parent) : QObject(parent) , d(new Private) { d->image = image; d->node = node; } Node *Node::createNode(KisImageSP image, KisNodeSP node, QObject *parent) { if (node->inherits("KisGroupLayer")) { return new GroupLayer(dynamic_cast(node.data())); } else if (node->inherits("KisCloneLayer")) { return new CloneLayer(dynamic_cast(node.data())); } else if (node->inherits("KisFileLayer")) { return new FileLayer(dynamic_cast(node.data())); } else if (node->inherits("KisAdjustmentLayer")) { return new FilterLayer(dynamic_cast(node.data())); } else if (node->inherits("KisGeneratorLayer")) { return new FillLayer(dynamic_cast(node.data())); } else if (node->inherits("KisShapeLayer")) { return new VectorLayer(dynamic_cast(node.data())); } else if (node->inherits("KisFilterMask")) { return new FilterMask(image, dynamic_cast(node.data())); } else if (node->inherits("KisSelectionMask")) { return new SelectionMask(image, dynamic_cast(node.data())); } else { return new Node(image, node, parent); } } Node::~Node() { delete d; } bool Node::operator==(const Node &other) const { return (d->node == other.d->node && d->image == other.d->image); } bool Node::operator!=(const Node &other) const { return !(operator==(other)); } Node *Node::clone() const { KisNodeSP clone = d->node->clone(); Node *node = Node::createNode(0, clone); return node; } bool Node::alphaLocked() const { if (!d->node) return false; KisPaintLayerSP paintLayer = qobject_cast(d->node.data()); if (paintLayer) { return paintLayer->alphaLocked(); } return false; } void Node::setAlphaLocked(bool value) { if (!d->node) return; KisPaintLayerSP paintLayer = qobject_cast(d->node.data()); if (paintLayer) { paintLayer->setAlphaLocked(value); } } QString Node::blendingMode() const { if (!d->node) return QString(); return d->node->compositeOpId(); } void Node::setBlendingMode(QString value) { if (!d->node) return; d->node->setCompositeOpId(value); } QList Node::channels() const { QList channels; if (!d->node) return channels; if (!d->node->inherits("KisLayer")) return channels; Q_FOREACH(KoChannelInfo *info, d->node->colorSpace()->channels()) { Channel *channel = new Channel(d->node, info); channels << channel; } return channels; } QList Node::childNodes() const { QList nodes; if (d->node) { KisNodeList nodeList; int childCount = d->node->childCount(); for (int i = 0; i < childCount; ++i) { nodeList << d->node->at(i); } nodes = LibKisUtils::createNodeList(nodeList, d->image); } return nodes; } bool Node::addChildNode(Node *child, Node *above) { if (!d->node) return false; if (above) { return d->image->addNode(child->node(), d->node, above->node()); } else { return d->image->addNode(child->node(), d->node, d->node->childCount()); } } bool Node::removeChildNode(Node *child) { if (!d->node) return false; return d->image->removeNode(child->node()); } void Node::setChildNodes(QList nodes) { if (!d->node) return; KisNodeSP node = d->node->firstChild(); while (node) { d->image->removeNode(node); node = node->nextSibling(); } Q_FOREACH(Node *node, nodes) { d->image->addNode(node->node(), d->node); } } int Node::colorLabel() const { if (!d->node) return 0; return d->node->colorLabelIndex(); } void Node::setColorLabel(int index) { if (!d->node) return; d->node->setColorLabelIndex(index); } QString Node::colorDepth() const { if (!d->node) return ""; if (!d->node->projection()) return d->node->colorSpace()->colorDepthId().id(); return d->node->projection()->colorSpace()->colorDepthId().id(); } QString Node::colorModel() const { if (!d->node) return ""; if (!d->node->projection()) return d->node->colorSpace()->colorModelId().id(); return d->node->projection()->colorSpace()->colorModelId().id(); } QString Node::colorProfile() const { if (!d->node) return ""; if (!d->node->projection()) return d->node->colorSpace()->profile()->name(); return d->node->projection()->colorSpace()->profile()->name(); } bool Node::setColorProfile(const QString &colorProfile) { if (!d->node) return false; if (!d->node->inherits("KisLayer")) return false; KisLayer *layer = qobject_cast(d->node.data()); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileByName(colorProfile); - const KoColorSpace *srcCS = layer->colorSpace(); - const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(srcCS->colorModelId().id(), - srcCS->colorDepthId().id(), - profile); - KisChangeProfileVisitor v(srcCS, dstCs); - return layer->accept(v); + bool result = d->image->assignLayerProfile(layer, profile); + d->image->waitForDone(); + return result; } bool Node::setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile) { if (!d->node) return false; if (!d->node->inherits("KisLayer")) return false; KisLayer *layer = qobject_cast(d->node.data()); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileByName(colorProfile); const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, profile); d->image->convertLayerColorSpace(d->node, dstCs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); d->image->waitForDone(); return true; } bool Node::animated() const { if (!d->node) return false; return d->node->isAnimated(); } void Node::enableAnimation() const { if (!d->node) return; d->node->enableAnimation(); } void Node::setShowInTimeline(bool showInTimeline) const { if (!d->node) return; d->node->setUseInTimeline(showInTimeline); } bool Node::showInTimeline() const { if (!d->node) return false; return d->node->useInTimeline(); } bool Node::collapsed() const { if (!d->node) return false; return d->node->collapsed(); } void Node::setCollapsed(bool collapsed) { if (!d->node) return; d->node->setCollapsed(collapsed); } bool Node::inheritAlpha() const { if (!d->node) return false; if (!d->node->inherits("KisLayer")) return false; return qobject_cast(d->node)->alphaChannelDisabled(); } void Node::setInheritAlpha(bool value) { if (!d->node) return; if (!d->node->inherits("KisLayer")) return; const_cast(qobject_cast(d->node))->disableAlphaChannel(value); } bool Node::locked() const { if (!d->node) return false; return d->node->userLocked(); } void Node::setLocked(bool value) { if (!d->node) return; d->node->setUserLocked(value); } bool Node::hasExtents() { return !d->node->extent().isEmpty(); } QString Node::name() const { if (!d->node) return QString(); return d->node->name(); } void Node::setName(QString name) { if (!d->node) return; d->node->setName(name); } int Node::opacity() const { if (!d->node) return 0; return d->node->opacity(); } void Node::setOpacity(int value) { if (!d->node) return; if (value < 0) value = 0; if (value > 255) value = 255; d->node->setOpacity(value); } Node* Node::parentNode() const { if (!d->node) return 0; return Node::createNode(d->image, d->node->parent()); } QString Node::type() const { if (!d->node) return QString(); if (qobject_cast(d->node)) { return "paintlayer"; } else if (qobject_cast(d->node)) { return "grouplayer"; } if (qobject_cast(d->node)) { return "filelayer"; } if (qobject_cast(d->node)) { return "filterlayer"; } if (qobject_cast(d->node)) { return "filllayer"; } if (qobject_cast(d->node)) { return "clonelayer"; } if (qobject_cast(d->node)) { return "referenceimageslayer"; } if (qobject_cast(d->node)) { return "vectorlayer"; } if (qobject_cast(d->node)) { return "transparencymask"; } if (qobject_cast(d->node)) { return "filtermask"; } if (qobject_cast(d->node)) { return "transformmask"; } if (qobject_cast(d->node)) { return "selectionmask"; } if (qobject_cast(d->node)) { return "colorizemask"; } return QString(); } QIcon Node::icon() const { QIcon icon; if (d->node) { icon = d->node->icon(); } return icon; } bool Node::visible() const { if (!d->node) return false; return d->node->visible(); } bool Node::hasKeyframeAtTime(int frameNumber) { if (!d->node || !d->node->isAnimated()) return false; KisRasterKeyframeChannel *rkc = dynamic_cast(d->node->getKeyframeChannel(KisKeyframeChannel::Content.id())); if (!rkc) return false; KisKeyframeSP timeOfCurrentKeyframe = rkc->keyframeAt(frameNumber); if (!timeOfCurrentKeyframe) { return false; } // do an assert just to be careful KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(timeOfCurrentKeyframe->time() == frameNumber, false); return true; } void Node::setVisible(bool visible) { if (!d->node) return; d->node->setVisible(visible); } QByteArray Node::pixelData(int x, int y, int w, int h) const { QByteArray ba; if (!d->node) return ba; KisPaintDeviceSP dev = d->node->paintDevice(); if (!dev) return ba; ba.resize(w * h * dev->pixelSize()); dev->readBytes(reinterpret_cast(ba.data()), x, y, w, h); return ba; } QByteArray Node::pixelDataAtTime(int x, int y, int w, int h, int time) const { QByteArray ba; if (!d->node || !d->node->isAnimated()) return ba; // KisRasterKeyframeChannel *rkc = dynamic_cast(d->node->getKeyframeChannel(KisKeyframeChannel::Content.id())); if (!rkc) return ba; KisKeyframeSP frame = rkc->keyframeAt(time); if (!frame) return ba; KisPaintDeviceSP dev = d->node->paintDevice(); if (!dev) return ba; rkc->fetchFrame(frame, dev); ba.resize(w * h * dev->pixelSize()); dev->readBytes(reinterpret_cast(ba.data()), x, y, w, h); return ba; } QByteArray Node::projectionPixelData(int x, int y, int w, int h) const { QByteArray ba; if (!d->node) return ba; KisPaintDeviceSP dev = d->node->projection(); if (!dev) return ba; ba.resize(w * h * dev->pixelSize()); dev->readBytes(reinterpret_cast(ba.data()), x, y, w, h); return ba; } void Node::setPixelData(QByteArray value, int x, int y, int w, int h) { if (!d->node) return; KisPaintDeviceSP dev = d->node->paintDevice(); if (!dev) return; dev->writeBytes((const quint8*)value.constData(), x, y, w, h); } QRect Node::bounds() const { if (!d->node) return QRect(); return d->node->exactBounds(); } void Node::move(int x, int y) { if (!d->node) return; d->node->setX(x); d->node->setY(y); } QPoint Node::position() const { if (!d->node) return QPoint(); return QPoint(d->node->x(), d->node->y()); } bool Node::remove() { if (!d->node) return false; if (!d->node->parent()) return false; return d->image->removeNode(d->node); } Node* Node::duplicate() { if (!d->node) return 0; return Node::createNode(d->image, d->node->clone()); } bool Node::save(const QString &filename, double xRes, double yRes, const InfoObject &exportConfiguration, const QRect &exportRect) { if (!d->node) return false; if (filename.isEmpty()) return false; KisPaintDeviceSP projection = d->node->projection(); QRect bounds = (exportRect.isEmpty())? d->node->exactBounds() : exportRect; QString mimeType = KisMimeDatabase::mimeTypeForFile(filename, false); QScopedPointer doc(KisPart::instance()->createDocument()); KisImageSP dst = new KisImage(doc->createUndoStore(), bounds.right(), bounds.bottom(), projection->compositionSourceColorSpace(), d->node->name()); dst->setResolution(xRes, yRes); doc->setFileBatchMode(Krita::instance()->batchmode()); doc->setCurrentImage(dst); KisPaintLayer* paintLayer = new KisPaintLayer(dst, "paint device", d->node->opacity()); paintLayer->paintDevice()->makeCloneFrom(projection, bounds); dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0)); dst->cropImage(bounds); dst->initialRefreshGraph(); bool r = doc->exportDocumentSync(QUrl::fromLocalFile(filename), mimeType.toLatin1(), exportConfiguration.configuration()); if (!r) { qWarning() << doc->errorMessage(); } return r; } Node* Node::mergeDown() { if (!d->node) return 0; if (!qobject_cast(d->node.data())) return 0; if (!d->node->prevSibling()) return 0; d->image->mergeDown(qobject_cast(d->node.data()), KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); d->image->waitForDone(); return Node::createNode(d->image, d->node->prevSibling()); } void Node::scaleNode(QPointF origin, int width, int height, QString strategy) { if (!d->node) return; if (!qobject_cast(d->node.data())) return; if (!d->node->parent()) return; KisFilterStrategy *actualStrategy = KisFilterStrategyRegistry::instance()->get(strategy); if (!actualStrategy) actualStrategy = KisFilterStrategyRegistry::instance()->get("Bicubic"); const QRect bounds(d->node->exactBounds()); d->image->scaleNode(d->node, origin, qreal(width) / bounds.width(), qreal(height) / bounds.height(), actualStrategy, 0); } void Node::rotateNode(double radians) { if (!d->node) return; if (!qobject_cast(d->node.data())) return; if (!d->node->parent()) return; d->image->rotateNode(d->node, radians, 0); } void Node::cropNode(int x, int y, int w, int h) { if (!d->node) return; if (!qobject_cast(d->node.data())) return; if (!d->node->parent()) return; QRect rect = QRect(x, y, w, h); d->image->cropNode(d->node, rect); } void Node::shearNode(double angleX, double angleY) { if (!d->node) return; if (!qobject_cast(d->node.data())) return; if (!d->node->parent()) return; d->image->shearNode(d->node, angleX, angleY, 0); } QImage Node::thumbnail(int w, int h) { if (!d->node) return QImage(); return d->node->createThumbnail(w, h); } KisPaintDeviceSP Node::paintDevice() const { return d->node->paintDevice(); } KisImageSP Node::image() const { return d->image; } KisNodeSP Node::node() const { return d->node; } diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp index 141598f756..8d58b71c90 100644 --- a/libs/ui/KisDocument.cpp +++ b/libs/ui/KisDocument.cpp @@ -1,2257 +1,2259 @@ /* This file is part of the Krita project * * Copyright (C) 2014 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" // XXX: remove #include // XXX: remove #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Krita Image #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_layer_utils.h" // Local #include "KisViewManager.h" #include "kis_clipboard.h" #include "widgets/kis_custom_image_widget.h" #include "canvas/kis_canvas2.h" #include "flake/kis_shape_controller.h" #include "kis_statusbar.h" #include "widgets/kis_progress_widget.h" #include "kis_canvas_resource_provider.h" #include "KisResourceServerProvider.h" #include "kis_node_manager.h" #include "KisPart.h" #include "KisApplication.h" #include "KisDocument.h" #include "KisImportExportManager.h" #include "KisView.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_image_barrier_lock_adapter.h" #include "KisReferenceImagesLayer.h" #include #include "kis_config_notifier.h" #include "kis_async_action_feedback.h" #include "KisCloneDocumentStroke.h" #include #include #include "kis_simple_stroke_strategy.h" // Define the protocol used here for embedded documents' URL // This used to "store" but QUrl didn't like it, // so let's simply make it "tar" ! #define STORE_PROTOCOL "tar" // The internal path is a hack to make QUrl happy and for document children #define INTERNAL_PROTOCOL "intern" #define INTERNAL_PREFIX "intern:/" // Warning, keep it sync in koStore.cc #include using namespace std; namespace { constexpr int errorMessageTimeout = 5000; constexpr int successMessageTimeout = 1000; } /********************************************************** * * KisDocument * **********************************************************/ //static QString KisDocument::newObjectName() { static int s_docIFNumber = 0; QString name; name.setNum(s_docIFNumber++); name.prepend("document_"); return name; } class UndoStack : public KUndo2Stack { public: UndoStack(KisDocument *doc) : KUndo2Stack(doc), m_doc(doc) { } void setIndex(int idx) override { KisImageWSP image = this->image(); image->requestStrokeCancellation(); if(image->tryBarrierLock()) { KUndo2Stack::setIndex(idx); image->unlock(); } } void notifySetIndexChangedOneCommand() override { KisImageWSP image = this->image(); image->unlock(); /** * Some very weird commands may emit blocking signals to * the GUI (e.g. KisGuiContextCommand). Here is the best thing * we can do to avoid the deadlock */ while(!image->tryBarrierLock()) { QApplication::processEvents(); } } void undo() override { KisImageWSP image = this->image(); image->requestUndoDuringStroke(); if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) { return; } if(image->tryBarrierLock()) { KUndo2Stack::undo(); image->unlock(); } } void redo() override { KisImageWSP image = this->image(); if(image->tryBarrierLock()) { KUndo2Stack::redo(); image->unlock(); } } private: KisImageWSP image() { KisImageWSP currentImage = m_doc->image(); Q_ASSERT(currentImage); return currentImage; } private: KisDocument *m_doc; }; class Q_DECL_HIDDEN KisDocument::Private { public: Private(KisDocument *_q) : q(_q) , docInfo(new KoDocumentInfo(_q)) // deleted by QObject , importExportManager(new KisImportExportManager(_q)) // deleted manually , autoSaveTimer(new QTimer(_q)) , undoStack(new UndoStack(_q)) // deleted by QObject , m_bAutoDetectedMime(false) , modified(false) , readwrite(true) , firstMod(QDateTime::currentDateTime()) , lastMod(firstMod) , nserver(new KisNameServer(1)) , imageIdleWatcher(2000 /*ms*/) , globalAssistantsColor(KisConfig(true).defaultAssistantsColor()) , savingLock(&savingMutex) , batchMode(false) { if (QLocale().measurementSystem() == QLocale::ImperialSystem) { unit = KoUnit::Inch; } else { unit = KoUnit::Centimeter; } } Private(const Private &rhs, KisDocument *_q) : q(_q) , docInfo(new KoDocumentInfo(*rhs.docInfo, _q)) , importExportManager(new KisImportExportManager(_q)) , autoSaveTimer(new QTimer(_q)) , undoStack(new UndoStack(_q)) , nserver(new KisNameServer(*rhs.nserver)) , preActivatedNode(0) // the node is from another hierarchy! , imageIdleWatcher(2000 /*ms*/) , savingLock(&savingMutex) { copyFromImpl(rhs, _q, CONSTRUCT); } ~Private() { // Don't delete m_d->shapeController because it's in a QObject hierarchy. delete nserver; } KisDocument *q = 0; KoDocumentInfo *docInfo = 0; KoUnit unit; KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options] QByteArray mimeType; // The actual mimetype of the document QByteArray outputMimeType; // The mimetype to use when saving QTimer *autoSaveTimer; QString lastErrorMessage; // see openFile() QString lastWarningMessage; int autoSaveDelay = 300; // in seconds, 0 to disable. bool modifiedAfterAutosave = false; bool isAutosaving = false; bool disregardAutosaveFailure = false; int autoSaveFailureCount = 0; KUndo2Stack *undoStack = 0; KisGuidesConfig guidesConfig; KisMirrorAxisConfig mirrorAxisConfig; bool m_bAutoDetectedMime = false; // whether the mimetype in the arguments was detected by the part itself QUrl m_url; // local url - the one displayed to the user. QString m_file; // Local file - the only one the part implementation should deal with. QMutex savingMutex; bool modified = false; bool readwrite = false; QDateTime firstMod; QDateTime lastMod; KisNameServer *nserver; KisImageSP image; KisImageSP savingImage; KisNodeWSP preActivatedNode; KisShapeController* shapeController = 0; KoShapeController* koShapeController = 0; KisIdleWatcher imageIdleWatcher; QScopedPointer imageIdleConnection; QList assistants; QColor globalAssistantsColor; QList paletteList; bool ownsPaletteList = false; KisGridConfig gridConfig; StdLockableWrapper savingLock; bool modifiedWhileSaving = false; QScopedPointer backgroundSaveDocument; QPointer savingUpdater; QFuture childSavingFuture; KritaUtils::ExportFileJob backgroundSaveJob; bool isRecovered = false; bool batchMode { false }; void syncDecorationsWrapperLayerState(); void setImageAndInitIdleWatcher(KisImageSP _image) { image = _image; imageIdleWatcher.setTrackedImage(image); if (image) { imageIdleConnection.reset( new KisSignalAutoConnection( &imageIdleWatcher, SIGNAL(startedIdleMode()), image.data(), SLOT(explicitRegenerateLevelOfDetail()))); } } void copyFrom(const Private &rhs, KisDocument *q); void copyFromImpl(const Private &rhs, KisDocument *q, KisDocument::CopyPolicy policy); /// clones the palette list oldList /// the ownership of the returned KoColorSet * belongs to the caller QList clonePaletteList(const QList &oldList); class StrippedSafeSavingLocker; }; void KisDocument::Private::syncDecorationsWrapperLayerState() { if (!this->image) return; KisImageSP image = this->image; KisDecorationsWrapperLayerSP decorationsLayer = KisLayerUtils::findNodeByType(image->root()); const bool needsDecorationsWrapper = gridConfig.showGrid() || (guidesConfig.showGuides() && guidesConfig.hasGuides()) || !assistants.isEmpty(); struct SyncDecorationsWrapperStroke : public KisSimpleStrokeStrategy { SyncDecorationsWrapperStroke(KisDocument *document, bool needsDecorationsWrapper) : KisSimpleStrokeStrategy("sync-decorations-wrapper", kundo2_noi18n("start-isolated-mode")), m_document(document), m_needsDecorationsWrapper(needsDecorationsWrapper) { this->enableJob(JOB_INIT, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); setClearsRedoOnStart(false); } void initStrokeCallback() { KisDecorationsWrapperLayerSP decorationsLayer = KisLayerUtils::findNodeByType(m_document->image()->root()); if (m_needsDecorationsWrapper && !decorationsLayer) { m_document->image()->addNode(new KisDecorationsWrapperLayer(m_document)); } else if (!m_needsDecorationsWrapper && decorationsLayer) { m_document->image()->removeNode(decorationsLayer); } } private: KisDocument *m_document = 0; bool m_needsDecorationsWrapper = false; }; KisStrokeId id = image->startStroke(new SyncDecorationsWrapperStroke(q, needsDecorationsWrapper)); image->endStroke(id); } void KisDocument::Private::copyFrom(const Private &rhs, KisDocument *q) { copyFromImpl(rhs, q, KisDocument::REPLACE); } void KisDocument::Private::copyFromImpl(const Private &rhs, KisDocument *q, KisDocument::CopyPolicy policy) { if (policy == REPLACE) { delete docInfo; } docInfo = (new KoDocumentInfo(*rhs.docInfo, q)); unit = rhs.unit; mimeType = rhs.mimeType; outputMimeType = rhs.outputMimeType; if (policy == REPLACE) { q->setGuidesConfig(rhs.guidesConfig); q->setMirrorAxisConfig(rhs.mirrorAxisConfig); q->setModified(rhs.modified); q->setAssistants(KisPaintingAssistant::cloneAssistantList(rhs.assistants)); q->setGridConfig(rhs.gridConfig); } else { // in CONSTRUCT mode, we cannot use the functions of KisDocument // because KisDocument does not yet have a pointer to us. guidesConfig = rhs.guidesConfig; mirrorAxisConfig = rhs.mirrorAxisConfig; modified = rhs.modified; assistants = KisPaintingAssistant::cloneAssistantList(rhs.assistants); gridConfig = rhs.gridConfig; } m_bAutoDetectedMime = rhs.m_bAutoDetectedMime; m_url = rhs.m_url; m_file = rhs.m_file; readwrite = rhs.readwrite; firstMod = rhs.firstMod; lastMod = rhs.lastMod; // XXX: the display properties will be shared between different snapshots globalAssistantsColor = rhs.globalAssistantsColor; if (policy == REPLACE) { QList newPaletteList = clonePaletteList(rhs.paletteList); q->setPaletteList(newPaletteList, /* emitSignal = */ true); // we still do not own palettes if we did not } else { paletteList = rhs.paletteList; } batchMode = rhs.batchMode; } QList KisDocument::Private::clonePaletteList(const QList &oldList) { QList newList; Q_FOREACH (KoColorSet *palette, oldList) { newList << new KoColorSet(*palette); } return newList; } class KisDocument::Private::StrippedSafeSavingLocker { public: StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image) : m_locked(false) , m_image(image) , m_savingLock(savingMutex) , m_imageLock(image, true) { /** * Initial try to lock both objects. Locking the image guards * us from any image composition threads running in the * background, while savingMutex guards us from entering the * saving code twice by autosave and main threads. * * Since we are trying to lock multiple objects, so we should * do it in a safe manner. */ m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; if (!m_locked) { m_image->requestStrokeEnd(); QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); // one more try... m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; } } ~StrippedSafeSavingLocker() { if (m_locked) { m_imageLock.unlock(); m_savingLock.unlock(); } } bool successfullyLocked() const { return m_locked; } private: bool m_locked; KisImageSP m_image; StdLockableWrapper m_savingLock; KisImageBarrierLockAdapter m_imageLock; }; KisDocument::KisDocument() : d(new Private(this)) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); setObjectName(newObjectName()); // preload the krita resources KisResourceServerProvider::instance(); d->shapeController = new KisShapeController(this, d->nserver); d->koShapeController = new KoShapeController(0, d->shapeController); d->shapeController->resourceManager()->setGlobalShapeController(d->koShapeController); slotConfigChanged(); } KisDocument::KisDocument(const KisDocument &rhs) : QObject(), d(new Private(*rhs.d, this)) { copyFromDocumentImpl(rhs, CONSTRUCT); } KisDocument::~KisDocument() { // wait until all the pending operations are in progress waitForSavingToComplete(); /** * Push a timebomb, which will try to release the memory after * the document has been deleted */ KisPaintDevice::createMemoryReleaseObject()->deleteLater(); d->autoSaveTimer->disconnect(this); d->autoSaveTimer->stop(); delete d->importExportManager; // Despite being QObject they needs to be deleted before the image delete d->shapeController; delete d->koShapeController; if (d->image) { d->image->notifyAboutToBeDeleted(); /** * WARNING: We should wait for all the internal image jobs to * finish before entering KisImage's destructor. The problem is, * while execution of KisImage::~KisImage, all the weak shared * pointers pointing to the image enter an inconsistent * state(!). The shared counter is already zero and destruction * has started, but the weak reference doesn't know about it, * because KisShared::~KisShared hasn't been executed yet. So all * the threads running in background and having weak pointers will * enter the KisImage's destructor as well. */ d->image->requestStrokeCancellation(); d->image->waitForDone(); // clear undo commands that can still point to the image d->undoStack->clear(); d->image->waitForDone(); KisImageWSP sanityCheckPointer = d->image; Q_UNUSED(sanityCheckPointer); // The following line trigger the deletion of the image d->image.clear(); // check if the image has actually been deleted KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid()); } if (d->ownsPaletteList) { qDeleteAll(d->paletteList); } delete d; } bool KisDocument::reload() { // XXX: reimplement! return false; } KisDocument *KisDocument::clone() { return new KisDocument(*this); } bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { QFileInfo filePathInfo(job.filePath); if (filePathInfo.exists() && !filePathInfo.isWritable()) { slotCompleteSavingDocument(job, ImportExportCodes::NoAccessToWrite, i18n("%1 cannot be written to. Please save under a different name.", job.filePath)); //return ImportExportCodes::NoAccessToWrite; return false; } KisConfig cfg(true); if (cfg.backupFile() && filePathInfo.exists()) { QString backupDir; switch(cfg.readEntry("backupfilelocation", 0)) { case 1: backupDir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); break; case 2: backupDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation); break; default: // Do nothing: the empty string is user file location break; } int numOfBackupsKept = cfg.readEntry("numberofbackupfiles", 1); QString suffix = cfg.readEntry("backupfilesuffix", "~"); if (numOfBackupsKept == 1) { if (!KBackup::simpleBackupFile(job.filePath, backupDir, suffix)) { qWarning() << "Failed to create simple backup file!" << job.filePath << backupDir << suffix; KisUsageLogger::log(QString("Failed to create a simple backup for %1 in %2.").arg(job.filePath).arg(backupDir.isEmpty() ? "the same location as the file" : backupDir)); return false; } else { KisUsageLogger::log(QString("Create a simple backup for %1 in %2.").arg(job.filePath).arg(backupDir.isEmpty() ? "the same location as the file" : backupDir)); } } else if (numOfBackupsKept > 2) { if (!KBackup::numberedBackupFile(job.filePath, backupDir, suffix, numOfBackupsKept)) { qWarning() << "Failed to create numbered backup file!" << job.filePath << backupDir << suffix; KisUsageLogger::log(QString("Failed to create a numbered backup for %2.").arg(job.filePath).arg(backupDir.isEmpty() ? "the same location as the file" : backupDir)); return false; } else { KisUsageLogger::log(QString("Create a simple backup for %1 in %2.").arg(job.filePath).arg(backupDir.isEmpty() ? "the same location as the file" : backupDir)); } } } //KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false); if (job.mimeType.isEmpty()) { KisImportExportErrorCode error = ImportExportCodes::FileFormatIncorrect; slotCompleteSavingDocument(job, error, error.errorMessage()); return false; } const QString actionName = job.flags & KritaUtils::SaveIsExporting ? i18n("Exporting Document...") : i18n("Saving Document..."); bool started = initiateSavingInBackground(actionName, this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob, KisImportExportErrorCode ,QString)), job, exportConfiguration); if (!started) { emit canceled(QString()); } return started; } bool KisDocument::exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; SaveFlags flags = SaveIsExporting; if (showWarnings) { flags |= SaveShowWarnings; } KisUsageLogger::log(QString("Exporting Document: %1 as %2. %3 * %4 pixels, %5 layers, %6 frames, %7 framerate. Export configuration: %8") .arg(url.toLocalFile()) .arg(QString::fromLatin1(mimeType)) .arg(d->image->width()) .arg(d->image->height()) .arg(d->image->nlayers()) .arg(d->image->animationInterface()->totalLength()) .arg(d->image->animationInterface()->framerate()) .arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration")); return exportDocumentImpl(KritaUtils::ExportFileJob(url.toLocalFile(), mimeType, flags), exportConfiguration); } bool KisDocument::saveAs(const QUrl &_url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; KisUsageLogger::log(QString("Saving Document %9 as %1 (mime: %2). %3 * %4 pixels, %5 layers. %6 frames, %7 framerate. Export configuration: %8") .arg(_url.toLocalFile()) .arg(QString::fromLatin1(mimeType)) .arg(d->image->width()) .arg(d->image->height()) .arg(d->image->nlayers()) .arg(d->image->animationInterface()->totalLength()) .arg(d->image->animationInterface()->framerate()) .arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration") .arg(url().toLocalFile())); return exportDocumentImpl(ExportFileJob(_url.toLocalFile(), mimeType, showWarnings ? SaveShowWarnings : SaveNone), exportConfiguration); } bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { return saveAs(url(), mimeType(), showWarnings, exportConfiguration); } QByteArray KisDocument::serializeToNativeByteArray() { QByteArray byteArray; QBuffer buffer(&byteArray); QScopedPointer filter(KisImportExportManager::filterForMimeType(nativeFormatMimeType(), KisImportExportManager::Export)); filter->setBatchMode(true); filter->setMimeType(nativeFormatMimeType()); Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return byteArray; } d->savingImage = d->image; if (!filter->convert(this, &buffer).isOk()) { qWarning() << "serializeToByteArray():: Could not export to our native format"; } return byteArray; } void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage) { if (status.isCancelled()) return; const QString fileName = QFileInfo(job.filePath).fileName(); if (!status.isOk()) { emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", "Error during saving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); if (!fileBatchMode()) { const QString filePath = job.filePath; QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, exportErrorToUserMessage(status, errorMessage))); } } else { if (!(job.flags & KritaUtils::SaveIsExporting)) { const QString existingAutoSaveBaseName = localFilePath(); const bool wasRecovered = isRecovered(); setUrl(QUrl::fromLocalFile(job.filePath)); setLocalFilePath(job.filePath); setMimeType(job.mimeType); updateEditingTime(true); if (!d->modifiedWhileSaving) { /** * If undo stack is already clean/empty, it doesn't emit any * signals, so we might forget update document modified state * (which was set, e.g. while recovering an autosave file) */ if (d->undoStack->isClean()) { setModified(false); } else { d->undoStack->setClean(); } } setRecovered(false); removeAutoSaveFiles(existingAutoSaveBaseName, wasRecovered); } emit completed(); emit sigSavingFinished(); emit statusBarMessage(i18n("Finished saving %1", fileName), successMessageTimeout); } } QByteArray KisDocument::mimeType() const { return d->mimeType; } void KisDocument::setMimeType(const QByteArray & mimeType) { d->mimeType = mimeType; } bool KisDocument::fileBatchMode() const { return d->batchMode; } void KisDocument::setFileBatchMode(const bool batchMode) { d->batchMode = batchMode; } KisDocument* KisDocument::lockAndCloneForSaving() { // force update of all the asynchronous nodes before cloning QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); KisLayerUtils::forceAllDelayedNodesUpdate(d->image->root()); KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { if (!window->viewManager()->blockUntilOperationsFinished(d->image)) { return 0; } } } Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return 0; } return new KisDocument(*this); } KisDocument *KisDocument::lockAndCreateSnapshot() { KisDocument *doc = lockAndCloneForSaving(); if (doc) { // clone palette list doc->d->paletteList = doc->d->clonePaletteList(doc->d->paletteList); doc->d->ownsPaletteList = true; } return doc; } void KisDocument::copyFromDocument(const KisDocument &rhs) { copyFromDocumentImpl(rhs, REPLACE); } void KisDocument::copyFromDocumentImpl(const KisDocument &rhs, CopyPolicy policy) { if (policy == REPLACE) { d->copyFrom(*(rhs.d), this); d->undoStack->clear(); } else { // in CONSTRUCT mode, d should be already initialized connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); d->shapeController = new KisShapeController(this, d->nserver); d->koShapeController = new KoShapeController(0, d->shapeController); d->shapeController->resourceManager()->setGlobalShapeController(d->koShapeController); } setObjectName(rhs.objectName()); slotConfigChanged(); if (rhs.d->image) { if (policy == REPLACE) { d->image->barrierLock(/* readOnly = */ false); rhs.d->image->barrierLock(/* readOnly = */ true); d->image->copyFromImage(*(rhs.d->image)); d->image->unlock(); rhs.d->image->unlock(); setCurrentImage(d->image, /* forceInitialUpdate = */ true); } else { // clone the image with keeping the GUIDs of the layers intact // NOTE: we expect the image to be locked! setCurrentImage(rhs.image()->clone(/* exactCopy = */ true), /* forceInitialUpdate = */ false); } } if (rhs.d->preActivatedNode) { QQueue linearizedNodes; KisLayerUtils::recursiveApplyNodes(rhs.d->image->root(), [&linearizedNodes](KisNodeSP node) { linearizedNodes.enqueue(node); }); KisLayerUtils::recursiveApplyNodes(d->image->root(), [&linearizedNodes, &rhs, this](KisNodeSP node) { KisNodeSP refNode = linearizedNodes.dequeue(); if (rhs.d->preActivatedNode.data() == refNode.data()) { d->preActivatedNode = node; } }); } // reinitialize references' signal connection KisReferenceImagesLayerSP referencesLayer = this->referenceImagesLayer(); setReferenceImagesLayer(referencesLayer, false); KisDecorationsWrapperLayerSP decorationsLayer = KisLayerUtils::findNodeByType(d->image->root()); if (decorationsLayer) { decorationsLayer->setDocument(this); } if (policy == REPLACE) { setModified(true); } } bool KisDocument::exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration) { { /** * The caller guarantees that no one else uses the document (usually, * it is a temporary document created specifically for exporting), so * we don't need to copy or lock the document. Instead we should just * ensure the barrier lock is synced and then released. */ Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return false; } } d->savingImage = d->image; const QString fileName = url.toLocalFile(); KisImportExportErrorCode status = d->importExportManager-> exportDocument(fileName, fileName, mimeType, false, exportConfiguration); d->savingImage = 0; return status.isOk(); } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { return initiateSavingInBackground(actionName, receiverObject, receiverMethod, job, exportConfiguration, std::unique_ptr()); } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, std::unique_ptr &&optionalClonedDocument) { KIS_ASSERT_RECOVER_RETURN_VALUE(job.isValid(), false); QScopedPointer clonedDocument; if (!optionalClonedDocument) { clonedDocument.reset(lockAndCloneForSaving()); } else { clonedDocument.reset(optionalClonedDocument.release()); } // we block saving until the current saving is finished! if (!clonedDocument || !d->savingMutex.tryLock()) { return false; } auto waitForImage = [] (KisImageSP image) { KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { window->viewManager()->blockUntilOperationsFinishedForced(image); } } }; { KisNodeSP newRoot = clonedDocument->image()->root(); KIS_SAFE_ASSERT_RECOVER(!KisLayerUtils::hasDelayedNodeWithUpdates(newRoot)) { KisLayerUtils::forceAllDelayedNodesUpdate(newRoot); waitForImage(clonedDocument->image()); } } KIS_SAFE_ASSERT_RECOVER(clonedDocument->image()->isIdle()) { waitForImage(clonedDocument->image()); } KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveDocument, false); KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveJob.isValid(), false); d->backgroundSaveDocument.reset(clonedDocument.take()); d->backgroundSaveJob = job; d->modifiedWhileSaving = false; if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = true; } connect(d->backgroundSaveDocument.data(), SIGNAL(sigBackgroundSavingFinished(KisImportExportErrorCode, QString)), this, SLOT(slotChildCompletedSavingInBackground(KisImportExportErrorCode, QString))); connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob, KisImportExportErrorCode, QString)), receiverObject, receiverMethod, Qt::UniqueConnection); bool started = d->backgroundSaveDocument->startExportInBackground(actionName, job.filePath, job.filePath, job.mimeType, job.flags & KritaUtils::SaveShowWarnings, exportConfiguration); if (!started) { // the state should have been deinitialized in slotChildCompletedSavingInBackground() KIS_SAFE_ASSERT_RECOVER (!d->backgroundSaveDocument && !d->backgroundSaveJob.isValid()) { d->backgroundSaveDocument.take()->deleteLater(); d->savingMutex.unlock(); d->backgroundSaveJob = KritaUtils::ExportFileJob(); } } return started; } void KisDocument::slotChildCompletedSavingInBackground(KisImportExportErrorCode status, const QString &errorMessage) { KIS_ASSERT_RECOVER_RETURN(isSaving()); KIS_ASSERT_RECOVER(d->backgroundSaveDocument) { d->savingMutex.unlock(); return; } if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = false; } d->backgroundSaveDocument.take()->deleteLater(); KIS_ASSERT_RECOVER(d->backgroundSaveJob.isValid()) { d->savingMutex.unlock(); return; } const KritaUtils::ExportFileJob job = d->backgroundSaveJob; d->backgroundSaveJob = KritaUtils::ExportFileJob(); // unlock at the very end d->savingMutex.unlock(); QFileInfo fi(job.filePath); KisUsageLogger::log(QString("Completed saving %1 (mime: %2). Result: %3. Size: %4. MD5 Hash: %5") .arg(job.filePath) .arg(QString::fromLatin1(job.mimeType)) .arg(!status.isOk() ? exportErrorToUserMessage(status, errorMessage) : "OK") .arg(fi.size()) .arg(QString::fromLatin1(KoMD5Generator().generateHash(job.filePath).toHex()))); emit sigCompleteBackgroundSaving(job, status, errorMessage); } void KisDocument::slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument) { if (!d->modified || !d->modifiedAfterAutosave) return; const QString autoSaveFileName = generateAutoSaveFileName(localFilePath()); emit statusBarMessage(i18n("Autosaving... %1", autoSaveFileName), successMessageTimeout); KisUsageLogger::log(QString("Autosaving: %1").arg(autoSaveFileName)); const bool hadClonedDocument = bool(optionalClonedDocument); bool started = false; if (d->image->isIdle() || hadClonedDocument) { started = initiateSavingInBackground(i18n("Autosaving..."), this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob, KisImportExportErrorCode, QString)), KritaUtils::ExportFileJob(autoSaveFileName, nativeFormatMimeType(), KritaUtils::SaveIsExporting | KritaUtils::SaveInAutosaveMode), 0, std::move(optionalClonedDocument)); } else { emit statusBarMessage(i18n("Autosaving postponed: document is busy..."), errorMessageTimeout); } if (!started && !hadClonedDocument && d->autoSaveFailureCount >= 3) { KisCloneDocumentStroke *stroke = new KisCloneDocumentStroke(this); connect(stroke, SIGNAL(sigDocumentCloned(KisDocument*)), this, SLOT(slotInitiateAsyncAutosaving(KisDocument*)), Qt::BlockingQueuedConnection); KisStrokeId strokeId = d->image->startStroke(stroke); d->image->endStroke(strokeId); setInfiniteAutoSaveInterval(); } else if (!started) { setEmergencyAutoSaveInterval(); } else { d->modifiedAfterAutosave = false; } } void KisDocument::slotAutoSave() { slotAutoSaveImpl(std::unique_ptr()); } void KisDocument::slotInitiateAsyncAutosaving(KisDocument *clonedDocument) { slotAutoSaveImpl(std::unique_ptr(clonedDocument)); } void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage) { Q_UNUSED(job); const QString fileName = QFileInfo(job.filePath).fileName(); if (!status.isOk()) { setEmergencyAutoSaveInterval(); emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", "Error during autosaving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); } else { KisConfig cfg(true); d->autoSaveDelay = cfg.autoSaveInterval(); if (!d->modifiedWhileSaving) { d->autoSaveTimer->stop(); // until the next change d->autoSaveFailureCount = 0; } else { setNormalAutoSaveInterval(); } emit statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout); } } bool KisDocument::startExportInBackground(const QString &actionName, const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { d->savingImage = d->image; KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName); d->importExportManager->setUpdater(d->savingUpdater); } } KisImportExportErrorCode initializationStatus(ImportExportCodes::OK); d->childSavingFuture = d->importExportManager->exportDocumentAsyc(location, realLocation, mimeType, initializationStatus, showWarnings, exportConfiguration); if (!initializationStatus.isOk()) { if (d->savingUpdater) { d->savingUpdater->cancel(); } d->savingImage.clear(); emit sigBackgroundSavingFinished(initializationStatus, initializationStatus.errorMessage()); return false; } typedef QFutureWatcher StatusWatcher; StatusWatcher *watcher = new StatusWatcher(); watcher->setFuture(d->childSavingFuture); connect(watcher, SIGNAL(finished()), SLOT(finishExportInBackground())); connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater())); return true; } void KisDocument::finishExportInBackground() { KIS_SAFE_ASSERT_RECOVER(d->childSavingFuture.isFinished()) { emit sigBackgroundSavingFinished(ImportExportCodes::InternalError, ""); return; } KisImportExportErrorCode status = d->childSavingFuture.result(); const QString errorMessage = status.errorMessage(); d->savingImage.clear(); d->childSavingFuture = QFuture(); d->lastErrorMessage.clear(); if (d->savingUpdater) { d->savingUpdater->setProgress(100); } emit sigBackgroundSavingFinished(status, errorMessage); } void KisDocument::setReadWrite(bool readwrite) { d->readwrite = readwrite; setNormalAutoSaveInterval(); Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) { mainWindow->setReadWrite(readwrite); } } void KisDocument::setAutoSaveDelay(int delay) { if (isReadWrite() && delay > 0) { d->autoSaveTimer->start(delay * 1000); } else { d->autoSaveTimer->stop(); } } void KisDocument::setNormalAutoSaveInterval() { setAutoSaveDelay(d->autoSaveDelay); d->autoSaveFailureCount = 0; } void KisDocument::setEmergencyAutoSaveInterval() { const int emergencyAutoSaveInterval = 10; /* sec */ setAutoSaveDelay(emergencyAutoSaveInterval); d->autoSaveFailureCount++; } void KisDocument::setInfiniteAutoSaveInterval() { setAutoSaveDelay(-1); } KoDocumentInfo *KisDocument::documentInfo() const { return d->docInfo; } bool KisDocument::isModified() const { return d->modified; } QPixmap KisDocument::generatePreview(const QSize& size) { KisImageSP image = d->image; if (d->savingImage) image = d->savingImage; if (image) { QRect bounds = image->bounds(); QSize newSize = bounds.size(); newSize.scale(size, Qt::KeepAspectRatio); QPixmap px = QPixmap::fromImage(image->convertToQImage(newSize, 0)); if (px.size() == QSize(0,0)) { px = QPixmap(newSize); QPainter gc(&px); QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5)); gc.fillRect(px.rect(), checkBrush); gc.end(); } return px; } return QPixmap(size); } QString KisDocument::generateAutoSaveFileName(const QString & path) const { QString retval; // Using the extension allows to avoid relying on the mime magic when opening const QString extension (".kra"); QString prefix = KisConfig(true).readEntry("autosavefileshidden") ? QString(".") : QString(); QRegularExpression autosavePattern1("^\\..+-autosave.kra$"); QRegularExpression autosavePattern2("^.+-autosave.kra$"); QFileInfo fi(path); QString dir = fi.absolutePath(); QString filename = fi.fileName(); if (path.isEmpty() || autosavePattern1.match(filename).hasMatch() || autosavePattern2.match(filename).hasMatch() || !fi.isWritable()) { // Never saved? #ifdef Q_OS_WIN // On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921) retval = QString("%1%2%7%3-%4-%5-autosave%6").arg(QDir::tempPath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension).arg(prefix); #else // On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file retval = QString("%1%2%7%3-%4-%5-autosave%6").arg(QDir::homePath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension).arg(prefix); #endif } else { retval = QString("%1%2%5%3-autosave%4").arg(dir).arg(QDir::separator()).arg(filename).arg(extension).arg(prefix); } //qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval; return retval; } bool KisDocument::importDocument(const QUrl &_url) { bool ret; dbgUI << "url=" << _url.url(); // open... ret = openUrl(_url); // reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a // File --> Import if (ret) { dbgUI << "success, resetting url"; resetURL(); setTitleModified(); } return ret; } bool KisDocument::openUrl(const QUrl &_url, OpenFlags flags) { if (!_url.isLocalFile()) { return false; } dbgUI << "url=" << _url.url(); d->lastErrorMessage.clear(); // Reimplemented, to add a check for autosave files and to improve error reporting if (!_url.isValid()) { d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ? return false; } QUrl url(_url); bool autosaveOpened = false; if (url.isLocalFile() && !fileBatchMode()) { QString file = url.toLocalFile(); QString asf = generateAutoSaveFileName(file); if (QFile::exists(asf)) { KisApplication *kisApp = static_cast(qApp); kisApp->hideSplashScreen(); //qDebug() <<"asf=" << asf; // ## TODO compare timestamps ? int res = QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("An autosaved file exists for this document.\nDo you want to open the autosaved file instead?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); switch (res) { case QMessageBox::Yes : url.setPath(asf); autosaveOpened = true; break; case QMessageBox::No : KisUsageLogger::log(QString("Removing autosave file: %1").arg(asf)); QFile::remove(asf); break; default: // Cancel return false; } } } bool ret = openUrlInternal(url); if (autosaveOpened || flags & RecoveryFile) { setReadWrite(true); // enable save button setModified(true); setRecovered(true); } else { if (ret) { if (!(flags & DontAddToRecent)) { KisPart::instance()->addRecentURLToAllMainWindows(_url); } // Detect readonly local-files; remote files are assumed to be writable QFileInfo fi(url.toLocalFile()); setReadWrite(fi.isWritable()); } setRecovered(false); } return ret; } class DlgLoadMessages : public KoDialog { public: DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings) { setWindowTitle(title); setWindowIcon(KisIconUtils::loadIcon("warning")); QWidget *page = new QWidget(this); QVBoxLayout *layout = new QVBoxLayout(page); QHBoxLayout *hlayout = new QHBoxLayout(); QLabel *labelWarning= new QLabel(); labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32)); hlayout->addWidget(labelWarning); hlayout->addWidget(new QLabel(message)); layout->addLayout(hlayout); QTextBrowser *browser = new QTextBrowser(); QString warning = "

"; if (warnings.size() == 1) { warning += " Reason:

"; } else { warning += " Reasons:

"; } warning += "

    "; Q_FOREACH(const QString &w, warnings) { warning += "\n
  • " + w + "
  • "; } warning += "
"; browser->setHtml(warning); browser->setMinimumHeight(200); browser->setMinimumWidth(400); layout->addWidget(browser); setMainWidget(page); setButtons(KoDialog::Ok); resize(minimumSize()); } }; bool KisDocument::openFile() { //dbgUI <<"for" << localFilePath(); if (!QFile::exists(localFilePath()) && !fileBatchMode()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath())); return false; } QString filename = localFilePath(); QString typeName = mimeType(); if (typeName.isEmpty()) { typeName = KisMimeDatabase::mimeTypeForFile(filename); } //qDebug() << "mimetypes 4:" << typeName; // Allow to open backup files, don't keep the mimetype application/x-trash. if (typeName == "application/x-trash") { QString path = filename; while (path.length() > 0) { path.chop(1); typeName = KisMimeDatabase::mimeTypeForFile(path); //qDebug() << "\t" << path << typeName; if (!typeName.isEmpty()) { break; } } //qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName; } dbgUI << localFilePath() << "type:" << typeName; KisMainWindow *window = KisPart::instance()->currentMainwindow(); KoUpdaterPtr updater; if (window && window->viewManager()) { updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document")); d->importExportManager->setUpdater(updater); } KisImportExportErrorCode status = d->importExportManager->importDocument(localFilePath(), typeName); if (!status.isOk()) { if (window && window->viewManager()) { updater->cancel(); } QString msg = status.errorMessage(); if (!msg.isEmpty() && !fileBatchMode()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("Could not open %2.\nReason: %1.", msg, prettyPathOrUrl()), errorMessage().split("\n") + warningMessage().split("\n")); dlg.exec(); } return false; } else if (!warningMessage().isEmpty() && !fileBatchMode()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("There were problems opening %1.", prettyPathOrUrl()), warningMessage().split("\n")); dlg.exec(); setUrl(QUrl()); } setMimeTypeAfterLoading(typeName); d->syncDecorationsWrapperLayerState(); emit sigLoadingFinished(); undoStack()->clear(); return true; } void KisDocument::autoSaveOnPause() { if (!d->modified || !d->modifiedAfterAutosave) return; const QString autoSaveFileName = generateAutoSaveFileName(localFilePath()); QUrl url("file:/" + autoSaveFileName); bool started = exportDocumentSync(url, nativeFormatMimeType()); if (started) { d->modifiedAfterAutosave = false; dbgAndroid << "autoSaveOnPause successful"; } else { qWarning() << "Could not auto-save when paused"; } } // shared between openFile and koMainWindow's "create new empty document" code void KisDocument::setMimeTypeAfterLoading(const QString& mimeType) { d->mimeType = mimeType.toLatin1(); d->outputMimeType = d->mimeType; } bool KisDocument::loadNativeFormat(const QString & file_) { return openUrl(QUrl::fromLocalFile(file_)); } void KisDocument::setModified(bool mod) { if (mod) { updateEditingTime(false); } if (d->isAutosaving) // ignore setModified calls due to autosaving return; if ( !d->readwrite && d->modified ) { errKrita << "Can't set a read-only document to 'modified' !" << endl; return; } //dbgUI<<" url:" << url.path(); //dbgUI<<" mod="<docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod))); d->firstMod = now; } else if (firstModDelta > 60 || forceStoreElapsed) { d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta)); d->firstMod = now; } d->lastMod = now; } QString KisDocument::prettyPathOrUrl() const { QString _url(url().toDisplayString()); #ifdef Q_OS_WIN if (url().isLocalFile()) { _url = QDir::toNativeSeparators(_url); } #endif return _url; } // Get caption from document info (title(), in about page) QString KisDocument::caption() const { QString c; const QString _url(url().fileName()); // if URL is empty...it is probably an unsaved file if (_url.isEmpty()) { c = " [" + i18n("Not Saved") + "] "; } else { c = _url; // Fall back to document URL } return c; } void KisDocument::setTitleModified() { emit titleModified(caption(), isModified()); } QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const { return createDomDocument("krita", tagName, version); } //static QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version) { QDomImplementation impl; QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version); QDomDocumentType dtype = impl.createDocumentType(tagName, QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version), url); // The namespace URN doesn't need to include the version number. QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName); QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype); doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement()); return doc; } bool KisDocument::isNativeFormat(const QByteArray& mimetype) const { if (mimetype == nativeFormatMimeType()) return true; return extraNativeMimeTypes().contains(mimetype); } void KisDocument::setErrorMessage(const QString& errMsg) { d->lastErrorMessage = errMsg; } QString KisDocument::errorMessage() const { return d->lastErrorMessage; } void KisDocument::setWarningMessage(const QString& warningMsg) { d->lastWarningMessage = warningMsg; } QString KisDocument::warningMessage() const { return d->lastWarningMessage; } void KisDocument::removeAutoSaveFiles(const QString &autosaveBaseName, bool wasRecovered) { // Eliminate any auto-save file QString asf = generateAutoSaveFileName(autosaveBaseName); // the one in the current dir if (QFile::exists(asf)) { KisUsageLogger::log(QString("Removing autosave file: %1").arg(asf)); QFile::remove(asf); } asf = generateAutoSaveFileName(QString()); // and the one in $HOME if (QFile::exists(asf)) { KisUsageLogger::log(QString("Removing autosave file: %1").arg(asf)); QFile::remove(asf); } QList expressions; expressions << QRegularExpression("^\\..+-autosave.kra$") << QRegularExpression("^.+-autosave.kra$"); Q_FOREACH(const QRegularExpression &rex, expressions) { if (wasRecovered && !autosaveBaseName.isEmpty() && rex.match(QFileInfo(autosaveBaseName).fileName()).hasMatch() && QFile::exists(autosaveBaseName)) { KisUsageLogger::log(QString("Removing autosave file: %1").arg(autosaveBaseName)); QFile::remove(autosaveBaseName); } } } KoUnit KisDocument::unit() const { return d->unit; } void KisDocument::setUnit(const KoUnit &unit) { if (d->unit != unit) { d->unit = unit; emit unitChanged(unit); } } KUndo2Stack *KisDocument::undoStack() { return d->undoStack; } KisImportExportManager *KisDocument::importExportManager() const { return d->importExportManager; } void KisDocument::addCommand(KUndo2Command *command) { if (command) d->undoStack->push(command); } void KisDocument::beginMacro(const KUndo2MagicString & text) { d->undoStack->beginMacro(text); } void KisDocument::endMacro() { d->undoStack->endMacro(); } void KisDocument::slotUndoStackCleanChanged(bool value) { setModified(!value); } void KisDocument::slotConfigChanged() { KisConfig cfg(true); if (d->undoStack->undoLimit() != cfg.undoStackLimit()) { if (!d->undoStack->isClean()) { d->undoStack->clear(); } d->undoStack->setUndoLimit(cfg.undoStackLimit()); } d->autoSaveDelay = cfg.autoSaveInterval(); setNormalAutoSaveInterval(); } void KisDocument::slotImageRootChanged() { d->syncDecorationsWrapperLayerState(); } void KisDocument::clearUndoHistory() { d->undoStack->clear(); } KisGridConfig KisDocument::gridConfig() const { return d->gridConfig; } void KisDocument::setGridConfig(const KisGridConfig &config) { if (d->gridConfig != config) { d->gridConfig = config; d->syncDecorationsWrapperLayerState(); emit sigGridConfigChanged(config); } } QList &KisDocument::paletteList() { return d->paletteList; } void KisDocument::setPaletteList(const QList &paletteList, bool emitSignal) { if (d->paletteList != paletteList) { QList oldPaletteList = d->paletteList; d->paletteList = paletteList; if (emitSignal) { emit sigPaletteListChanged(oldPaletteList, paletteList); } } } const KisGuidesConfig& KisDocument::guidesConfig() const { return d->guidesConfig; } void KisDocument::setGuidesConfig(const KisGuidesConfig &data) { if (d->guidesConfig == data) return; d->guidesConfig = data; d->syncDecorationsWrapperLayerState(); emit sigGuidesConfigChanged(d->guidesConfig); } const KisMirrorAxisConfig& KisDocument::mirrorAxisConfig() const { return d->mirrorAxisConfig; } void KisDocument::setMirrorAxisConfig(const KisMirrorAxisConfig &config) { if (d->mirrorAxisConfig == config) { return; } d->mirrorAxisConfig = config; setModified(true); emit sigMirrorAxisConfigChanged(); } void KisDocument::resetURL() { setUrl(QUrl()); setLocalFilePath(QString()); } KoDocumentInfoDlg *KisDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const { return new KoDocumentInfoDlg(parent, docInfo); } bool KisDocument::isReadWrite() const { return d->readwrite; } QUrl KisDocument::url() const { return d->m_url; } bool KisDocument::closeUrl(bool promptToSave) { if (promptToSave) { if ( isReadWrite() && isModified()) { Q_FOREACH (KisView *view, KisPart::instance()->views()) { if (view && view->document() == this) { if (!view->queryClose()) { return false; } } } } } // Not modified => ok and delete temp file. d->mimeType = QByteArray(); // It always succeeds for a read-only part, // but the return value exists for reimplementations // (e.g. pressing cancel for a modified read-write part) return true; } void KisDocument::setUrl(const QUrl &url) { d->m_url = url; } QString KisDocument::localFilePath() const { return d->m_file; } void KisDocument::setLocalFilePath( const QString &localFilePath ) { d->m_file = localFilePath; } bool KisDocument::openUrlInternal(const QUrl &url) { if ( !url.isValid() ) { return false; } if (d->m_bAutoDetectedMime) { d->mimeType = QByteArray(); d->m_bAutoDetectedMime = false; } QByteArray mimetype = d->mimeType; if ( !closeUrl() ) { return false; } d->mimeType = mimetype; setUrl(url); d->m_file.clear(); if (d->m_url.isLocalFile()) { d->m_file = d->m_url.toLocalFile(); bool ret; // set the mimetype only if it was not already set (for example, by the host application) if (d->mimeType.isEmpty()) { // get the mimetype of the file // using findByUrl() to avoid another string -> url conversion QString mime = KisMimeDatabase::mimeTypeForFile(d->m_url.toLocalFile()); d->mimeType = mime.toLocal8Bit(); d->m_bAutoDetectedMime = true; } setUrl(d->m_url); ret = openFile(); if (ret) { emit completed(); } else { emit canceled(QString()); } return ret; } return false; } bool KisDocument::newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace* cs, const KoColor &bgColor, KisConfig::BackgroundStyle bgStyle, int numberOfLayers, const QString &description, const double imageResolution) { Q_ASSERT(cs); KisImageSP image; if (!cs) return false; QApplication::setOverrideCursor(Qt::BusyCursor); image = new KisImage(createUndoStore(), width, height, cs, name); Q_CHECK_PTR(image); connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); image->setResolution(imageResolution, imageResolution); image->assignImageProfile(cs->profile()); + image->waitForDone(); + documentInfo()->setAboutInfo("title", name); documentInfo()->setAboutInfo("abstract", description); KisLayerSP layer; if (bgStyle == KisConfig::RASTER_LAYER || bgStyle == KisConfig::FILL_LAYER) { KoColor strippedAlpha = bgColor; strippedAlpha.setOpacity(OPACITY_OPAQUE_U8); if (bgStyle == KisConfig::RASTER_LAYER) { layer = new KisPaintLayer(image.data(), "Background", OPACITY_OPAQUE_U8, cs);; layer->paintDevice()->setDefaultPixel(strippedAlpha); } else if (bgStyle == KisConfig::FILL_LAYER) { KisFilterConfigurationSP filter_config = KisGeneratorRegistry::instance()->get("color")->defaultConfiguration(); filter_config->setProperty("color", strippedAlpha.toQColor()); layer = new KisGeneratorLayer(image.data(), "Background Fill", filter_config, image->globalSelection()); } layer->setOpacity(bgColor.opacityU8()); if (numberOfLayers > 1) { //Lock bg layer if others are present. layer->setUserLocked(true); } } else { // KisConfig::CANVAS_COLOR (needs an unlocked starting layer). image->setDefaultProjectionColor(bgColor); layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs); } Q_CHECK_PTR(layer); image->addNode(layer.data(), image->rootLayer().data()); layer->setDirty(QRect(0, 0, width, height)); setCurrentImage(image); for(int i = 1; i < numberOfLayers; ++i) { KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8, cs); image->addNode(layer, image->root(), i); layer->setDirty(QRect(0, 0, width, height)); } KisConfig cfg(false); cfg.defImageWidth(width); cfg.defImageHeight(height); cfg.defImageResolution(imageResolution); cfg.defColorModel(image->colorSpace()->colorModelId().id()); cfg.setDefaultColorDepth(image->colorSpace()->colorDepthId().id()); cfg.defColorProfile(image->colorSpace()->profile()->name()); KisUsageLogger::log(QString("Created image \"%1\", %2 * %3 pixels, %4 dpi. Color model: %6 %5 (%7). Layers: %8") .arg(name) .arg(width).arg(height) .arg(imageResolution * 72.0) .arg(image->colorSpace()->colorModelId().name()) .arg(image->colorSpace()->colorDepthId().name()) .arg(image->colorSpace()->profile()->name()) .arg(numberOfLayers)); QApplication::restoreOverrideCursor(); return true; } bool KisDocument::isSaving() const { const bool result = d->savingMutex.tryLock(); if (result) { d->savingMutex.unlock(); } return !result; } void KisDocument::waitForSavingToComplete() { if (isSaving()) { KisAsyncActionFeedback f(i18nc("progress dialog message when the user closes the document that is being saved", "Waiting for saving to complete..."), 0); f.waitForMutex(&d->savingMutex); } } KoShapeControllerBase *KisDocument::shapeController() const { return d->shapeController; } KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const { return d->shapeController->shapeForNode(layer); } QList KisDocument::assistants() const { return d->assistants; } void KisDocument::setAssistants(const QList &value) { if (d->assistants != value) { d->assistants = value; d->syncDecorationsWrapperLayerState(); emit sigAssistantsChanged(); } } KisReferenceImagesLayerSP KisDocument::referenceImagesLayer() const { if (!d->image) return KisReferenceImagesLayerSP(); KisReferenceImagesLayerSP referencesLayer = KisLayerUtils::findNodeByType(d->image->root()); return referencesLayer; } void KisDocument::setReferenceImagesLayer(KisSharedPtr layer, bool updateImage) { KisReferenceImagesLayerSP currentReferenceLayer = referenceImagesLayer(); if (currentReferenceLayer == layer) { return; } if (currentReferenceLayer) { currentReferenceLayer->disconnect(this); } if (updateImage) { if (currentReferenceLayer) { d->image->removeNode(currentReferenceLayer); } if (layer) { d->image->addNode(layer); } } currentReferenceLayer = layer; if (currentReferenceLayer) { connect(currentReferenceLayer, SIGNAL(sigUpdateCanvas(QRectF)), this, SIGNAL(sigReferenceImagesChanged())); } emit sigReferenceImagesLayerChanged(layer); } void KisDocument::setPreActivatedNode(KisNodeSP activatedNode) { d->preActivatedNode = activatedNode; } KisNodeSP KisDocument::preActivatedNode() const { return d->preActivatedNode; } KisImageWSP KisDocument::image() const { return d->image; } KisImageSP KisDocument::savingImage() const { return d->savingImage; } void KisDocument::setCurrentImage(KisImageSP image, bool forceInitialUpdate) { if (d->image) { // Disconnect existing sig/slot connections d->image->setUndoStore(new KisDumbUndoStore()); d->image->disconnect(this); d->shapeController->setImage(0); d->image = 0; } if (!image) return; d->setImageAndInitIdleWatcher(image); d->image->setUndoStore(new KisDocumentUndoStore(this)); d->shapeController->setImage(image); setModified(false); connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); connect(d->image, SIGNAL(sigLayersChangedAsync()), this, SLOT(slotImageRootChanged())); if (forceInitialUpdate) { d->image->initialRefreshGraph(); } } void KisDocument::hackPreliminarySetImage(KisImageSP image) { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->image); d->setImageAndInitIdleWatcher(image); d->shapeController->setImage(image); } void KisDocument::setImageModified() { // we only set as modified if undo stack is not at clean state setModified(!d->undoStack->isClean()); } KisUndoStore* KisDocument::createUndoStore() { return new KisDocumentUndoStore(this); } bool KisDocument::isAutosaving() const { return d->isAutosaving; } QString KisDocument::exportErrorToUserMessage(KisImportExportErrorCode status, const QString &errorMessage) { return errorMessage.isEmpty() ? status.errorMessage() : errorMessage; } void KisDocument::setAssistantsGlobalColor(QColor color) { d->globalAssistantsColor = color; } QColor KisDocument::assistantsGlobalColor() { return d->globalAssistantsColor; } QRectF KisDocument::documentBounds() const { QRectF bounds = d->image->bounds(); KisReferenceImagesLayerSP referenceImagesLayer = this->referenceImagesLayer(); if (referenceImagesLayer) { bounds |= referenceImagesLayer->boundingImageRect(); } return bounds; } diff --git a/libs/ui/canvas/kis_display_color_converter.cpp b/libs/ui/canvas/kis_display_color_converter.cpp index 2a81001dd7..a21552bfc1 100644 --- a/libs/ui/canvas/kis_display_color_converter.cpp +++ b/libs/ui/canvas/kis_display_color_converter.cpp @@ -1,698 +1,698 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_display_color_converter.h" #include #include #include #include #include #include #include #include #include #include "kis_config_notifier.h" #include "kis_canvas_resource_provider.h" #include "kis_canvas2.h" #include "KisViewManager.h" #include "kis_image.h" #include "kis_node.h" #include "kundo2command.h" #include "kis_config.h" #include "kis_paint_device.h" #include "kis_iterator_ng.h" #include "kis_fixed_paint_device.h" #include "opengl/KisOpenGLModeProber.h" Q_GLOBAL_STATIC(KisDisplayColorConverter, s_instance) struct KisDisplayColorConverter::Private { Private(KisDisplayColorConverter *_q, KoCanvasResourceProvider *_resourceManager) : q(_q), resourceManager(_resourceManager), nodeColorSpace(0), paintingColorSpace(0), monitorProfile(0), renderingIntent(KoColorConversionTransformation::internalRenderingIntent()), conversionFlags(KoColorConversionTransformation::internalConversionFlags()), displayFilter(0), displayRenderer(new DisplayRenderer(_q, _resourceManager)) { useHDRMode = KisOpenGLModeProber::instance()->useHDRMode(); } KisDisplayColorConverter *const q; KoCanvasResourceProvider *resourceManager; const KoColorSpace *nodeColorSpace; const KoColorSpace *paintingColorSpace; const KoColorProfile* inputImageProfile = 0; const KoColorProfile* qtWidgetsProfile() const { return useHDRMode ? KoColorSpaceRegistry::instance()->p709SRGBProfile() : monitorProfile; } const KoColorProfile* openGLSurfaceProfile() const { return useHDRMode && openGLCanvasIsActive ? KisOpenGLModeProber::instance()->rootSurfaceColorProfile() : monitorProfile; } const KoColorProfile* ocioInputProfile() const { return displayFilter && displayFilter->useInternalColorManagement() ? openGLSurfaceProfile() : inputImageProfile; } const KoColorProfile* ocioOutputProfile() const { return openGLSurfaceProfile(); } const KoColorSpace* ocioInputColorSpace() const { return KoColorSpaceRegistry::instance()-> colorSpace( RGBAColorModelID.id(), Float32BitsColorDepthID.id(), ocioInputProfile()); } const KoColorSpace* ocioOutputColorSpace() const { return KoColorSpaceRegistry::instance()-> colorSpace( RGBAColorModelID.id(), Float32BitsColorDepthID.id(), ocioOutputProfile()); } const KoColorSpace* qtWidgetsColorSpace() const { return KoColorSpaceRegistry::instance()-> colorSpace( RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), qtWidgetsProfile()); } const KoColorSpace* openGLSurfaceColorSpace(const KoID &bitDepthId) const { return KoColorSpaceRegistry::instance()-> colorSpace( RGBAColorModelID.id(), bitDepthId.id(), openGLSurfaceProfile()); } const KoColorSpace* intermediateColorSpace() const { // the color space where we apply exposure and // gamma should always be linear return KoColorSpaceRegistry::instance()-> colorSpace( RGBAColorModelID.id(), Float32BitsColorDepthID.id(), KoColorSpaceRegistry::instance()->p2020G10Profile()); } const KoColorProfile *monitorProfile; KoColorConversionTransformation::Intent renderingIntent; KoColorConversionTransformation::ConversionFlags conversionFlags; QSharedPointer displayFilter; KoColor intermediateFgColor; KisNodeSP connectedNode; KisImageSP image; bool useHDRMode = false; bool openGLCanvasIsActive = false; inline KoColor approximateFromQColor(const QColor &qcolor); inline QColor approximateToQColor(const KoColor &color); void slotCanvasResourceChanged(int key, const QVariant &v); void slotUpdateCurrentNodeColorSpace(); void selectPaintingColorSpace(); void updateIntermediateFgColor(const KoColor &color); void setCurrentNode(KisNodeSP node); bool useOcio() const; class DisplayRenderer : public KoColorDisplayRendererInterface { public: DisplayRenderer(KisDisplayColorConverter *displayColorConverter, KoCanvasResourceProvider *resourceManager) : m_displayColorConverter(displayColorConverter), m_resourceManager(resourceManager) { displayColorConverter->connect(displayColorConverter, SIGNAL(displayConfigurationChanged()), this, SIGNAL(displayConfigurationChanged()), Qt::UniqueConnection); } QImage convertToQImage(const KoColorSpace *srcColorSpace, const quint8 *data, qint32 width, qint32 height) const override { KisPaintDeviceSP dev = new KisPaintDevice(srcColorSpace); dev->writeBytes(data, 0, 0, width, height); return m_displayColorConverter->toQImage(dev); } QColor toQColor(const KoColor &c) const override { return m_displayColorConverter->toQColor(c); } KoColor approximateFromRenderedQColor(const QColor &c) const override { return m_displayColorConverter->approximateFromRenderedQColor(c); } KoColor fromHsv(int h, int s, int v, int a) const override { return m_displayColorConverter->fromHsv(h, s, v, a); } void getHsv(const KoColor &srcColor, int *h, int *s, int *v, int *a) const override { m_displayColorConverter->getHsv(srcColor, h, s, v, a); } qreal minVisibleFloatValue(const KoChannelInfo *chaninfo) const override { return chaninfo->getUIMin(); } qreal maxVisibleFloatValue(const KoChannelInfo *chaninfo) const override { qreal maxValue = chaninfo->getUIMax(); if (m_resourceManager) { qreal exposure = m_resourceManager->resource(KisCanvasResourceProvider::HdrExposure).value(); // not sure if *= is what we want maxValue *= std::pow(2.0, -exposure); } return maxValue; } const KoColorSpace* getPaintingColorSpace() const override { return m_displayColorConverter->paintingColorSpace(); } private: KisDisplayColorConverter *m_displayColorConverter; QPointer m_resourceManager; }; QScopedPointer displayRenderer; }; KisDisplayColorConverter::KisDisplayColorConverter(KoCanvasResourceProvider *resourceManager, QObject *parent) : QObject(parent), m_d(new Private(this, resourceManager)) { connect(m_d->resourceManager, SIGNAL(canvasResourceChanged(int,QVariant)), SLOT(slotCanvasResourceChanged(int,QVariant))); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(selectPaintingColorSpace())); m_d->inputImageProfile = KoColorSpaceRegistry::instance()->p709SRGBProfile(); m_d->setCurrentNode(0); setMonitorProfile(0); setDisplayFilter(QSharedPointer(0)); } KisDisplayColorConverter::KisDisplayColorConverter() : m_d(new Private(this, 0)) { setDisplayFilter(QSharedPointer(0)); m_d->inputImageProfile = KoColorSpaceRegistry::instance()->p709SRGBProfile(); m_d->paintingColorSpace = KoColorSpaceRegistry::instance()->rgb8(); m_d->setCurrentNode(0); setMonitorProfile(0); } KisDisplayColorConverter::~KisDisplayColorConverter() { } void KisDisplayColorConverter::setImageColorSpace(const KoColorSpace *cs) { m_d->inputImageProfile = cs->colorModelId() == RGBAColorModelID ? cs->profile() : KoColorSpaceRegistry::instance()->p709SRGBProfile(); emit displayConfigurationChanged(); } KisDisplayColorConverter* KisDisplayColorConverter::dumbConverterInstance() { return s_instance; } KoColorDisplayRendererInterface* KisDisplayColorConverter::displayRendererInterface() const { return m_d->displayRenderer.data(); } bool KisDisplayColorConverter::Private::useOcio() const { return displayFilter && paintingColorSpace && paintingColorSpace->colorModelId() == RGBAColorModelID; } void KisDisplayColorConverter::Private::updateIntermediateFgColor(const KoColor &srcColor) { KIS_ASSERT_RECOVER_RETURN(displayFilter); KoColor color = srcColor; color.convertTo(intermediateColorSpace()); displayFilter->approximateForwardTransformation(color.data(), 1); intermediateFgColor = color; } void KisDisplayColorConverter::Private::slotCanvasResourceChanged(int key, const QVariant &v) { if (key == KisCanvasResourceProvider::CurrentKritaNode) { KisNodeSP currentNode = v.value(); setCurrentNode(currentNode); } else if (useOcio() && key == KoCanvasResourceProvider::ForegroundColor) { updateIntermediateFgColor(v.value()); } } void KisDisplayColorConverter::Private::slotUpdateCurrentNodeColorSpace() { setCurrentNode(connectedNode); } inline KisPaintDeviceSP findValidDevice(KisNodeSP node) { return node->paintDevice() ? node->paintDevice() : node->original(); } void KisDisplayColorConverter::Private::setCurrentNode(KisNodeSP node) { if (connectedNode) { KisPaintDeviceSP device = findValidDevice(connectedNode); if (device) { q->disconnect(device, 0); } } nodeColorSpace = 0; if (node) { KisPaintDeviceSP device = findValidDevice(node); nodeColorSpace = device ? device->compositionSourceColorSpace() : node->colorSpace(); KIS_SAFE_ASSERT_RECOVER_NOOP(nodeColorSpace); if (device) { q->connect(device, SIGNAL(profileChanged(const KoColorProfile*)), SLOT(slotUpdateCurrentNodeColorSpace()), Qt::UniqueConnection); q->connect(device, SIGNAL(colorSpaceChanged(const KoColorSpace*)), SLOT(slotUpdateCurrentNodeColorSpace()), Qt::UniqueConnection); } } if (!nodeColorSpace) { nodeColorSpace = KoColorSpaceRegistry::instance()->rgb8(); } connectedNode = node; selectPaintingColorSpace(); } void KisDisplayColorConverter::Private::selectPaintingColorSpace() { KisConfig cfg(true); paintingColorSpace = cfg.customColorSelectorColorSpace(); if (!paintingColorSpace || displayFilter) { paintingColorSpace = nodeColorSpace; } emit q->displayConfigurationChanged(); } const KoColorSpace* KisDisplayColorConverter::paintingColorSpace() const { KIS_ASSERT_RECOVER(m_d->paintingColorSpace) { return KoColorSpaceRegistry::instance()->rgb8(); } return m_d->paintingColorSpace; } void KisDisplayColorConverter::setMonitorProfile(const KoColorProfile *monitorProfile) { if (m_d->useHDRMode) { // we don't use ICCcolor management in HDR mode monitorProfile = KoColorSpaceRegistry::instance()->p709SRGBProfile(); } m_d->monitorProfile = monitorProfile; m_d->renderingIntent = renderingIntent(); m_d->conversionFlags = conversionFlags(); emit displayConfigurationChanged(); } void KisDisplayColorConverter::setDisplayFilter(QSharedPointer displayFilter) { if (m_d->displayFilter && displayFilter && displayFilter->lockCurrentColorVisualRepresentation()) { KoColor color(m_d->intermediateFgColor); displayFilter->approximateInverseTransformation(color.data(), 1); color.convertTo(m_d->paintingColorSpace); m_d->resourceManager->setForegroundColor(color); } m_d->displayFilter = displayFilter; if (m_d->displayFilter) { m_d->updateIntermediateFgColor( m_d->resourceManager->foregroundColor()); } { // sanity check // KisConfig cfg; // KIS_ASSERT_RECOVER_NOOP(cfg.useOcio() == (bool) m_d->displayFilter); } m_d->selectPaintingColorSpace(); } KoColorConversionTransformation::Intent KisDisplayColorConverter::renderingIntent() { KisConfig cfg(true); return (KoColorConversionTransformation::Intent)cfg.monitorRenderIntent(); } KoColorConversionTransformation::ConversionFlags KisDisplayColorConverter::conversionFlags() { KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::HighQuality; KisConfig cfg(true); if (cfg.useBlackPointCompensation()) conversionFlags |= KoColorConversionTransformation::BlackpointCompensation; if (!cfg.allowLCMSOptimization()) conversionFlags |= KoColorConversionTransformation::NoOptimization; return conversionFlags; } QSharedPointer KisDisplayColorConverter::displayFilter() const { return m_d->displayFilter; } const KoColorProfile* KisDisplayColorConverter::monitorProfile() const { return m_d->monitorProfile; } const KoColorProfile* KisDisplayColorConverter::openGLCanvasSurfaceProfile() const { return m_d->openGLSurfaceProfile(); } bool KisDisplayColorConverter::isHDRMode() const { return m_d->useHDRMode; } void KisDisplayColorConverter::notifyOpenGLCanvasIsActive(bool value) { m_d->openGLCanvasIsActive = value; emit displayConfigurationChanged(); } QColor KisDisplayColorConverter::toQColor(const KoColor &srcColor) const { KoColor c(srcColor); if (m_d->useOcio()) { KIS_ASSERT_RECOVER(m_d->ocioInputColorSpace()->pixelSize() == 16) { return QColor(Qt::green); } c.convertTo(m_d->ocioInputColorSpace()); m_d->displayFilter->filter(c.data(), 1); c.setProfile(m_d->ocioOutputProfile()); } // we expect the display profile is rgb8, which is BGRA here KIS_ASSERT_RECOVER(m_d->qtWidgetsColorSpace()->pixelSize() == 4) { return QColor(Qt::red); } c.convertTo(m_d->qtWidgetsColorSpace(), m_d->renderingIntent, m_d->conversionFlags); const quint8 *p = c.data(); return QColor(p[2], p[1], p[0], p[3]); } KoColor KisDisplayColorConverter::applyDisplayFiltering(const KoColor &srcColor, const KoID &bitDepthId) const { KoColor c(srcColor); if (m_d->useOcio()) { KIS_ASSERT_RECOVER(m_d->ocioInputColorSpace()->pixelSize() == 16) { return srcColor; } c.convertTo(m_d->ocioInputColorSpace()); m_d->displayFilter->filter(c.data(), 1); c.setProfile(m_d->ocioOutputProfile()); } c.convertTo(m_d->openGLSurfaceColorSpace(bitDepthId), m_d->renderingIntent, m_d->conversionFlags); return c; } bool KisDisplayColorConverter::canSkipDisplayConversion(const KoColorSpace *cs) const { const KoColorProfile *displayProfile = m_d->openGLSurfaceProfile(); return !m_d->useOcio() && cs->colorModelId() == RGBAColorModelID && (!!cs->profile() == !!displayProfile) && (!cs->profile() || cs->profile()->uniqueId() == displayProfile->uniqueId()); } KoColor KisDisplayColorConverter::approximateFromRenderedQColor(const QColor &c) const { return m_d->approximateFromQColor(c); } QImage KisDisplayColorConverter::toQImage(KisPaintDeviceSP srcDevice) const { KisPaintDeviceSP device = srcDevice; QRect bounds = srcDevice->exactBounds(); if (bounds.isEmpty()) return QImage(); if (m_d->useOcio()) { KIS_ASSERT_RECOVER(m_d->ocioInputColorSpace()->pixelSize() == 16) { return QImage(); } device = new KisPaintDevice(*srcDevice); device->convertTo(m_d->ocioInputColorSpace()); KisSequentialIterator it(device, bounds); int numConseqPixels = it.nConseqPixels(); while (it.nextPixels(numConseqPixels)) { numConseqPixels = it.nConseqPixels(); m_d->displayFilter->filter(it.rawData(), numConseqPixels); } - device->setProfile(m_d->ocioOutputProfile()); + device->setProfile(m_d->ocioOutputProfile(), 0); } // we expect the display profile is rgb8, which is BGRA here KIS_ASSERT_RECOVER(m_d->qtWidgetsColorSpace()->pixelSize() == 4) { return QImage(); } return device->convertToQImage(m_d->qtWidgetsProfile(), bounds, m_d->renderingIntent, m_d->conversionFlags); } void KisDisplayColorConverter::applyDisplayFilteringF32(KisFixedPaintDeviceSP device, const KoID &bitDepthId) const { /** * This method is optimized for the case when device is already in 32f * version of the pating color space. */ KIS_SAFE_ASSERT_RECOVER_RETURN(device->colorSpace()->colorDepthId() == Float32BitsColorDepthID); KIS_SAFE_ASSERT_RECOVER_RETURN(device->colorSpace()->colorModelId() == RGBAColorModelID); KIS_SAFE_ASSERT_RECOVER_RETURN(device->bounds().isValid()); if (m_d->useOcio()) { KIS_ASSERT_RECOVER_RETURN(m_d->ocioInputColorSpace()->pixelSize() == 16); device->convertTo(m_d->ocioInputColorSpace()); m_d->displayFilter->filter(device->data(), device->bounds().width() * device->bounds().height()); device->setProfile(m_d->ocioOutputProfile()); } device->convertTo(m_d->openGLSurfaceColorSpace(bitDepthId)); } KoColor KisDisplayColorConverter::Private::approximateFromQColor(const QColor &qcolor) { if (!useOcio()) { return KoColor(qcolor, paintingColorSpace); } else { KoColor color(qcolor, intermediateColorSpace()); displayFilter->approximateInverseTransformation(color.data(), 1); color.convertTo(paintingColorSpace); return color; } qFatal("Must not be reachable"); return KoColor(); } QColor KisDisplayColorConverter::Private::approximateToQColor(const KoColor &srcColor) { KoColor color(srcColor); if (useOcio()) { color.convertTo(intermediateColorSpace()); displayFilter->approximateForwardTransformation(color.data(), 1); } return color.toQColor(); } KoColor KisDisplayColorConverter::fromHsv(int h, int s, int v, int a) const { // generate HSV from sRGB! QColor qcolor(QColor::fromHsv(h, s, v, a)); return m_d->approximateFromQColor(qcolor); } void KisDisplayColorConverter::getHsv(const KoColor &srcColor, int *h, int *s, int *v, int *a) const { // we are going through sRGB here! QColor color = m_d->approximateToQColor(srcColor); color.getHsv(h, s, v, a); } KoColor KisDisplayColorConverter::fromHsvF(qreal h, qreal s, qreal v, qreal a) { // generate HSV from sRGB! QColor qcolor(QColor::fromHsvF(h, s, v, a)); return m_d->approximateFromQColor(qcolor); } void KisDisplayColorConverter::getHsvF(const KoColor &srcColor, qreal *h, qreal *s, qreal *v, qreal *a) { // we are going through sRGB here! QColor color = m_d->approximateToQColor(srcColor); color.getHsvF(h, s, v, a); } KoColor KisDisplayColorConverter::fromHslF(qreal h, qreal s, qreal l, qreal a) { // generate HSL from sRGB! QColor qcolor(QColor::fromHslF(h, s, l, a)); if (!qcolor.isValid()) { warnKrita << "Could not construct valid color from h" << h << "s" << s << "l" << l << "a" << a; qcolor = Qt::black; } return m_d->approximateFromQColor(qcolor); } void KisDisplayColorConverter::getHslF(const KoColor &srcColor, qreal *h, qreal *s, qreal *l, qreal *a) { // we are going through sRGB here! QColor color = m_d->approximateToQColor(srcColor); color.getHslF(h, s, l, a); } KoColor KisDisplayColorConverter::fromHsiF(qreal h, qreal s, qreal i) { // generate HSI from sRGB! qreal r=0.0; qreal g=0.0; qreal b=0.0; qreal a=1.0; HSIToRGB(h, s, i, &r, &g, &b); QColor qcolor; qcolor.setRgbF(qBound(0.0,r,1.0), qBound(0.0,g,1.0), qBound(0.0,b,1.0), a); return m_d->approximateFromQColor(qcolor); } void KisDisplayColorConverter::getHsiF(const KoColor &srcColor, qreal *h, qreal *s, qreal *i) { // we are going through sRGB here! QColor color = m_d->approximateToQColor(srcColor); qreal r=color.redF(); qreal g=color.greenF(); qreal b=color.blueF(); RGBToHSI(r, g, b, h, s, i); } KoColor KisDisplayColorConverter::fromHsyF(qreal h, qreal s, qreal y, qreal R, qreal G, qreal B, qreal gamma) { // generate HSL from sRGB! QVector channelValues(3); y = pow(y, gamma); HSYToRGB(h, s, y, &channelValues[0], &channelValues[1], &channelValues[2], R, G, B); KoColorSpaceRegistry::instance()->rgb8()->profile()->delinearizeFloatValueFast(channelValues); QColor qcolor; qcolor.setRgbF(qBound(0.0,channelValues[0],1.0), qBound(0.0,channelValues[1],1.0), qBound(0.0,channelValues[2],1.0), 1.0); return m_d->approximateFromQColor(qcolor); } void KisDisplayColorConverter::getHsyF(const KoColor &srcColor, qreal *h, qreal *s, qreal *y, qreal R, qreal G, qreal B, qreal gamma) { // we are going through sRGB here! QColor color = m_d->approximateToQColor(srcColor); QVector channelValues(3); channelValues[0]=color.redF(); channelValues[1]=color.greenF(); channelValues[2]=color.blueF(); //TODO: if we're going to have KoColor here, remember to check whether the TRC of the profile exists... KoColorSpaceRegistry::instance()->rgb8()->profile()->linearizeFloatValueFast(channelValues); RGBToHSY(channelValues[0], channelValues[1], channelValues[2], h, s, y, R, G, B); *y = pow(*y, 1/gamma); } #include "moc_kis_display_color_converter.cpp" diff --git a/plugins/impex/libkra/kis_kra_load_visitor.cpp b/plugins/impex/libkra/kis_kra_load_visitor.cpp index 51716bcebc..c11dc50054 100644 --- a/plugins/impex/libkra/kis_kra_load_visitor.cpp +++ b/plugins/impex/libkra/kis_kra_load_visitor.cpp @@ -1,784 +1,784 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * * 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_kra_load_visitor.h" #include "kis_kra_tags.h" #include "flake/kis_shape_layer.h" #include "flake/KisReferenceImagesLayer.h" #include "KisReferenceImage.h" #include #include #include #include #include #include #include #include #include #include #include #include #include // kritaimage #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_transform_mask_params_factory_registry.h" #include #include #include #include #include "kis_shape_selection.h" #include "kis_colorize_dom_utils.h" #include "kis_dom_utils.h" #include "kis_raster_keyframe_channel.h" #include "kis_paint_device_frames_interface.h" #include "kis_filter_registry.h" using namespace KRA; QString expandEncodedDirectory(const QString& _intern) { QString intern = _intern; QString result; int pos; while ((pos = intern.indexOf('/')) != -1) { if (QChar(intern.at(0)).isDigit()) result += "part"; result += intern.left(pos + 1); // copy numbers (or "pictures") + "/" intern = intern.mid(pos + 1); // remove the dir we just processed } if (!intern.isEmpty() && QChar(intern.at(0)).isDigit()) result += "part"; result += intern; return result; } KisKraLoadVisitor::KisKraLoadVisitor(KisImageSP image, KoStore *store, KoShapeControllerBase *shapeController, QMap &layerFilenames, QMap &keyframeFilenames, const QString & name, int syntaxVersion) : KisNodeVisitor() , m_image(image) , m_store(store) , m_external(false) , m_layerFilenames(layerFilenames) , m_keyframeFilenames(keyframeFilenames) , m_name(name) , m_shapeController(shapeController) { m_store->pushDirectory(); if (!m_store->enterDirectory(m_name)) { QStringList directories = m_store->directoryList(); dbgKrita << directories; if (directories.size() > 0) { dbgFile << "Could not locate the directory, maybe some encoding issue? Grab the first directory, that'll be the image one." << m_name << directories; m_name = directories.first(); } else { dbgFile << "Could not enter directory" << m_name << ", probably an old-style file with 'part' added."; m_name = expandEncodedDirectory(m_name); } } else { m_store->popDirectory(); } m_syntaxVersion = syntaxVersion; } void KisKraLoadVisitor::setExternalUri(const QString &uri) { m_external = true; m_uri = uri; } bool KisKraLoadVisitor::visit(KisExternalLayer * layer) { bool result = false; if (auto *referencesLayer = dynamic_cast(layer)) { Q_FOREACH(KoShape *shape, referencesLayer->shapes()) { auto *reference = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(reference, false); while (!reference->loadImage(m_store)) { if (reference->embed()) { m_errorMessages << i18n("Could not load embedded reference image %1 ", reference->internalFile()); break; } else { QString msg = i18nc( "@info", "A reference image linked to an external file could not be loaded.\n\n" "Path: %1\n\n" "Do you want to select another location?", reference->filename()); int locateManually = QMessageBox::warning(0, i18nc("@title:window", "File not found"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); QString url; if (locateManually == QMessageBox::Yes) { KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); url = dialog.filename(); } if (url.isEmpty()) { break; } else { reference->setFilename(url); } } } } } else if (KisShapeLayer *shapeLayer = dynamic_cast(layer)) { if (!loadMetaData(layer)) { return false; } m_store->pushDirectory(); m_store->enterDirectory(getLocation(layer, DOT_SHAPE_LAYER)) ; result = shapeLayer->loadLayer(m_store); m_store->popDirectory(); } result = visitAll(layer) && result; return result; } bool KisKraLoadVisitor::visit(KisPaintLayer *layer) { loadNodeKeyframes(layer); if (!loadPaintDevice(layer->paintDevice(), getLocation(layer))) { return false; } if (!loadProfile(layer->paintDevice(), getLocation(layer, DOT_ICC))) { return false; } if (!loadMetaData(layer)) { return false; } if (m_syntaxVersion == 1) { // Check whether there is a file with a .mask extension in the // layer directory, if so, it's an old-style transparency mask // that should be converted. QString location = getLocation(layer, ".mask"); if (m_store->open(location)) { KisSelectionSP selection = KisSelectionSP(new KisSelection()); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); if (!pixelSelection->read(m_store->device())) { pixelSelection->disconnect(); } else { KisTransparencyMask* mask = new KisTransparencyMask(); mask->setSelection(selection); m_image->addNode(mask, layer, layer->firstChild()); } m_store->close(); } } bool result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisGroupLayer *layer) { if (*layer->colorSpace() != *m_image->colorSpace()) { layer->resetCache(m_image->colorSpace()); } if (!loadMetaData(layer)) { return false; } bool result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisAdjustmentLayer* layer) { loadNodeKeyframes(layer); // Adjustmentlayers are tricky: there's the 1.x style and the 2.x // style, which has selections with selection components bool result = true; if (m_syntaxVersion == 1) { KisSelectionSP selection = new KisSelection(); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); result = loadPaintDevice(pixelSelection, getLocation(layer, ".selection")); layer->setInternalSelection(selection); } else if (m_syntaxVersion == 2) { result = loadSelection(getLocation(layer), layer->internalSelection()); } else { // We use the default, empty selection } if (!loadMetaData(layer)) { return false; } loadFilterConfiguration(layer, getLocation(layer, DOT_FILTERCONFIG)); fixOldFilterConfigurations(layer->filter()); result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisGeneratorLayer *layer) { if (!loadMetaData(layer)) { return false; } bool result = true; loadNodeKeyframes(layer); result = loadSelection(getLocation(layer), layer->internalSelection()); // HACK ALERT: we set the same filter again to ensure the layer // is correctly updated result = loadFilterConfiguration(layer, getLocation(layer, DOT_FILTERCONFIG)); layer->setFilter(layer->filter()); result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisCloneLayer *layer) { if (!loadMetaData(layer)) { return false; } // the layer might have already been lazily initialized // from the mask loading code if (layer->copyFrom()) { return true; } KisNodeSP srcNode = layer->copyFromInfo().findNode(m_image->rootLayer()); KisLayerSP srcLayer = qobject_cast(srcNode.data()); Q_ASSERT(srcLayer); layer->setCopyFrom(srcLayer); // Clone layers have no data except for their masks bool result = visitAll(layer); return result; } void KisKraLoadVisitor::initSelectionForMask(KisMask *mask) { KisLayer *cloneLayer = dynamic_cast(mask->parent().data()); if (cloneLayer) { // the clone layers should be initialized out of order // and lazily, because their original() is still not // initialized cloneLayer->accept(*this); } KisLayer *parentLayer = qobject_cast(mask->parent().data()); // the KisKraLoader must have already set the parent for us Q_ASSERT(parentLayer); mask->initSelection(parentLayer); } bool KisKraLoadVisitor::visit(KisFilterMask *mask) { initSelectionForMask(mask); loadNodeKeyframes(mask); bool result = true; result = loadSelection(getLocation(mask), mask->selection()); result = loadFilterConfiguration(mask, getLocation(mask, DOT_FILTERCONFIG)); fixOldFilterConfigurations(mask->filter()); return result; } bool KisKraLoadVisitor::visit(KisTransformMask *mask) { QString location = getLocation(mask, DOT_TRANSFORMCONFIG); if (m_store->hasFile(location)) { QByteArray data; m_store->open(location); data = m_store->read(m_store->size()); m_store->close(); if (!data.isEmpty()) { QDomDocument doc; doc.setContent(data); QDomElement rootElement = doc.documentElement(); QDomElement main; if (!KisDomUtils::findOnlyElement(rootElement, "main", &main/*, &m_errorMessages*/)) { return false; } QString id = main.attribute("id", "not-valid"); if (id == "not-valid") { m_errorMessages << i18n("Could not load \"id\" of the transform mask"); return false; } QDomElement data; if (!KisDomUtils::findOnlyElement(rootElement, "data", &data, &m_errorMessages)) { return false; } KisTransformMaskParamsInterfaceSP params = KisTransformMaskParamsFactoryRegistry::instance()->createParams(id, data); if (!params) { m_errorMessages << i18n("Could not create transform mask params"); return false; } mask->setTransformParams(params); loadNodeKeyframes(mask); params->clearChangedFlag(); return true; } } return false; } bool KisKraLoadVisitor::visit(KisTransparencyMask *mask) { initSelectionForMask(mask); loadNodeKeyframes(mask); return loadSelection(getLocation(mask), mask->selection()); } bool KisKraLoadVisitor::visit(KisSelectionMask *mask) { initSelectionForMask(mask); return loadSelection(getLocation(mask), mask->selection()); } bool KisKraLoadVisitor::visit(KisColorizeMask *mask) { m_store->pushDirectory(); QString location = getLocation(mask, DOT_COLORIZE_MASK); m_store->enterDirectory(location) ; QByteArray data; if (!m_store->extractFile("content.xml", data)) return false; QDomDocument doc; if (!doc.setContent(data)) return false; QVector strokes; if (!KisDomUtils::loadValue(doc.documentElement(), COLORIZE_KEYSTROKES_SECTION, &strokes, mask->colorSpace())) return false; int i = 0; Q_FOREACH (const KisLazyFillTools::KeyStroke &stroke, strokes) { const QString fileName = QString("%1_%2").arg(COLORIZE_KEYSTROKE).arg(i++); loadPaintDevice(stroke.dev, fileName); } mask->setKeyStrokesDirect(QList::fromVector(strokes)); loadPaintDevice(mask->coloringProjection(), COLORIZE_COLORING_DEVICE); mask->resetCache(); m_store->popDirectory(); return true; } QStringList KisKraLoadVisitor::errorMessages() const { return m_errorMessages; } QStringList KisKraLoadVisitor::warningMessages() const { return m_warningMessages; } struct SimpleDevicePolicy { bool read(KisPaintDeviceSP dev, QIODevice *stream) { return dev->read(stream); } void setDefaultPixel(KisPaintDeviceSP dev, const KoColor &defaultPixel) const { return dev->setDefaultPixel(defaultPixel); } }; struct FramedDevicePolicy { FramedDevicePolicy(int frameId) : m_frameId(frameId) {} bool read(KisPaintDeviceSP dev, QIODevice *stream) { return dev->framesInterface()->readFrame(stream, m_frameId); } void setDefaultPixel(KisPaintDeviceSP dev, const KoColor &defaultPixel) const { return dev->framesInterface()->setFrameDefaultPixel(defaultPixel, m_frameId); } int m_frameId; }; bool KisKraLoadVisitor::loadPaintDevice(KisPaintDeviceSP device, const QString& location) { // Layer data KisPaintDeviceFramesInterface *frameInterface = device->framesInterface(); QList frames; if (frameInterface) { frames = device->framesInterface()->frames(); } if (!frameInterface || frames.count() <= 1) { return loadPaintDeviceFrame(device, location, SimpleDevicePolicy()); } else { KisRasterKeyframeChannel *keyframeChannel = device->keyframeChannel(); for (int i = 0; i < frames.count(); i++) { int id = frames[i]; if (keyframeChannel->frameFilename(id).isEmpty()) { m_warningMessages << i18n("Could not find keyframe pixel data for frame %1 in %2.", id, location); } else { Q_ASSERT(!keyframeChannel->frameFilename(id).isEmpty()); QString frameFilename = getLocation(keyframeChannel->frameFilename(id)); Q_ASSERT(!frameFilename.isEmpty()); if (!loadPaintDeviceFrame(device, frameFilename, FramedDevicePolicy(id))) { m_warningMessages << i18n("Could not load keyframe pixel data for frame %1 in %2.", id, location); } } } } return true; } template bool KisKraLoadVisitor::loadPaintDeviceFrame(KisPaintDeviceSP device, const QString &location, DevicePolicy policy) { { const int pixelSize = device->colorSpace()->pixelSize(); KoColor color(Qt::transparent, device->colorSpace()); if (m_store->open(location + ".defaultpixel")) { if (m_store->size() == pixelSize) { m_store->read((char*)color.data(), pixelSize); } m_store->close(); } policy.setDefaultPixel(device, color); } if (m_store->open(location)) { if (!policy.read(device, m_store->device())) { m_warningMessages << i18n("Could not read pixel data: %1.", location); device->disconnect(); m_store->close(); return true; } m_store->close(); } else { m_warningMessages << i18n("Could not load pixel data: %1.", location); return true; } return true; } bool KisKraLoadVisitor::loadProfile(KisPaintDeviceSP device, const QString& location) { if (m_store->hasFile(location)) { m_store->open(location); QByteArray data; data.resize(m_store->size()); dbgFile << "Data to load: " << m_store->size() << " from " << location << " with color space " << device->colorSpace()->id(); int read = m_store->read(data.data(), m_store->size()); dbgFile << "Profile size: " << data.size() << " " << m_store->atEnd() << " " << m_store->device()->bytesAvailable() << " " << read; m_store->close(); KoHashGenerator *hashGenerator = KoHashGeneratorProvider::instance()->getGenerator("MD5"); QByteArray hash = hashGenerator->generateHash(data); if (m_profileCache.contains(hash)) { - if (device->setProfile(m_profileCache[hash])) { + if (device->setProfile(m_profileCache[hash], 0)) { return true; } } else { // Create a colorspace with the embedded profile const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(device->colorSpace()->colorModelId().id(), device->colorSpace()->colorDepthId().id(), data); m_profileCache[hash] = profile; - if (device->setProfile(profile)) { + if (device->setProfile(profile, 0)) { return true; } } } m_warningMessages << i18n("Could not load profile: %1.", location); return true; } bool KisKraLoadVisitor::loadFilterConfiguration(KisNodeFilterInterface *nodeInterface, const QString& location) { KisFilterConfigurationSP kfc = nodeInterface->filter(); if (m_store->hasFile(location)) { QByteArray data; m_store->open(location); data = m_store->read(m_store->size()); m_store->close(); if (!data.isEmpty()) { QDomDocument doc; doc.setContent(data); QDomElement e = doc.documentElement(); if (e.tagName() == "filterconfig") { kfc->fromLegacyXML(e); } else { kfc->fromXML(e); } loadDeprecatedFilter(kfc); return true; } } m_warningMessages << i18n("Could not filter configuration %1.", location); return true; } void KisKraLoadVisitor::fixOldFilterConfigurations(KisFilterConfigurationSP kfc) { KisFilterSP filter = KisFilterRegistry::instance()->value(kfc->name()); KIS_SAFE_ASSERT_RECOVER_RETURN(filter); if (!filter->configurationAllowedForMask(kfc)) { filter->fixLoadedFilterConfigurationForMasks(kfc); } KIS_SAFE_ASSERT_RECOVER_NOOP(filter->configurationAllowedForMask(kfc)); } bool KisKraLoadVisitor::loadMetaData(KisNode* node) { KisLayer* layer = qobject_cast(node); if (!layer) return true; KisMetaData::IOBackend* backend = KisMetaData::IOBackendRegistry::instance()->get("xmp"); if (!backend || !backend->supportLoading()) { if (backend) dbgFile << "Backend " << backend->id() << " does not support loading."; else dbgFile << "Could not load the XMP backend at all"; return true; } QString location = getLocation(node, QString(".") + backend->id() + DOT_METADATA); dbgFile << "going to load " << backend->id() << ", " << backend->name() << " from " << location; if (m_store->hasFile(location)) { QByteArray data; m_store->open(location); data = m_store->read(m_store->size()); m_store->close(); QBuffer buffer(&data); if (!backend->loadFrom(layer->metaData(), &buffer)) { m_warningMessages << i18n("Could not load metadata for layer %1.", layer->name()); } } return true; } bool KisKraLoadVisitor::loadSelection(const QString& location, KisSelectionSP dstSelection) { // by default the selection is expected to be fully transparent { KisPixelSelectionSP pixelSelection = dstSelection->pixelSelection(); KoColor transparent(Qt::transparent, pixelSelection->colorSpace()); pixelSelection->setDefaultPixel(transparent); } // Pixel selection bool result = true; QString pixelSelectionLocation = location + DOT_PIXEL_SELECTION; if (m_store->hasFile(pixelSelectionLocation)) { KisPixelSelectionSP pixelSelection = dstSelection->pixelSelection(); result = loadPaintDevice(pixelSelection, pixelSelectionLocation); if (!result) { m_warningMessages << i18n("Could not load raster selection %1.", location); } pixelSelection->invalidateOutlineCache(); } // Shape selection QString shapeSelectionLocation = location + DOT_SHAPE_SELECTION; if (m_store->hasFile(shapeSelectionLocation + "/content.svg") || m_store->hasFile(shapeSelectionLocation + "/content.xml")) { m_store->pushDirectory(); m_store->enterDirectory(shapeSelectionLocation) ; KisShapeSelection* shapeSelection = new KisShapeSelection(m_shapeController, m_image, dstSelection); dstSelection->setShapeSelection(shapeSelection); result = shapeSelection->loadSelection(m_store); m_store->popDirectory(); if (!result) { m_warningMessages << i18n("Could not load vector selection %1.", location); } } return true; } QString KisKraLoadVisitor::getLocation(KisNode* node, const QString& suffix) { return getLocation(m_layerFilenames[node], suffix); } QString KisKraLoadVisitor::getLocation(const QString &filename, const QString& suffix) { QString location = m_external ? QString() : m_uri; location += m_name + LAYER_PATH + filename + suffix; return location; } void KisKraLoadVisitor::loadNodeKeyframes(KisNode *node) { if (!m_keyframeFilenames.contains(node)) return; node->enableAnimation(); const QString &location = getLocation(m_keyframeFilenames[node]); if (!m_store->open(location)) { m_errorMessages << i18n("Could not load keyframes from %1.", location); return; } QString errorMsg; int errorLine; int errorColumn; QDomDocument dom; bool ok = dom.setContent(m_store->device(), &errorMsg, &errorLine, &errorColumn); m_store->close(); if (!ok) { m_errorMessages << i18n("parsing error in the keyframe file %1 at line %2, column %3\nError message: %4", location, errorLine, errorColumn, i18n(errorMsg.toUtf8())); return; } QDomElement root = dom.firstChildElement(); for (QDomElement child = root.firstChildElement(); !child.isNull(); child = child.nextSiblingElement()) { if (child.nodeName().toUpper() == "CHANNEL") { QString id = child.attribute("name"); KisKeyframeChannel *channel = node->getKeyframeChannel(id, true); if (!channel) { m_warningMessages << i18n("unknown keyframe channel type: %1 in %2", id, location); continue; } channel->loadXML(child); } } } void KisKraLoadVisitor::loadDeprecatedFilter(KisFilterConfigurationSP cfg) { if (cfg->getString("legacy") == "left edge detections") { cfg->setProperty("horizRadius", 1); cfg->setProperty("vertRadius", 1); cfg->setProperty("type", "prewitt"); cfg->setProperty("output", "yFall"); cfg->setProperty("lockAspect", true); cfg->setProperty("transparency", false); } else if (cfg->getString("legacy") == "right edge detections") { cfg->setProperty("horizRadius", 1); cfg->setProperty("vertRadius", 1); cfg->setProperty("type", "prewitt"); cfg->setProperty("output", "yGrowth"); cfg->setProperty("lockAspect", true); cfg->setProperty("transparency", false); } else if (cfg->getString("legacy") == "top edge detections") { cfg->setProperty("horizRadius", 1); cfg->setProperty("vertRadius", 1); cfg->setProperty("type", "prewitt"); cfg->setProperty("output", "xGrowth"); cfg->setProperty("lockAspect", true); cfg->setProperty("transparency", false); } else if (cfg->getString("legacy") == "bottom edge detections") { cfg->setProperty("horizRadius", 1); cfg->setProperty("vertRadius", 1); cfg->setProperty("type", "prewitt"); cfg->setProperty("output", "xFall"); cfg->setProperty("lockAspect", true); cfg->setProperty("transparency", false); } } diff --git a/plugins/impex/libkra/kis_kra_loader.cpp b/plugins/impex/libkra/kis_kra_loader.cpp index 46237c8506..93bb259f6f 100644 --- a/plugins/impex/libkra/kis_kra_loader.cpp +++ b/plugins/impex/libkra/kis_kra_loader.cpp @@ -1,1265 +1,1267 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2007 * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_kra_loader.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lazybrush/kis_colorize_mask.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisResourceServerProvider.h" #include "kis_keyframe_channel.h" #include #include "KisReferenceImagesLayer.h" #include "KisReferenceImage.h" #include #include "KisDocument.h" #include "kis_config.h" #include "kis_kra_tags.h" #include "kis_kra_utils.h" #include "kis_kra_load_visitor.h" #include "kis_dom_utils.h" #include "kis_image_animation_interface.h" #include "kis_time_range.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_image_config.h" #include "KisProofingConfiguration.h" #include "kis_layer_properties_icons.h" #include "kis_node_view_color_scheme.h" #include "KisMirrorAxisConfig.h" /* Color model id comparison through the ages: 2.4 2.5 2.6 ideal ALPHA ALPHA ALPHA ALPHAU8 CMYK CMYK CMYK CMYKAU8 CMYKAF32 CMYKAF32 CMYKA16 CMYKAU16 CMYKAU16 GRAYA GRAYA GRAYA GRAYAU8 GrayF32 GRAYAF32 GRAYAF32 GRAYA16 GRAYAU16 GRAYAU16 LABA LABA LABA LABAU16 LABAF32 LABAF32 LABAU8 LABAU8 RGBA RGBA RGBA RGBAU8 RGBA16 RGBA16 RGBA16 RGBAU16 RgbAF32 RGBAF32 RGBAF32 RgbAF16 RgbAF16 RGBAF16 XYZA16 XYZA16 XYZA16 XYZAU16 XYZA8 XYZA8 XYZAU8 XyzAF16 XyzAF16 XYZAF16 XyzAF32 XYZAF32 XYZAF32 YCbCrA YCBCRA8 YCBCRA8 YCBCRAU8 YCbCrAU16 YCBCRAU16 YCBCRAU16 YCBCRF32 YCBCRF32 */ using namespace KRA; struct KisKraLoader::Private { public: KisDocument* document; QString imageName; // used to be stored in the image, is now in the documentInfo block QString imageComment; // used to be stored in the image, is now in the documentInfo block QMap layerFilenames; // temp storage during loading int syntaxVersion; // version of the fileformat we are loading vKisNodeSP selectedNodes; // the nodes that were active when saving the document. QMap assistantsFilenames; QList assistants; QMap keyframeFilenames; QVector paletteFilenames; QStringList errorMessages; QStringList warningMessages; }; void convertColorSpaceNames(QString &colorspacename, QString &profileProductName) { if (colorspacename == "Grayscale + Alpha") { colorspacename = "GRAYA"; profileProductName.clear(); } else if (colorspacename == "RgbAF32") { colorspacename = "RGBAF32"; profileProductName.clear(); } else if (colorspacename == "RgbAF16") { colorspacename = "RGBAF16"; profileProductName.clear(); } else if (colorspacename == "CMYKA16") { colorspacename = "CMYKAU16"; } else if (colorspacename == "GrayF32") { colorspacename = "GRAYAF32"; profileProductName.clear(); } else if (colorspacename == "GRAYA16") { colorspacename = "GRAYAU16"; } else if (colorspacename == "XyzAF16") { colorspacename = "XYZAF16"; profileProductName.clear(); } else if (colorspacename == "XyzAF32") { colorspacename = "XYZAF32"; profileProductName.clear(); } else if (colorspacename == "YCbCrA") { colorspacename = "YCBCRA8"; } else if (colorspacename == "YCbCrAU16") { colorspacename = "YCBCRAU16"; } } KisKraLoader::KisKraLoader(KisDocument * document, int syntaxVersion) : m_d(new Private()) { m_d->document = document; m_d->syntaxVersion = syntaxVersion; } KisKraLoader::~KisKraLoader() { delete m_d; } KisImageSP KisKraLoader::loadXML(const KoXmlElement& element) { QString attr; KisImageSP image = 0; qint32 width; qint32 height; QString profileProductName; double xres; double yres; QString colorspacename; const KoColorSpace * cs; if ((attr = element.attribute(MIME)) == NATIVE_MIMETYPE) { if ((m_d->imageName = element.attribute(NAME)).isNull()) { m_d->errorMessages << i18n("Image does not have a name."); return KisImageSP(0); } if ((attr = element.attribute(WIDTH)).isNull()) { m_d->errorMessages << i18n("Image does not specify a width."); return KisImageSP(0); } width = KisDomUtils::toInt(attr); if ((attr = element.attribute(HEIGHT)).isNull()) { m_d->errorMessages << i18n("Image does not specify a height."); return KisImageSP(0); } height = KisDomUtils::toInt(attr); m_d->imageComment = element.attribute(DESCRIPTION); xres = 100.0 / 72.0; if (!(attr = element.attribute(X_RESOLUTION)).isNull()) { qreal value = KisDomUtils::toDouble(attr); if (value > 1.0) { xres = value / 72.0; } } yres = 100.0 / 72.0; if (!(attr = element.attribute(Y_RESOLUTION)).isNull()) { qreal value = KisDomUtils::toDouble(attr); if (value > 1.0) { yres = value / 72.0; } } if ((colorspacename = element.attribute(COLORSPACE_NAME)).isNull()) { // An old file: take a reasonable default. // Krita didn't support anything else in those // days anyway. colorspacename = "RGBA"; } profileProductName = element.attribute(PROFILE); // A hack for an old colorspacename convertColorSpaceNames(colorspacename, profileProductName); QString colorspaceModel = KoColorSpaceRegistry::instance()->colorSpaceColorModelId(colorspacename).id(); QString colorspaceDepth = KoColorSpaceRegistry::instance()->colorSpaceColorDepthId(colorspacename).id(); if (profileProductName.isNull()) { // no mention of profile so get default profile"; cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, ""); } else { cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, profileProductName); } if (cs == 0) { // try once more without the profile cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, ""); if (cs == 0) { m_d->errorMessages << i18n("Image specifies an unsupported color model: %1.", colorspacename); return KisImageSP(0); } } KisProofingConfigurationSP proofingConfig = KisImageConfig(true).defaultProofingconfiguration(); if (!(attr = element.attribute(PROOFINGPROFILENAME)).isNull()) { proofingConfig->proofingProfile = attr; } if (!(attr = element.attribute(PROOFINGMODEL)).isNull()) { proofingConfig->proofingModel = attr; } if (!(attr = element.attribute(PROOFINGDEPTH)).isNull()) { proofingConfig->proofingDepth = attr; } if (!(attr = element.attribute(PROOFINGINTENT)).isNull()) { proofingConfig->intent = (KoColorConversionTransformation::Intent) KisDomUtils::toInt(attr); } if (!(attr = element.attribute(PROOFINGADAPTATIONSTATE)).isNull()) { proofingConfig->adaptationState = KisDomUtils::toDouble(attr); } if (m_d->document) { image = new KisImage(m_d->document->createUndoStore(), width, height, cs, m_d->imageName); } else { image = new KisImage(0, width, height, cs, m_d->imageName); } image->setResolution(xres, yres); loadNodes(element, image, const_cast(image->rootLayer().data())); KoXmlNode child; for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { KoXmlElement e = child.toElement(); if(e.tagName() == CANVASPROJECTIONCOLOR) { if (e.hasAttribute(COLORBYTEDATA)) { QByteArray colorData = QByteArray::fromBase64(e.attribute(COLORBYTEDATA).toLatin1()); KoColor color((const quint8*)colorData.data(), image->colorSpace()); image->setDefaultProjectionColor(color); } } if(e.tagName() == GLOBALASSISTANTSCOLOR) { if (e.hasAttribute(SIMPLECOLORDATA)) { QString colorData = e.attribute(SIMPLECOLORDATA); m_d->document->setAssistantsGlobalColor(KisDomUtils::qStringToQColor(colorData)); } } if(e.tagName()== PROOFINGWARNINGCOLOR) { QDomDocument dom; KoXml::asQDomElement(dom, e); QDomElement eq = dom.firstChildElement(); proofingConfig->warningColor = KoColor::fromXML(eq.firstChildElement(), Integer8BitsColorDepthID.id()); } if (e.tagName().toLower() == "animation") { loadAnimationMetadata(e, image); } } image->setProofingConfiguration(proofingConfig); for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { KoXmlElement e = child.toElement(); if(e.tagName() == "compositions") { loadCompositions(e, image); } } } KoXmlNode child; for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { KoXmlElement e = child.toElement(); if (e.tagName() == "grid") { loadGrid(e); } else if (e.tagName() == "guides") { loadGuides(e); } else if (e.tagName() == MIRROR_AXIS) { loadMirrorAxis(e); } else if (e.tagName() == "assistants") { loadAssistantsList(e); } else if (e.tagName() == "audio") { loadAudio(e, image); } } // reading palettes from XML for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { QDomElement e = child.toElement(); if (e.tagName() == PALETTES) { for (QDomElement paletteElement = e.lastChildElement(); !paletteElement.isNull(); paletteElement = paletteElement.previousSiblingElement()) { QString paletteName = paletteElement.attribute("filename"); m_d->paletteFilenames.append(paletteName); } break; } } return image; } void KisKraLoader::loadBinaryData(KoStore * store, KisImageSP image, const QString & uri, bool external) { // icc profile: if present, this overrides the profile product name loaded in loadXML. QString location = external ? QString() : uri; location += m_d->imageName + ICC_PATH; if (store->hasFile(location)) { if (store->open(location)) { QByteArray data; data.resize(store->size()); bool res = (store->read(data.data(), store->size()) > -1); store->close(); if (res) { const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(image->colorSpace()->colorModelId().id(), image->colorSpace()->colorDepthId().id(), data); if (profile && profile->valid()) { res = image->assignImageProfile(profile); + image->waitForDone(); } if (!res) { const QString defaultProfileId = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(image->colorSpace()->id()); profile = KoColorSpaceRegistry::instance()->profileByName(defaultProfileId); Q_ASSERT(profile && profile->valid()); image->assignImageProfile(profile); + image->waitForDone(); } } } } //load the embed proofing profile, it only needs to be loaded into Krita, not assigned. location = external ? QString() : uri; location += m_d->imageName + ICC_PROOFING_PATH; if (store->hasFile(location)) { if (store->open(location)) { QByteArray proofingData; proofingData.resize(store->size()); bool proofingProfileRes = (store->read(proofingData.data(), store->size())>-1); store->close(); KisProofingConfigurationSP proofingConfig = image->proofingConfiguration(); if (!proofingConfig) { proofingConfig = KisImageConfig(true).defaultProofingconfiguration(); } if (proofingProfileRes) { const KoColorProfile *proofingProfile = KoColorSpaceRegistry::instance()->createColorProfile(proofingConfig->proofingModel, proofingConfig->proofingDepth, proofingData); if (proofingProfile->valid()){ KoColorSpaceRegistry::instance()->addProfile(proofingProfile); } } } } // Load the layers data: if there is a profile associated with a layer it will be set now. KisKraLoadVisitor visitor(image, store, m_d->document->shapeController(), m_d->layerFilenames, m_d->keyframeFilenames, m_d->imageName, m_d->syntaxVersion); if (external) { visitor.setExternalUri(uri); } image->rootLayer()->accept(visitor); if (!visitor.errorMessages().isEmpty()) { m_d->errorMessages.append(visitor.errorMessages()); } if (!visitor.warningMessages().isEmpty()) { m_d->warningMessages.append(visitor.warningMessages()); } // annotations // exif location = external ? QString() : uri; location += m_d->imageName + EXIF_PATH; if (store->hasFile(location)) { QByteArray data; store->open(location); data = store->read(store->size()); store->close(); image->addAnnotation(KisAnnotationSP(new KisAnnotation("exif", "", data))); } // layer styles location = external ? QString() : uri; location += m_d->imageName + LAYER_STYLES_PATH; if (store->hasFile(location)) { KisPSDLayerStyleCollectionResource *collection = new KisPSDLayerStyleCollectionResource("Embedded Styles.asl"); collection->setName(i18nc("Auto-generated layer style collection name for embedded styles (collection)", "<%1> (embedded)", m_d->imageName)); KIS_ASSERT_RECOVER_NOOP(!collection->valid()); store->open(location); { KoStoreDevice device(store); device.open(QIODevice::ReadOnly); /** * ASL loading code cannot work with non-sequential IO devices, * so convert the device beforehand! */ QByteArray buf = device.readAll(); QBuffer raDevice(&buf); raDevice.open(QIODevice::ReadOnly); collection->loadFromDevice(&raDevice); } store->close(); if (collection->valid()) { KoResourceServer *server = KisResourceServerProvider::instance()->layerStyleCollectionServer(); server->addResource(collection, false); collection->assignAllLayerStyles(image->root()); } else { warnKrita << "WARNING: Couldn't load layer styles library from .kra!"; delete collection; } } if (m_d->document && m_d->document->documentInfo()->aboutInfo("title").isNull()) m_d->document->documentInfo()->setAboutInfo("title", m_d->imageName); if (m_d->document && m_d->document->documentInfo()->aboutInfo("comment").isNull()) m_d->document->documentInfo()->setAboutInfo("comment", m_d->imageComment); loadAssistants(store, uri, external); } void KisKraLoader::loadPalettes(KoStore *store, KisDocument *doc) { QList list; Q_FOREACH (const QString &filename, m_d->paletteFilenames) { KoColorSet *newPalette = new KoColorSet(filename); store->open(m_d->imageName + PALETTE_PATH + filename); QByteArray data = store->read(store->size()); newPalette->fromByteArray(data); newPalette->setIsGlobal(false); newPalette->setIsEditable(true); store->close(); list.append(newPalette); } doc->setPaletteList(list); } vKisNodeSP KisKraLoader::selectedNodes() const { return m_d->selectedNodes; } QList KisKraLoader::assistants() const { return m_d->assistants; } QStringList KisKraLoader::errorMessages() const { return m_d->errorMessages; } QStringList KisKraLoader::warningMessages() const { return m_d->warningMessages; } QString KisKraLoader::imageName() const { return m_d->imageName; } void KisKraLoader::loadAssistants(KoStore *store, const QString &uri, bool external) { QString file_path; QString location; QMap handleMap; KisPaintingAssistant* assistant = 0; const QColor globalColor = m_d->document->assistantsGlobalColor(); QMap::const_iterator loadedAssistant = m_d->assistantsFilenames.constBegin(); while (loadedAssistant != m_d->assistantsFilenames.constEnd()){ const KisPaintingAssistantFactory* factory = KisPaintingAssistantFactoryRegistry::instance()->get(loadedAssistant.value()); if (factory) { assistant = factory->createPaintingAssistant(); location = external ? QString() : uri; location += m_d->imageName + ASSISTANTS_PATH; file_path = location + loadedAssistant.key(); assistant->loadXml(store, handleMap, file_path); assistant->setAssistantGlobalColorCache(globalColor); //If an assistant has too few handles than it should according to it's own setup, just don't load it// if (assistant->handles().size()==assistant->numHandles()){ m_d->assistants.append(toQShared(assistant)); } } loadedAssistant++; } } void KisKraLoader::loadAnimationMetadata(const KoXmlElement &element, KisImageSP image) { QDomDocument qDom; KoXml::asQDomElement(qDom, element); QDomElement qElement = qDom.firstChildElement(); float framerate; KisTimeRange range; int currentTime; KisImageAnimationInterface *animation = image->animationInterface(); if (KisDomUtils::loadValue(qElement, "framerate", &framerate)) { animation->setFramerate(framerate); } if (KisDomUtils::loadValue(qElement, "range", &range)) { animation->setFullClipRange(range); } if (KisDomUtils::loadValue(qElement, "currentTime", ¤tTime)) { animation->switchCurrentTimeAsync(currentTime); } } KisNodeSP KisKraLoader::loadNodes(const KoXmlElement& element, KisImageSP image, KisNodeSP parent) { KoXmlNode node = element.firstChild(); KoXmlNode child; if (!node.isNull()) { if (node.isElement()) { if (node.nodeName().toUpper() == LAYERS.toUpper() || node.nodeName().toUpper() == MASKS.toUpper()) { for (child = node.lastChild(); !child.isNull(); child = child.previousSibling()) { KisNodeSP node = loadNode(child.toElement(), image); if (node) { image->nextLayerName(); // Make sure the nameserver is current with the number of nodes. image->addNode(node, parent); if (node->inherits("KisLayer") && KoXml::childNodesCount(child) > 0) { loadNodes(child.toElement(), image, node); } } } } } } return parent; } KisNodeSP KisKraLoader::loadNode(const KoXmlElement& element, KisImageSP image) { // Nota bene: If you add new properties to layers, you should // ALWAYS define a default value in case the property is not // present in the layer definition: this helps a LOT with backward // compatibility. QString name = element.attribute(NAME, "No Name"); QUuid id = QUuid(element.attribute(UUID, QUuid().toString())); qint32 x = element.attribute(X, "0").toInt(); qint32 y = element.attribute(Y, "0").toInt(); qint32 opacity = element.attribute(OPACITY, QString::number(OPACITY_OPAQUE_U8)).toInt(); if (opacity < OPACITY_TRANSPARENT_U8) opacity = OPACITY_TRANSPARENT_U8; if (opacity > OPACITY_OPAQUE_U8) opacity = OPACITY_OPAQUE_U8; const KoColorSpace* colorSpace = 0; if ((element.attribute(COLORSPACE_NAME)).isNull()) { dbgFile << "No attribute color space for layer: " << name; colorSpace = image->colorSpace(); } else { QString colorspacename = element.attribute(COLORSPACE_NAME); QString profileProductName; convertColorSpaceNames(colorspacename, profileProductName); QString colorspaceModel = KoColorSpaceRegistry::instance()->colorSpaceColorModelId(colorspacename).id(); QString colorspaceDepth = KoColorSpaceRegistry::instance()->colorSpaceColorDepthId(colorspacename).id(); dbgFile << "Searching color space: " << colorspacename << colorspaceModel << colorspaceDepth << " for layer: " << name; // use default profile - it will be replaced later in completeLoading colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, ""); dbgFile << "found colorspace" << colorSpace; if (!colorSpace) { m_d->warningMessages << i18n("Layer %1 specifies an unsupported color model: %2.", name, colorspacename); return 0; } } const bool visible = element.attribute(VISIBLE, "1") == "0" ? false : true; const bool locked = element.attribute(LOCKED, "0") == "0" ? false : true; const bool collapsed = element.attribute(COLLAPSED, "0") == "0" ? false : true; int colorLabelIndex = element.attribute(COLOR_LABEL, "0").toInt(); QVector labels = KisNodeViewColorScheme::instance()->allColorLabels(); if (colorLabelIndex >= labels.size()) { colorLabelIndex = labels.size() - 1; } // Now find out the layer type and do specific handling QString nodeType; if (m_d->syntaxVersion == 1) { nodeType = element.attribute("layertype"); if (nodeType.isEmpty()) { nodeType = PAINT_LAYER; } } else { nodeType = element.attribute(NODE_TYPE); } if (nodeType.isEmpty()) { m_d->warningMessages << i18n("Layer %1 has an unsupported type.", name); return 0; } KisNodeSP node = 0; if (nodeType == PAINT_LAYER) node = loadPaintLayer(element, image, name, colorSpace, opacity); else if (nodeType == GROUP_LAYER) node = loadGroupLayer(element, image, name, colorSpace, opacity); else if (nodeType == ADJUSTMENT_LAYER) node = loadAdjustmentLayer(element, image, name, colorSpace, opacity); else if (nodeType == SHAPE_LAYER) node = loadShapeLayer(element, image, name, colorSpace, opacity); else if (nodeType == GENERATOR_LAYER) node = loadGeneratorLayer(element, image, name, colorSpace, opacity); else if (nodeType == CLONE_LAYER) node = loadCloneLayer(element, image, name, colorSpace, opacity); else if (nodeType == FILTER_MASK) node = loadFilterMask(element); else if (nodeType == TRANSFORM_MASK) node = loadTransformMask(element); else if (nodeType == TRANSPARENCY_MASK) node = loadTransparencyMask(element); else if (nodeType == SELECTION_MASK) node = loadSelectionMask(image, element); else if (nodeType == COLORIZE_MASK) node = loadColorizeMask(image, element, colorSpace); else if (nodeType == FILE_LAYER) node = loadFileLayer(element, image, name, opacity); else if (nodeType == REFERENCE_IMAGES_LAYER) node = loadReferenceImagesLayer(element, image); else { m_d->warningMessages << i18n("Layer %1 has an unsupported type: %2.", name, nodeType); return 0; } // Loading the node went wrong. Return empty node and leave to // upstream to complain to the user if (!node) { m_d->warningMessages << i18n("Failure loading layer %1 of type: %2.", name, nodeType); return 0; } node->setVisible(visible, true); node->setUserLocked(locked); node->setCollapsed(collapsed); node->setColorLabelIndex(colorLabelIndex); node->setX(x); node->setY(y); node->setName(name); if (! id.isNull()) // if no uuid in file, new one has been generated already node->setUuid(id); if (node->inherits("KisLayer") || node->inherits("KisColorizeMask")) { QString compositeOpName = element.attribute(COMPOSITE_OP, "normal"); node->setCompositeOpId(compositeOpName); } if (node->inherits("KisLayer")) { KisLayer* layer = qobject_cast(node.data()); QBitArray channelFlags = stringToFlags(element.attribute(CHANNEL_FLAGS, ""), colorSpace->channelCount()); layer->setChannelFlags(channelFlags); if (element.hasAttribute(LAYER_STYLE_UUID)) { QString uuidString = element.attribute(LAYER_STYLE_UUID); QUuid uuid(uuidString); if (!uuid.isNull()) { KisPSDLayerStyleSP dumbLayerStyle(new KisPSDLayerStyle()); dumbLayerStyle->setUuid(uuid); layer->setLayerStyle(dumbLayerStyle); } else { warnKrita << "WARNING: Layer style for layer" << layer->name() << "contains invalid UUID" << uuidString; } } } if (node->inherits("KisGroupLayer")) { if (element.hasAttribute(PASS_THROUGH_MODE)) { bool value = element.attribute(PASS_THROUGH_MODE, "0") != "0"; KisGroupLayer *group = qobject_cast(node.data()); group->setPassThroughMode(value); } } const bool timelineEnabled = element.attribute(VISIBLE_IN_TIMELINE, "0") == "0" ? false : true; node->setUseInTimeline(timelineEnabled); if (node->inherits("KisPaintLayer")) { KisPaintLayer* layer = qobject_cast(node.data()); QBitArray channelLockFlags = stringToFlags(element.attribute(CHANNEL_LOCK_FLAGS, ""), colorSpace->channelCount()); layer->setChannelLockFlags(channelLockFlags); bool onionEnabled = element.attribute(ONION_SKIN_ENABLED, "0") == "0" ? false : true; layer->setOnionSkinEnabled(onionEnabled); } if (element.attribute(FILE_NAME).isNull()) { m_d->layerFilenames[node.data()] = name; } else { m_d->layerFilenames[node.data()] = element.attribute(FILE_NAME); } if (element.hasAttribute("selected") && element.attribute("selected") == "true") { m_d->selectedNodes.append(node); } if (element.hasAttribute(KEYFRAME_FILE)) { m_d->keyframeFilenames.insert(node.data(), element.attribute(KEYFRAME_FILE)); } return node; } KisNodeSP KisKraLoader::loadPaintLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(element); KisPaintLayer* layer; layer = new KisPaintLayer(image, name, opacity, cs); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadFileLayer(const KoXmlElement& element, KisImageSP image, const QString& name, quint32 opacity) { QString filename = element.attribute("source", QString()); if (filename.isNull()) return 0; bool scale = (element.attribute("scale", "true") == "true"); int scalingMethod = element.attribute("scalingmethod", "-1").toInt(); if (scalingMethod < 0) { if (scale) { scalingMethod = KisFileLayer::ToImagePPI; } else { scalingMethod = KisFileLayer::None; } } QString documentPath; if (m_d->document) { documentPath = m_d->document->url().toLocalFile(); } QFileInfo info(documentPath); QString basePath = info.absolutePath(); QString fullPath = QDir(basePath).filePath(QDir::cleanPath(filename)); if (!QFileInfo(fullPath).exists()) { qApp->setOverrideCursor(Qt::ArrowCursor); QString msg = i18nc( "@info", "The file associated to a file layer with the name \"%1\" is not found.\n\n" "Expected path:\n" "%2\n\n" "Do you want to locate it manually?", name, fullPath); int result = QMessageBox::warning(0, i18nc("@title:window", "File not found"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (result == QMessageBox::Yes) { KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); dialog.setDefaultDir(basePath); QString url = dialog.filename(); if (!QFileInfo(basePath).exists()) { filename = url; } else { QDir d(basePath); filename = d.relativeFilePath(url); } } qApp->restoreOverrideCursor(); } KisLayer *layer = new KisFileLayer(image, basePath, filename, (KisFileLayer::ScalingMethod)scalingMethod, name, opacity); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadGroupLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(element); Q_UNUSED(cs); QString attr; KisGroupLayer* layer; layer = new KisGroupLayer(image, name, opacity); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadAdjustmentLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { // XXX: do something with filterversion? Q_UNUSED(cs); QString attr; KisAdjustmentLayer* layer; QString filtername; QString legacy = filtername; if ((filtername = element.attribute(FILTER_NAME)).isNull()) { // XXX: Invalid adjustmentlayer! We should warn about it! warnFile << "No filter in adjustment layer"; return 0; } //get deprecated filters. if (filtername=="brightnesscontrast") { legacy = filtername; filtername = "perchannel"; } if (filtername=="left edge detections" || filtername=="right edge detections" || filtername=="top edge detections" || filtername=="bottom edge detections") { legacy = filtername; filtername = "edge detection"; } KisFilterSP f = KisFilterRegistry::instance()->value(filtername); if (!f) { warnFile << "No filter for filtername" << filtername << ""; return 0; // XXX: We don't have this filter. We should warn about it! } KisFilterConfigurationSP kfc = f->factoryConfiguration(); kfc->setProperty("legacy", legacy); if (legacy=="brightnesscontrast") { kfc->setProperty("colorModel", cs->colorModelId().id()); } // We'll load the configuration and the selection later. layer = new KisAdjustmentLayer(image, name, kfc, 0); Q_CHECK_PTR(layer); layer->setOpacity(opacity); return layer; } KisNodeSP KisKraLoader::loadShapeLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(element); Q_UNUSED(cs); QString attr; KoShapeControllerBase * shapeController = 0; if (m_d->document) { shapeController = m_d->document->shapeController(); } KisShapeLayer* layer = new KisShapeLayer(shapeController, image, name, opacity); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadGeneratorLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(cs); // XXX: do something with generator version? KisGeneratorLayer* layer; QString generatorname = element.attribute(GENERATOR_NAME); if (generatorname.isNull()) { // XXX: Invalid generator layer! We should warn about it! warnFile << "No generator in generator layer"; return 0; } KisGeneratorSP generator = KisGeneratorRegistry::instance()->value(generatorname); if (!generator) { warnFile << "No generator for generatorname" << generatorname << ""; return 0; // XXX: We don't have this generator. We should warn about it! } KisFilterConfigurationSP kgc = generator->factoryConfiguration(); // We'll load the configuration and the selection later. layer = new KisGeneratorLayer(image, name, kgc, 0); Q_CHECK_PTR(layer); layer->setOpacity(opacity); return layer; } KisNodeSP KisKraLoader::loadCloneLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(cs); KisCloneLayerSP layer = new KisCloneLayer(0, image, name, opacity); KisNodeUuidInfo info; if (! (element.attribute(CLONE_FROM_UUID)).isNull()) { info = KisNodeUuidInfo(QUuid(element.attribute(CLONE_FROM_UUID))); } else { if ((element.attribute(CLONE_FROM)).isNull()) { return 0; } else { info = KisNodeUuidInfo(element.attribute(CLONE_FROM)); } } layer->setCopyFromInfo(info); if ((element.attribute(CLONE_TYPE)).isNull()) { return 0; } else { layer->setCopyType((CopyLayerType) element.attribute(CLONE_TYPE).toInt()); } return layer; } KisNodeSP KisKraLoader::loadFilterMask(const KoXmlElement& element) { QString attr; KisFilterMask* mask; QString filtername; // XXX: should we check the version? if ((filtername = element.attribute(FILTER_NAME)).isNull()) { // XXX: Invalid filter layer! We should warn about it! warnFile << "No filter in filter layer"; return 0; } KisFilterSP f = KisFilterRegistry::instance()->value(filtername); if (!f) { warnFile << "No filter for filtername" << filtername << ""; return 0; // XXX: We don't have this filter. We should warn about it! } KisFilterConfigurationSP kfc = f->factoryConfiguration(); // We'll load the configuration and the selection later. mask = new KisFilterMask(); mask->setFilter(kfc); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadTransformMask(const KoXmlElement& element) { Q_UNUSED(element); KisTransformMask* mask; /** * We'll load the transform configuration later on a stage * of binary data loading */ mask = new KisTransformMask(); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadTransparencyMask(const KoXmlElement& element) { Q_UNUSED(element); KisTransparencyMask* mask = new KisTransparencyMask(); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadSelectionMask(KisImageSP image, const KoXmlElement& element) { KisSelectionMaskSP mask = new KisSelectionMask(image); bool active = element.attribute(ACTIVE, "1") == "0" ? false : true; mask->setActive(active); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadColorizeMask(KisImageSP image, const KoXmlElement& element, const KoColorSpace *colorSpace) { KisColorizeMaskSP mask = new KisColorizeMask(); const bool editKeystrokes = element.attribute(COLORIZE_EDIT_KEYSTROKES, "1") == "0" ? false : true; const bool showColoring = element.attribute(COLORIZE_SHOW_COLORING, "1") == "0" ? false : true; KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, editKeystrokes, image); KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, showColoring, image); const bool useEdgeDetection = KisDomUtils::toInt(element.attribute(COLORIZE_USE_EDGE_DETECTION, "0")); const qreal edgeDetectionSize = KisDomUtils::toDouble(element.attribute(COLORIZE_EDGE_DETECTION_SIZE, "4")); const qreal radius = KisDomUtils::toDouble(element.attribute(COLORIZE_FUZZY_RADIUS, "0")); const int cleanUp = KisDomUtils::toInt(element.attribute(COLORIZE_CLEANUP, "0")); const bool limitToDevice = KisDomUtils::toInt(element.attribute(COLORIZE_LIMIT_TO_DEVICE, "0")); mask->setUseEdgeDetection(useEdgeDetection); mask->setEdgeDetectionSize(edgeDetectionSize); mask->setFuzzyRadius(radius); mask->setCleanUpAmount(qreal(cleanUp) / 100.0); mask->setLimitToDeviceBounds(limitToDevice); delete mask->setColorSpace(colorSpace); mask->setImage(image); return mask; } void KisKraLoader::loadCompositions(const KoXmlElement& elem, KisImageSP image) { KoXmlNode child; for (child = elem.firstChild(); !child.isNull(); child = child.nextSibling()) { KoXmlElement e = child.toElement(); QString name = e.attribute("name"); bool exportEnabled = e.attribute("exportEnabled", "1") == "0" ? false : true; KisLayerCompositionSP composition(new KisLayerComposition(image, name)); composition->setExportEnabled(exportEnabled); KoXmlNode value; for (value = child.lastChild(); !value.isNull(); value = value.previousSibling()) { KoXmlElement e = value.toElement(); QUuid uuid(e.attribute("uuid")); bool visible = e.attribute("visible", "1") == "0" ? false : true; composition->setVisible(uuid, visible); bool collapsed = e.attribute("collapsed", "1") == "0" ? false : true; composition->setCollapsed(uuid, collapsed); } image->addComposition(composition); } } void KisKraLoader::loadAssistantsList(const KoXmlElement &elem) { KoXmlNode child; int count = 0; for (child = elem.firstChild(); !child.isNull(); child = child.nextSibling()) { KoXmlElement e = child.toElement(); QString type = e.attribute("type"); QString file_name = e.attribute("filename"); m_d->assistantsFilenames.insert(file_name,type); count++; } } void KisKraLoader::loadGrid(const KoXmlElement& elem) { QDomDocument dom; KoXml::asQDomElement(dom, elem); QDomElement domElement = dom.firstChildElement(); KisGridConfig config; config.loadDynamicDataFromXml(domElement); config.loadStaticData(); m_d->document->setGridConfig(config); } void KisKraLoader::loadGuides(const KoXmlElement& elem) { QDomDocument dom; KoXml::asQDomElement(dom, elem); QDomElement domElement = dom.firstChildElement(); KisGuidesConfig guides; guides.loadFromXml(domElement); m_d->document->setGuidesConfig(guides); } void KisKraLoader::loadMirrorAxis(const KoXmlElement &elem) { QDomDocument dom; KoXml::asQDomElement(dom, elem); QDomElement domElement = dom.firstChildElement(); KisMirrorAxisConfig mirrorAxis; mirrorAxis.loadFromXml(domElement); m_d->document->setMirrorAxisConfig(mirrorAxis); } void KisKraLoader::loadAudio(const KoXmlElement& elem, KisImageSP image) { QDomDocument dom; KoXml::asQDomElement(dom, elem); QDomElement qElement = dom.firstChildElement(); QString fileName; if (KisDomUtils::loadValue(qElement, "masterChannelPath", &fileName)) { fileName = QDir::toNativeSeparators(fileName); QDir baseDirectory = QFileInfo(m_d->document->localFilePath()).absoluteDir(); fileName = baseDirectory.absoluteFilePath(fileName); QFileInfo info(fileName); if (!info.exists()) { qApp->setOverrideCursor(Qt::ArrowCursor); QString msg = i18nc( "@info", "Audio channel file \"%1\" doesn't exist!\n\n" "Expected path:\n" "%2\n\n" "Do you want to locate it manually?", info.fileName(), info.absoluteFilePath()); int result = QMessageBox::warning(0, i18nc("@title:window", "File not found"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (result == QMessageBox::Yes) { info.setFile(KisImportExportManager::askForAudioFileName(info.absolutePath(), 0)); } qApp->restoreOverrideCursor(); } if (info.exists()) { image->animationInterface()->setAudioChannelFileName(info.absoluteFilePath()); } } bool audioMuted = false; if (KisDomUtils::loadValue(qElement, "audioMuted", &audioMuted)) { image->animationInterface()->setAudioMuted(audioMuted); } qreal audioVolume = 0.5; if (KisDomUtils::loadValue(qElement, "audioVolume", &audioVolume)) { image->animationInterface()->setAudioVolume(audioVolume); } } KisNodeSP KisKraLoader::loadReferenceImagesLayer(const KoXmlElement &elem, KisImageSP image) { KisSharedPtr layer = new KisReferenceImagesLayer(m_d->document->shapeController(), image); m_d->document->setReferenceImagesLayer(layer, false); for (QDomElement child = elem.firstChildElement(); !child.isNull(); child = child.nextSiblingElement()) { if (child.nodeName().toLower() == "referenceimage") { auto* reference = KisReferenceImage::fromXml(child); layer->addShape(reference); } } return layer; }