diff --git a/krita/data/shortcuts/krita_default.shortcuts b/krita/data/shortcuts/krita_default.shortcuts
index 2192cab160..cc0168a19e 100644
--- a/krita/data/shortcuts/krita_default.shortcuts
+++ b/krita/data/shortcuts/krita_default.shortcuts
@@ -1,285 +1,285 @@
[Shortcuts]
activateNextLayer=PgUp
activatePreviousLayer=PgDown
add_blank_frame=none
add_duplicate_frame=none
add_new_adjustment_layer=none
add_new_clone_layer=none
add_new_file_layer=none
add_new_fill_layer=none
add_new_filter_mask=none
add_new_group_layer=none
add_new_paint_layer=none
add_new_selection_mask=none
add_new_shape_layer=none
add_new_transform_mask=none
add_new_transparency_mask=none
borderselection=none
BrushesAndStuff=none
brushslider2=none
brushslider3=none
canvassize=Ctrl+Alt+C
clear=Del
clones_array=none
colorrange=none
composite_actions=none
convert_selection_to_shape=none
convert_shapes_to_vector_selection=none
convert_to_filter_mask=none
convert_to_paint_layer=none
convert_to_selection_mask=none
convert_to_transparency_mask=none
convert_to_vector_selection=none
copy_merged=Ctrl+Shift+C
copy_selection_to_new_layer=Ctrl+Alt+J
copy_sharp=none
create_bundle=none
create_copy=none
create_template=none
cut_selection_to_new_layer=Ctrl+Shift+J
cut_sharp=none
decrease_brush_size=[
decrease_opacity=I
delete_keyframe=none
deselect=Ctrl+Shift+A
dual=none
duplicatelayer=Ctrl+J
edit_copy=Ctrl+C
edit_cut=Ctrl+X
EditLayerMetaData=none
edit_paste=Ctrl+V
edit_redo=Ctrl+Shift+Z
edit_undo=Ctrl+Z
erase_action=E
featherselection=Shift+F6
file_close_all=Ctrl+Shift+W
file_close=Ctrl+W
file_documentinfo=none
file_export_animation=none
file_export_file=none
file_export_pdf=none
file_import_file=none
file_new=Ctrl+N
file_open=Ctrl+O
file_open_recent=none
file_print=Ctrl+P
file_print_preview=none
file_quit=Ctrl+Q
file_save_as=Ctrl+Shift+S
file_save=Ctrl+S
fill_selection_background_color=Backspace
fill_selection_background_color_opacity=Ctrl+Backspace
fill_selection_foreground_color_opacity=Ctrl+Shift+Backspace
fill_selection_foreground_color=Shift+Backspace
fill_selection_pattern=none
fill_selection_pattern_opacity=none
filter_apply_again=Ctrl+F
first_frame=none
flatten_image=Ctrl+Shift+E
flatten_layer=none
fullscreen=Ctrl+Shift+F
gmic=none
gradients=none
growselection=none
help_about_app=none
help_about_kde=none
help_contents=F1
help_report_bug=none
histogram=none
hmirror_action=none
image_color=none
imagecolorspaceconversion=none
image_properties=none
imagesize=Ctrl+Alt+I
imagesplit=none
import_layer_as_filter_mask=none
import_layer_as_paint_layer=none
import_layer_as_selection_mask=none
import_layer_as_transparency_mask=none
import_layer_from_file=none
import_resources=none
increase_brush_size=]
increase_opacity=O
invert=Ctrl+Shift+I
-isolate_layer=none
+isolate_active_layer=none
krita_filter_autocontrast=none
krita_filter_blur=none
krita_filter_bottom edge detections=none
krita_filter_brightnesscontrast=none
krita_filter_burn=none
krita_filter_colorbalance=Ctrl+B
krita_filter_colortoalpha=none
krita_filter_colortransfer=none
krita_filter_desaturate=Ctrl+Shift+U
krita_filter_dodge=none
krita_filter_emboss all directions=none
krita_filter_emboss horizontal and vertical=none
krita_filter_emboss horizontal only=none
krita_filter_emboss laplascian=none
krita_filter_emboss=none
krita_filter_emboss vertical only=none
krita_filter_gaussian blur=none
krita_filter_gaussiannoisereducer=none
krita_filter_hsvadjustment=Ctrl+U
krita_filter_indexcolors=none
krita_filter_invert=Ctrl+I
krita_filter_left edge detections=none
krita_filter_lens blur=none
krita_filter_levels=Ctrl+L
krita_filter_maximize=none
krita_filter_mean removal=none
krita_filter_minimize=none
krita_filter_motion blur=none
krita_filter_noise=none
krita_filter_oilpaint=none
krita_filter_perchannel=Ctrl+M
krita_filter_phongbumpmap=none
krita_filter_pixelize=none
krita_filter_posterize=none
krita_filter_raindrops=none
krita_filter_randompick=none
krita_filter_right edge detections=none
krita_filter_roundcorners=none
krita_filter_sharpen=none
krita_filter_smalltiles=none
krita_filter_sobel=none
krita_filter_top edge detections=none
krita_filter_unsharp=none
krita_filter_waveletnoisereducer=none
krita_filter_wave=none
last_frame=none
layercolorspaceconversion=none
LayerGroupSwitcher/next=none
LayerGroupSwitcher/previous=none
layer_properties=none
layersize=none
layersplit=none
layer_style=none
lazy_frame=none
level_of_detail_mode=Shift+L
Macro_Open_Edit=none
Macro_Open_Play=none
mainToolBar=none
make_brush_color_darker=K
make_brush_color_lighter=L
manage_bundles=none
merge_layer=Ctrl+E
mirror_actions=none
mirror_canvas=M
mirrorImageHorizontal=none
mirrorImageVertical=none
mirrorNodeX=none
mirrorNodeY=none
move_layer_down=none
move_layer_up=none
next_favorite_preset=,
next_frame=none
next_keyframe=none
offsetimage=none
offsetlayer=none
open_resources_directory=none
options_configure_keybinding=none
options_configure=none
options_configure_toolbars=none
paintops=none
paste_at=none
paste_new=Ctrl+Shift+N
paste_as_reference=Ctrl+Shift+R
patterns=none
preserve_alpha=none
previous_favorite_preset=.
previous_frame=none
previous_keyframe=none
previous_preset=/
rasterize_layer=none
Recording_Start_Recording_Macro=none
Recording_Stop_Recording_Macro=none
reload_preset_action=none
remove_layer=Shift+Delete
RenameCurrentLayer=F2
reselect=Ctrl+Shift+D
resizeimagetolayer=none
resizeimagetoselection=none
rotate_canvas_left=Ctrl+[
rotate_canvas_right=Ctrl+]
rotateImage180=none
rotateImageCCW90=none
rotateImageCW90=none
rotateimage=none
rotateLayer180=none
rotateLayerCCW90=none
rotateLayerCW90=none
rotatelayer=none
save_groups_as_images=none
save_incremental_backup=F4
save_incremental_version=Ctrl+Alt+S
save_node_as_image=none
select_all=Ctrl+A
Select Behind Blending Mode=Alt+Shift+Q
Select Clear Blending Mode=Alt+Shift+R
Select Color Blending Mode=Alt+Shift+C
Select Color Burn Blending Mode=Alt+Shift+B
Select Color Dodge Blending Mode=Alt+Shift+D
Select Darken Blending Mode=Alt+Shift+K
Select Difference Blending Mode=Alt+Shift+E
Select Dissolve Blending Mode=Alt+Shift+I
Select Exclusion Blending Mode=Alt+Shift+X
Select Hard Light Blending Mode=Alt+Shift+H
Select Hard Mix Blending Mode=Alt+Shift+L
Select Hue Blending Mode=Alt+Shift+U
selectionscale=none
Select Lighten Blending Mode=Alt+Shift+G
Select Linear Burn Blending Mode=Alt+Shift+A
Select Linear Dodge Blending Mode=Alt+Shift+W
Select Linear Light Blending Mode=Alt+Shift+J
Select Luminosity Blending Mode=Alt+Shift+Y
Select Multiply Blending Mode=Alt+Shift+M
Select Normal Blending Mode=Alt+Shift+N
select_opaque=none
selectopaque=none
Select Overlay Blending Mode=Alt+Shift+O
Select Pin Light Blending Mode=Alt+Shift+Z
Select Saturation Blending Mode=Alt+Shift+T
Select Screen Blending Mode=Alt+Shift+S
Select Soft Light Blending Mode=Alt+Shift+F
Select Vivid Light Blending Mode=Alt+Shift+V
separate=none
settings_active_author=none
shearimage=none
shearlayer=none
show_brush_presets=F6
show-global-selection-mask=none
pin_to_timeline=none
showStatusBar=none
shrinkselection=none
smoothselection=none
split_alpha_into_mask=none
split_alpha_save_merged=none
split_alpha_write=none
stroke_shapes=none
tablet_debugger=Ctrl+Shift+T
toggle_display_selection=Ctrl+H
toggle_playback=none
toggle-selection-overlay-mode=none
trim_to_image=none
view_clear_perspective_grid=none
view_grid=Ctrl+Shift+'
view_newwindow=none
view_ruler=none
view_show_canvas_only=Tab
view_show_guides=none
view_snap_to_grid=Ctrl+Shift+;
view_toggle_assistant_previews=none
view_toggledockers=none
view_toggledockertitlebars=none
view_toggle_painting_assistants=none
view_toggle_perspective_grid=none
view_zoom_in=Ctrl++
view_zoom_out=Ctrl+-
vmirror_action=none
windows_cascade=none
windows_next=Ctrl+Tab
windows_previous=none
windows_tile=none
workspaces=none
zoom_to_100pct=Ctrl+0
diff --git a/krita/data/shortcuts/paint_tool_sai_compatible.shortcuts b/krita/data/shortcuts/paint_tool_sai_compatible.shortcuts
index 9798a71af5..08eb92b5a2 100644
--- a/krita/data/shortcuts/paint_tool_sai_compatible.shortcuts
+++ b/krita/data/shortcuts/paint_tool_sai_compatible.shortcuts
@@ -1,428 +1,428 @@
[Shortcuts]
activateNextLayer=PgUp
activatePreviousLayer=PgDown
add_blank_frame=none
add_duplicate_frame=none
add_new_adjustment_layer=none
add_new_clone_layer=none
add_new_file_layer=none
add_new_fill_layer=none
add_new_filter_mask=none
add_new_group_layer=none
add_new_paint_layer=none
add_new_selection_mask=none
add_new_shape_layer=none
add_new_transform_mask=none
add_new_transparency_mask=none
artistictext_anchor_end=none
artistictext_anchor_middle=none
artistictext_anchor_start=none
artistictext_convert_to_path=none
artistictext_detach_from_path=none
artistictext_font_bold=none
artistictext_font_italic=none
artistictext_subscript=none
artistictext_superscript=none
ArtisticTextTool=none
borderselection=none
BrushesAndStuff=none
brushslider2=none
brushslider3=none
canvassize=Ctrl+Alt+C
change_text_direction=Ctrl+Shift+D
clear=D
clones_array=none
colorrange=none
composite_actions=none
configure_bibliography=none
configure_section=none
convert_selection_to_shape=none
convert_shapes_to_vector_selection=none
convert_to_filter_mask=none
convert_to_paint_layer=none
convert-to-path=none
convert_to_selection_mask=none
convert_to_transparency_mask=none
convert_to_vector_selection=none
copy_merged=Ctrl+Shift+C
copy_selection_to_new_layer=Ctrl+Alt+J
copy_sharp=none
create_bundle=none
create_copy=none
CreateShapesTool=none
create_template=none
cut_selection_to_new_layer=Ctrl+Shift+J
cut_sharp=none
decrease_brush_size=[
decrease_opacity=I
delete_keyframe=none
deselect=Ctrl+Shift+A
dual=none
duplicatelayer=Ctrl+J
edit_copy=Ctrl+C
edit_cut=Ctrl+X
edit_deselect_all=Ctrl+Shift+A
EditLayerMetaData=none
edit_paste=Ctrl+V
edit_paste_text=none
edit_redo=Ctrl+Y
edit_select_all=Ctrl+A
edit_undo=Ctrl+Z
erase_action=E
featherselection=Shift+F6
file_close_all=Ctrl+Shift+W
file_close=none
file_documentinfo=none
file_export_animation=none
file_export_file=none
file_export_pdf=none
file_import_file=none
file_new=Ctrl+N
file_open=Ctrl+O
file_open_recent=F4
file_print=Ctrl+P
file_print_preview=none
file_quit=Ctrl+Q
file_save_as=Ctrl+Shift+S
file_save=Ctrl+S
fill_selection_background_color=Backspace
fill_selection_background_color_opacity=Ctrl+Backspace
fill_selection_foreground_color_opacity=Ctrl+Shift+Backspace
fill_selection_foreground_color=Shift+Backspace
fill_selection_pattern=none
fill_selection_pattern_opacity=none
filter_apply_again=Ctrl+F
first_frame=none
flatten_image=Ctrl+Shift+E
flatten_layer=none
fontsizedown=Ctrl+<
fontsizeup=Ctrl+>
format_alignblock=Ctrl+Alt+R
format_aligncenter=Ctrl+Alt+C
format_alignleft=none
format_alignright=Ctrl+Alt+R
format_backgroundcolor=none
format_bold=Ctrl+B
format_bulletlist=none
format_decreaseindent=none
format_endnotes=none
format_font=Ctrl+Alt+F
format_fontfamily=none
format_fontsize=none
format_footnotes=none
format_increaseindent=none
format_italic=Ctrl+I
format_numberlist=none
format_paragraph=Ctrl+Alt+P
format_strike=none
format_stylist=Ctrl+Alt+S
format_sub=Ctrl+Shift+B
format_super=Ctrl+Shift+P
format_tableofcontents=none
format_textcolor=none
format_underline=Ctrl+U
fullscreen=Ctrl+Shift+F
gmic=none
gradients=none
growselection=none
grow_to_fit_height=none
grow_to_fit_width=none
help_about_app=none
help_about_kde=none
help_contents=F1
help_report_bug=none
histogram=none
hmirror_action=none
image_color=none
imagecolorspaceconversion=none
image_properties=none
imagesize=Ctrl+Alt+I
imagesplit=none
import_layer_as_filter_mask=none
import_layer_as_paint_layer=none
import_layer_as_selection_mask=none
import_layer_as_transparency_mask=none
import_layer_from_file=none
import_resources=none
increase_brush_size=]
increase_opacity=O
insert_annotation=Ctrl+Shift+C
insert_autoendnote=none
insert_autofootnote=none
insert_bibliography=none
insert_bookmark=none
insert_citation=none
insert_configure_tableofcontents=none
insert_custom_bibliography=none
insert_index=Ctrl+T
insert_labeledendnote=none
insert_labeledfootnote=none
insert_link=none
insert_section=none
insert_specialchar=Alt+Shift+C
insert_tableofcontents=none
InteractionTool=none
invert=Ctrl+Shift+I
invoke_bookmark_handler=none
-isolate_layer=none
+isolate_active_layer=none
KarbonCalligraphyTool=none
KarbonGradientTool=none
KarbonPatternTool=none
KisRulerAssistantTool=none
KisToolCrop=C
KisToolPath=P
KisToolPencil=none
KisToolPerspectiveGrid=none
KisToolPolygon=none
KisToolPolyline=none
KisToolSelectContiguous=W
KisToolSelectElliptical=J
KisToolSelectOutline=none
KisToolSelectPath=none
KisToolSelectPolygonal=none
KisToolSelectRectangular=M
KisToolSelectSimilar=none
KisToolTransform=Ctrl+T
KritaFill/KisToolFill=G
KritaFill/KisToolGradient=none
krita_filter_autocontrast=none
krita_filter_blur=none
krita_filter_bottom edge detections=none
krita_filter_brightnesscontrast=none
krita_filter_burn=none
krita_filter_colorbalance=Ctrl+B
krita_filter_colortoalpha=none
krita_filter_colortransfer=none
krita_filter_desaturate=Ctrl+Shift+U
krita_filter_dodge=none
krita_filter_emboss all directions=none
krita_filter_emboss horizontal and vertical=none
krita_filter_emboss horizontal only=none
krita_filter_emboss laplascian=none
krita_filter_emboss=none
krita_filter_emboss vertical only=none
krita_filter_gaussian blur=none
krita_filter_gaussiannoisereducer=none
krita_filter_hsvadjustment=Ctrl+U
krita_filter_indexcolors=none
krita_filter_invert=Ctrl+I
krita_filter_left edge detections=none
krita_filter_lens blur=none
krita_filter_levels=Ctrl+L
krita_filter_maximize=none
krita_filter_mean removal=none
krita_filter_minimize=none
krita_filter_motion blur=none
krita_filter_noise=none
krita_filter_oilpaint=none
krita_filter_perchannel=Ctrl+M
krita_filter_phongbumpmap=none
krita_filter_pixelize=none
krita_filter_posterize=none
krita_filter_raindrops=none
krita_filter_randompick=none
krita_filter_right edge detections=none
krita_filter_roundcorners=none
krita_filter_sharpen=none
krita_filter_smalltiles=none
krita_filter_sobel=none
krita_filter_top edge detections=none
krita_filter_unsharp=none
krita_filter_waveletnoisereducer=none
krita_filter_wave=none
KritaSelected/KisToolColorPicker=none
KritaShape/KisToolBrush=B
KritaShape/KisToolDyna=none
KritaShape/KisToolEllipse=none
KritaShape/KisToolLine=none
KritaShape/KisToolMeasure=none
KritaShape/KisToolMultiBrush=Q
KritaShape/KisToolRectangle=none
KritaShape/KisToolText=none
KritaTransform/KisToolMove=T
last_frame=none
layercolorspaceconversion=none
LayerGroupSwitcher/next=none
LayerGroupSwitcher/previous=none
layer_properties=none
layersize=none
layersplit=none
layer_style=none
lazy_frame=none
level_of_detail_mode=Shift+L
Macro_Open_Edit=none
Macro_Open_Play=none
mainToolBar=none
make_brush_color_darker=K
make_brush_color_lighter=L
manage_bookmarks=none
manage_bundles=none
merge_layer=Ctrl+E
mirror_actions=none
mirror_canvas=Shift+H
mirrorImageHorizontal=none
mirrorImageVertical=none
mirrorNodeX=none
mirrorNodeY=none
move_layer_down=Ctrl+PgDown
move_layer_up=Ctrl+PgDown
next_favorite_preset=,
next_frame=none
next_keyframe=none
nonbreaking_hyphen=Ctrl+Shift+-
nonbreaking_space=Ctrl+Space
object_align_horizontal_center=none
object_align_horizontal_left=none
object_align_horizontal_right=none
object_align_vertical_bottom=none
object_align_vertical_center=none
object_align_vertical_top=none
object_group=none
object_order_back=Ctrl+Shift+[
object_order_front=Ctrl+Shift+]
object_order_lower=Ctrl+[
object_order_raise=Ctrl+]
object_ungroup=none
offsetimage=none
offsetlayer=none
open_resources_directory=none
options_configure_keybinding=none
options_configure=none
options_configure_toolbars=none
paintops=none
paste_at=none
paste_new=Ctrl+Shift+N
path-break-point=none
path-break-segment=none
pathpoint-corner=none
pathpoint-curve=none
pathpoint-insert=Ins
pathpoint-join=J
pathpoint-line=none
pathpoint-merge=none
pathpoint-remove=Backspace
pathpoint-smooth=none
pathpoint-symmetric=none
pathsegment-curve=Shift+C
pathsegment-line=F
PathTool=none
patterns=none
preserve_alpha=none
previous_favorite_preset=.
previous_frame=none
previous_keyframe=none
previous_preset=/
rasterize_layer=none
Recording_Start_Recording_Macro=none
Recording_Stop_Recording_Macro=none
ReferencesTool=none
reload_preset_action=none
remove_layer=none
rename_composition=none
RenameCurrentLayer=F2
repaint=none
reselect=Ctrl+Shift+D
reset_fg_bg=D
resizeimagetolayer=none
resizeimagetoselection=none
ReviewTool=none
rotate_canvas_left=Ctrl+[
rotate_canvas_right=Ctrl+]
rotateImage180=none
rotateImageCCW90=none
rotateImageCW90=none
rotateimage=none
rotateLayer180=none
rotateLayerCCW90=none
rotateLayerCW90=none
rotatelayer=none
save_groups_as_images=none
save_incremental_backup=F4
save_incremental_version=Ctrl+Alt+S
save_node_as_image=none
select_all=Ctrl+A
Select Behind Blending Mode=Alt+Shift+Q
Select Clear Blending Mode=Alt+Shift+R
Select Color Blending Mode=Alt+Shift+C
Select Color Burn Blending Mode=Alt+Shift+B
Select Color Dodge Blending Mode=Alt+Shift+D
Select Darken Blending Mode=Alt+Shift+K
Select Difference Blending Mode=Alt+Shift+E
Select Dissolve Blending Mode=Alt+Shift+I
Select Exclusion Blending Mode=Alt+Shift+X
Select Hard Light Blending Mode=Alt+Shift+H
Select Hard Mix Blending Mode=Alt+Shift+L
Select Hue Blending Mode=Alt+Shift+U
selectionscale=none
Select Lighten Blending Mode=Alt+Shift+G
Select Linear Burn Blending Mode=Alt+Shift+A
Select Linear Dodge Blending Mode=Alt+Shift+W
Select Linear Light Blending Mode=Alt+Shift+J
Select Luminosity Blending Mode=Alt+Shift+Y
Select Multiply Blending Mode=Alt+Shift+M
Select Normal Blending Mode=Alt+Shift+N
select_opaque=none
selectopaque=none
Select Overlay Blending Mode=Alt+Shift+O
Select Pin Light Blending Mode=Alt+Shift+Z
Select Saturation Blending Mode=Alt+Shift+T
Select Screen Blending Mode=Alt+Shift+S
Select Soft Light Blending Mode=Alt+Shift+F
Select Vivid Light Blending Mode=Alt+Shift+V
separate=none
set_no_brush_smoothing=none
set_simple_brush_smoothing=none
set_stabilizer_brush_smoothing=none
settings_active_author=none
set_weighted_brush_smoothing=none
shearimage=none
shearlayer=none
show_brush_presets=none
show_color_history=H
show_color_selector=Shift+I
show_common_colors=U
show-global-selection-mask=none
pin_to_timeline=none
show_minimal_shade_selector=Shift+N
show_mypaint_shade_selector=Shift+M
showStatusBar=none
shrinkselection=none
shrink_to_fit=none
smoothselection=none
soft_hyphen=none
split_alpha_into_mask=none
split_alpha_save_merged=none
split_alpha_write=none
split_sections=none
stroke_shapes=none
tablet_debugger=Ctrl+Shift+T
TextTool=none
toggle_assistant=Ctrl+Shift+L
toggle_display_selection=Ctrl+H
toggle_fg_bg=X
toggle_playback=none
toggle-selection-overlay-mode=none
trim_to_image=none
undo_polygon_selection=Shift+Z
VectorTool=none
view_clear_perspective_grid=none
view_grid=Ctrl+Shift+'
view_newwindow=none
view_ruler=none
view_show_canvas_only=none
view_show_guides=none
view_snap_to_grid=Ctrl+Shift+;
view_toggle_assistant_previews=none
view_toggledockers=Tab
view_toggledockertitlebars=none
view_toggle_painting_assistants=none
view_toggle_perspective_grid=none
view_zoom_in=none
view_zoom_out=none
vmirror_action=none
windows_cascade=none
windows_next=Ctrl+Tab
windows_previous=Ctrl+Shift+Tab
windows_tile=none
workspaces=none
zoom_to_100pct=Home
diff --git a/krita/data/shortcuts/photoshop_compatible.shortcuts b/krita/data/shortcuts/photoshop_compatible.shortcuts
index 6ce6e2e283..b0e4a57c71 100644
--- a/krita/data/shortcuts/photoshop_compatible.shortcuts
+++ b/krita/data/shortcuts/photoshop_compatible.shortcuts
@@ -1,428 +1,428 @@
black[Shortcuts]
activateNextLayer=Alt+]
activatePreviousLayer=Alt+[
add_blank_frame=none
add_duplicate_frame=none
add_new_adjustment_layer=none
add_new_clone_layer=none
add_new_file_layer=none
add_new_fill_layer=none
add_new_filter_mask=none
add_new_group_layer=Ctrl+G
add_new_paint_layer=Ctrl+Shift+N
add_new_selection_mask=none
add_new_shape_layer=none
add_new_transform_mask=none
add_new_transparency_mask=none
artistictext_anchor_end=none
artistictext_anchor_middle=none
artistictext_anchor_start=none
artistictext_convert_to_path=none
artistictext_detach_from_path=none
artistictext_font_bold=none
artistictext_font_italic=none
artistictext_subscript=none
artistictext_superscript=none
ArtisticTextTool=none
borderselection=none
BrushesAndStuff=none
brushslider2=none
brushslider3=none
canvassize=Ctrl+Alt+C
change_text_direction=Ctrl+Shift+D
clear=Del
clones_array=none
colorrange=none
composite_actions=none
configure_bibliography=none
configure_section=none
convert_selection_to_shape=none
convert_shapes_to_vector_selection=none
convert_to_filter_mask=none
convert_to_paint_layer=none
convert-to-path=P
convert_to_selection_mask=none
convert_to_transparency_mask=none
convert_to_vector_selection=none
copy_merged=Ctrl+Shift+C
copy_selection_to_new_layer=Ctrl+Alt+J
copy_sharp=none
create_bundle=none
create_copy=none
CreateShapesTool=none
create_template=none
cut_selection_to_new_layer=Ctrl+Shift+J
cut_sharp=none
decrease_brush_size=[
decrease_opacity=;\s
delete_keyframe=none
deselect=Ctrl+D
dual=none
duplicatelayer=Ctrl+J
edit_copy=Ctrl+C
edit_cut=Ctrl+X
edit_deselect_all=Ctrl+Shift+A
EditLayerMetaData=none
edit_paste=Ctrl+V
edit_paste_text=none
edit_redo=Ctrl+Shift+Z
edit_select_all=Ctrl+A
edit_undo=Ctrl+Z
erase_action=E
featherselection=Shift+F6
file_close_all=Ctrl+Shift+W
file_close=Ctrl+W
file_documentinfo=Ctrl+Alt+Shift+I
file_export_animation=none
file_export_file=none
file_export_pdf=none
file_import_file=none
file_new=Ctrl+N
file_open=Ctrl+O
file_open_recent=F4
file_print=Ctrl+P
file_print_preview=none
file_quit=Ctrl+Q
file_save_as=Ctrl+Shift+S
file_save=Ctrl+S
fill_selection_background_color=Backspace
fill_selection_background_color_opacity=Ctrl+Backspace
fill_selection_foreground_color_opacity=Ctrl+Shift+Backspace
fill_selection_foreground_color=Shift+Backspace
fill_selection_pattern=none
fill_selection_pattern_opacity=none
filter_apply_again=Ctrl+F
first_frame=none
flatten_image=Ctrl+Shift+E
flatten_layer=none
fontsizedown=Ctrl+<
fontsizeup=Ctrl+>
format_alignblock=Ctrl+Alt+R
format_aligncenter=Ctrl+Alt+C
format_alignleft=none
format_alignright=Ctrl+Alt+R
format_backgroundcolor=none
format_bold=Ctrl+B
format_bulletlist=none
format_decreaseindent=none
format_endnotes=none
format_font=Ctrl+Alt+F
format_fontfamily=none
format_fontsize=none
format_footnotes=none
format_increaseindent=none
format_italic=Ctrl+I
format_numberlist=none
format_paragraph=Ctrl+Alt+P
format_strike=none
format_stylist=Ctrl+Alt+S
format_sub=Ctrl+Shift+B
format_super=Ctrl+Shift+P
format_tableofcontents=none
format_textcolor=none
format_underline=Ctrl+U
fullscreen=Ctrl+Shift+F
gmic=none
gradients=none
growselection=none
grow_to_fit_height=none
grow_to_fit_width=none
help_about_app=none
help_about_kde=none
help_contents=F1
help_report_bug=none
histogram=none
hmirror_action=none
image_color=none
imagecolorspaceconversion=none
image_properties=none
imagesize=Ctrl+Alt+I
imagesplit=none
import_layer_as_filter_mask=none
import_layer_as_paint_layer=none
import_layer_as_selection_mask=none
import_layer_as_transparency_mask=none
import_layer_from_file=none
import_resources=none
increase_brush_size=]
increase_opacity=O
insert_annotation=Ctrl+Shift+C
insert_autoendnote=none
insert_autofootnote=none
insert_bibliography=none
insert_bookmark=none
insert_citation=none
insert_configure_tableofcontents=none
insert_custom_bibliography=none
insert_index=Ctrl+T
insert_labeledendnote=none
insert_labeledfootnote=none
insert_link=none
insert_section=none
insert_specialchar=Alt+Shift+C
insert_tableofcontents=none
InteractionTool=none
invert=Ctrl+Shift+I
invoke_bookmark_handler=none
-isolate_layer=none
+isolate_active_layer=none
KarbonCalligraphyTool=none
KarbonGradientTool=none
KarbonPatternTool=none
KisRulerAssistantTool=none
KisToolCrop=C
KisToolPath=none
KisToolPencil=none
KisToolPerspectiveGrid=none
KisToolPolygon=none
KisToolPolyline=none
KisToolSelectContiguous=none
KisToolSelectElliptical=Shift+M
KisToolSelectOutline=none
KisToolSelectPath=none
KisToolSelectPolygonal=none
KisToolSelectRectangular=M
KisToolSelectSimilar=none
KisToolTransform=Ctrl+T
KritaFill/KisToolFill=Shift+G
KritaFill/KisToolGradient=G
krita_filter_autocontrast=none
krita_filter_blur=none
krita_filter_bottom edge detections=none
krita_filter_brightnesscontrast=none
krita_filter_burn=none
krita_filter_colorbalance=Ctrl+B
krita_filter_colortoalpha=none
krita_filter_colortransfer=none
krita_filter_desaturate=Ctrl+Shift+U
krita_filter_dodge=none
krita_filter_emboss all directions=none
krita_filter_emboss horizontal and vertical=none
krita_filter_emboss horizontal only=none
krita_filter_emboss laplascian=none
krita_filter_emboss=none
krita_filter_emboss vertical only=none
krita_filter_gaussian blur=none
krita_filter_gaussiannoisereducer=none
krita_filter_hsvadjustment=Ctrl+U
krita_filter_indexcolors=none
krita_filter_invert=Ctrl+I
krita_filter_left edge detections=none
krita_filter_lens blur=none
krita_filter_levels=Ctrl+L
krita_filter_maximize=none
krita_filter_mean removal=none
krita_filter_minimize=none
krita_filter_motion blur=none
krita_filter_noise=none
krita_filter_oilpaint=none
krita_filter_perchannel=Ctrl+M
krita_filter_phongbumpmap=none
krita_filter_pixelize=none
krita_filter_posterize=none
krita_filter_raindrops=none
krita_filter_randompick=none
krita_filter_right edge detections=none
krita_filter_roundcorners=none
krita_filter_sharpen=none
krita_filter_smalltiles=none
krita_filter_sobel=none
krita_filter_top edge detections=none
krita_filter_unsharp=none
krita_filter_waveletnoisereducer=none
krita_filter_wave=none
KritaSelected/KisToolColorPicker=I
KritaShape/KisToolBrush=B
KritaShape/KisToolDyna=none
KritaShape/KisToolEllipse=none
KritaShape/KisToolLine=none
KritaShape/KisToolMeasure=none
KritaShape/KisToolMultiBrush=Q
KritaShape/KisToolRectangle=none
KritaShape/KisToolText=T
KritaTransform/KisToolMove=V
last_frame=none
layercolorspaceconversion=none
LayerGroupSwitcher/next=none
LayerGroupSwitcher/previous=none
layer_properties=none
layersize=none
layersplit=none
layer_style=none
lazy_frame=none
level_of_detail_mode=Shift+L
Macro_Open_Edit=none
Macro_Open_Play=none
mainToolBar=none
make_brush_color_darker=K
make_brush_color_lighter=L
manage_bookmarks=none
manage_bundles=none
merge_layer=Ctrl+E
mirror_actions=none
mirror_canvas=;\s
mirrorImageHorizontal=none
mirrorImageVertical=none
mirrorNodeX=none
mirrorNodeY=none
move_layer_down=Ctrl+[
move_layer_up=Ctrl+]
next_favorite_preset=,
next_frame=none
next_keyframe=none
nonbreaking_hyphen=Ctrl+Shift+-
nonbreaking_space=Ctrl+Space
object_align_horizontal_center=none
object_align_horizontal_left=none
object_align_horizontal_right=none
object_align_vertical_bottom=none
object_align_vertical_center=none
object_align_vertical_top=none
object_group=none
object_order_back=Ctrl+Shift+[
object_order_front=Ctrl+Shift+]
object_order_lower=none
object_order_raise=none
object_ungroup=none
offsetimage=none
offsetlayer=none
open_resources_directory=none
options_configure_keybinding=none
options_configure=none
options_configure_toolbars=none
paintops=none
paste_at=none
paste_new=none
path-break-point=none
path-break-segment=none
pathpoint-corner=none
pathpoint-curve=none
pathpoint-insert=Ins
pathpoint-join=J
pathpoint-line=none
pathpoint-merge=none
pathpoint-remove=Backspace
pathpoint-smooth=none
pathpoint-symmetric=none
pathsegment-curve=Shift+C
pathsegment-line=F
PathTool=none
patterns=none
preserve_alpha=none
previous_favorite_preset=.
previous_frame=none
previous_keyframe=none
previous_preset=/
rasterize_layer=none
Recording_Start_Recording_Macro=none
Recording_Stop_Recording_Macro=none
ReferencesTool=none
reload_preset_action=none
remove_layer=none
rename_composition=none
RenameCurrentLayer=F2
repaint=none
reselect=Ctrl+Shift+D
reset_fg_bg=D
resizeimagetolayer=none
resizeimagetoselection=none
ReviewTool=none
rotate_canvas_left=none
rotate_canvas_right=none
rotateImage180=none
rotateImageCCW90=none
rotateImageCW90=none
rotateimage=none
rotateLayer180=none
rotateLayerCCW90=none
rotateLayerCW90=none
rotatelayer=none
save_groups_as_images=none
save_incremental_backup=F4
save_incremental_version=Ctrl+Alt+S
save_node_as_image=none
select_all=Ctrl+A
Select Behind Blending Mode=Alt+Shift+Q
Select Clear Blending Mode=Alt+Shift+R
Select Color Blending Mode=Alt+Shift+C
Select Color Burn Blending Mode=Alt+Shift+B
Select Color Dodge Blending Mode=Alt+Shift+D
Select Darken Blending Mode=Alt+Shift+K
Select Difference Blending Mode=Alt+Shift+E
Select Dissolve Blending Mode=Alt+Shift+I
Select Exclusion Blending Mode=Alt+Shift+X
Select Hard Light Blending Mode=Alt+Shift+H
Select Hard Mix Blending Mode=Alt+Shift+L
Select Hue Blending Mode=Alt+Shift+U
selectionscale=none
Select Lighten Blending Mode=Alt+Shift+G
Select Linear Burn Blending Mode=Alt+Shift+A
Select Linear Dodge Blending Mode=Alt+Shift+W
Select Linear Light Blending Mode=Alt+Shift+J
Select Luminosity Blending Mode=Alt+Shift+Y
Select Multiply Blending Mode=Alt+Shift+M
Select Normal Blending Mode=Alt+Shift+N
select_opaque=none
selectopaque=none
Select Overlay Blending Mode=Alt+Shift+O
Select Pin Light Blending Mode=Alt+Shift+Z
Select Saturation Blending Mode=Alt+Shift+T
Select Screen Blending Mode=Alt+Shift+S
Select Soft Light Blending Mode=Alt+Shift+F
Select Vivid Light Blending Mode=Alt+Shift+V
separate=none
set_no_brush_smoothing=none
set_simple_brush_smoothing=none
set_stabilizer_brush_smoothing=none
settings_active_author=none
set_weighted_brush_smoothing=none
shearimage=none
shearlayer=none
show_brush_presets=F6
show_color_history=H
show_color_selector=Shift+I
show_common_colors=U
show-global-selection-mask=none
pin_to_timeline=none
show_minimal_shade_selector=Shift+N
show_mypaint_shade_selector=;\s
showStatusBar=none
shrinkselection=none
shrink_to_fit=none
smoothselection=none
soft_hyphen=none
split_alpha_into_mask=none
split_alpha_save_merged=none
split_alpha_write=none
split_sections=none
stroke_shapes=none
tablet_debugger=Ctrl+Shift+T
TextTool=none
toggle_assistant=Ctrl+Shift+L
toggle_display_selection=Ctrl+H
toggle_fg_bg=X
toggle_playback=none
toggle-selection-overlay-mode=none
trim_to_image=none
undo_polygon_selection=Shift+Z
VectorTool=none
view_clear_perspective_grid=none
view_grid=Ctrl+Shift+'
view_newwindow=none
view_ruler=none
view_show_canvas_only=Tab
view_show_guides=none
view_snap_to_grid=Ctrl+Shift+;
view_toggle_assistant_previews=none
view_toggledockers=none
view_toggledockertitlebars=none
view_toggle_painting_assistants=none
view_toggle_perspective_grid=none
view_zoom_in=Ctrl++
view_zoom_out=Ctrl+-
vmirror_action=none
windows_cascade=none
windows_next=none
windows_previous=none
windows_tile=none
workspaces=none
zoom_to_100pct=Ctrl+0
diff --git a/krita/data/shortcuts/tablet_pro.shortcuts b/krita/data/shortcuts/tablet_pro.shortcuts
index ab05346a68..6fbd1a5449 100644
--- a/krita/data/shortcuts/tablet_pro.shortcuts
+++ b/krita/data/shortcuts/tablet_pro.shortcuts
@@ -1,466 +1,466 @@
[Shortcuts]
activateNextLayer=PgUp
activatePreviousLayer=PgDown
add_blank_frame=none
add_duplicate_frame=none
add_new_adjustment_layer=none
add_new_clone_layer=none
add_new_file_layer=none
add_new_fill_layer=none
add_new_filter_mask=none
add_new_group_layer=none
add_new_paint_layer=Ins
add_new_selection_mask=none
add_new_shape_layer=none
add_new_transform_mask=none
add_new_transparency_mask=none
artistictext_anchor_end=none
artistictext_anchor_middle=none
artistictext_anchor_start=none
artistictext_convert_to_path=none
artistictext_detach_from_path=none
artistictext_font_bold=none
artistictext_font_italic=none
artistictext_subscript=none
artistictext_superscript=none
ArtisticTextTool=none
borderselection=none
BrushesAndStuff=none
brushslider1=none
brushslider2=none
brushslider3=none
canvassize=Ctrl+Alt+C
change_text_direction=Ctrl+Shift+D
clear=Del
clones_array=none
colorrange=none
composite_actions=none
configure_bibliography=none
configure_section=none
convert_selection_to_shape=none
convert_shapes_to_vector_selection=none
convert_to_filter_mask=none
convert_to_paint_layer=none
convert-to-path=P
convert_to_selection_mask=none
convert_to_transparency_mask=none
convert_to_vector_selection=none
copy_layer_clipboard=none
copy_merged=Ctrl+Shift+C
copy_selection_to_new_layer=Ctrl+Alt+J
copy_sharp=none
create_bundle=none
create_copy=none
create_quick_clipping_group=Ctrl+Shift+G
create_quick_group=Ctrl+G
CreateShapesTool=none
create_template=none
cut_layer_clipboard=none
cut_selection_to_new_layer=Ctrl+Shift+J
cut_sharp=none
decrease_brush_size=[
decrease_opacity=none
delete_keyframe=none
deselect=Ctrl+Shift+A
drop_frames=none
dual=none
duplicatelayer=Ctrl+J
edit_copy=Ctrl+C
edit_cut=Ctrl+X
edit_deselect_all=Ctrl+Shift+A
EditLayerMetaData=none
edit_paste=Ctrl+V
edit_paste_text=none
edit_redo=Ctrl+Shift+Z
edit_select_all=Ctrl+A
edit_undo=Ctrl+Z
erase_action=E
featherselection=Shift+F6
file_close_all=Ctrl+Shift+W
file_close=Ctrl+W
file_documentinfo=none
file_export_animation=none
file_export_file=none
file_export_pdf=none
file_import_animation=none
file_import_file=none
file_new=Ctrl+N
file_open=Ctrl+O
file_open_recent=none
file_quit=Ctrl+Q
file_save_as=Ctrl+Shift+S
file_save=Ctrl+S
fill_selection_background_color=Backspace
fill_selection_background_color_opacity=Ctrl+Backspace
fill_selection_foreground_color_opacity=Ctrl+Shift+Backspace
fill_selection_foreground_color=Shift+Backspace
fill_selection_pattern=none
fill_selection_pattern_opacity=none
filter_apply_again=Ctrl+F
first_frame=none
flatten_image=Ctrl+Shift+E
flatten_layer=none
fontsizedown=Ctrl+<
fontsizeup=Ctrl+>
format_alignblock=Ctrl+Alt+R
format_aligncenter=Ctrl+Alt+C
format_alignleft=none
format_alignright=Ctrl+Alt+R
format_backgroundcolor=none
format_bold=Ctrl+B
format_bulletlist=none
format_decreaseindent=none
format_endnotes=none
format_font=Ctrl+Alt+F
format_fontfamily=none
format_fontsize=none
format_footnotes=none
format_increaseindent=none
format_italic=Ctrl+I
format_numberlist=none
format_paragraph=Ctrl+Alt+P
format_strike=none
format_stylist=Ctrl+Alt+S
format_sub=Ctrl+Shift+B
format_super=Ctrl+Shift+P
format_tableofcontents=none
format_textcolor=none
format_underline=Ctrl+U
gmic=none
gradients=none
growselection=none
grow_to_fit_height=none
grow_to_fit_width=none
help_about_app=none
help_about_kde=none
help_contents=F1
help_report_bug=none
histogram=none
hmirror_action=none
image_color=none
imagecolorspaceconversion=none
image_properties=none
imagesize=Ctrl+Alt+I
imagesplit=none
import_layer_as_filter_mask=none
import_layer_as_paint_layer=none
import_layer_as_selection_mask=none
import_layer_as_transparency_mask=none
import_layer_from_file=none
increase_brush_size=]
increase_opacity=O
insert_annotation=Ctrl+Shift+C
insert_autoendnote=none
insert_autofootnote=none
insert_bibliography=none
insert_bookmark=none
insert_citation=none
insert_configure_tableofcontents=none
insert_custom_bibliography=none
insert_index=Ctrl+T
insert_labeledendnote=none
insert_labeledfootnote=none
insert_link=none
insert_section=none
insert_specialchar=Alt+Shift+C
insert_tableofcontents=none
InteractionTool=none
invert=Ctrl+Shift+I
invoke_bookmark_handler=none
-isolate_layer=none
+isolate_active_layer=none
KarbonCalligraphyTool=none
KarbonGradientTool=none
KarbonPatternTool=none
KisRulerAssistantTool=none
KisToolCrop=C
KisToolPath=none
KisToolPencil=none
KisToolPolygon=none
KisToolPolyline=none
KisToolSelectContiguous=none
KisToolSelectElliptical=Shift+M
KisToolSelectOutline=L
KisToolSelectPath=none
KisToolSelectPolygonal=none
KisToolSelectRectangular=M
KisToolSelectSimilar=none
KisToolTransform=Ctrl+T
KritaFill/KisToolFill=Shift+G
KritaFill/KisToolGradient=G
krita_filter_autocontrast=none
krita_filter_blur=none
krita_filter_bottom edge detections=none
krita_filter_brightnesscontrast=none
krita_filter_burn=none
krita_filter_colorbalance=Ctrl+B
krita_filter_colortoalpha=none
krita_filter_colortransfer=none
krita_filter_desaturate=Ctrl+Shift+U
krita_filter_dodge=none
krita_filter_emboss all directions=none
krita_filter_emboss horizontal and vertical=none
krita_filter_emboss horizontal only=none
krita_filter_emboss laplascian=none
krita_filter_emboss=none
krita_filter_emboss vertical only=none
krita_filter_gaussian blur=none
krita_filter_gaussiannoisereducer=none
krita_filter_hsvadjustment=Ctrl+U
krita_filter_indexcolors=none
krita_filter_invert=Ctrl+I
krita_filter_left edge detections=none
krita_filter_lens blur=none
krita_filter_levels=Ctrl+L
krita_filter_maximize=none
krita_filter_mean removal=none
krita_filter_minimize=none
krita_filter_motion blur=none
krita_filter_noise=none
krita_filter_oilpaint=none
krita_filter_perchannel=Ctrl+M
krita_filter_phongbumpmap=none
krita_filter_pixelize=none
krita_filter_posterize=none
krita_filter_raindrops=none
krita_filter_randompick=none
krita_filter_right edge detections=none
krita_filter_roundcorners=none
krita_filter_sharpen=none
krita_filter_smalltiles=none
krita_filter_sobel=none
krita_filter_top edge detections=none
krita_filter_unsharp=none
krita_filter_waveletnoisereducer=none
krita_filter_wave=none
KritaSelected/KisToolColorPicker=I
KritaShape/KisToolBrush=B
KritaShape/KisToolDyna=none
KritaShape/KisToolEllipse=none
KritaShape/KisToolLine=none
KritaShape/KisToolMeasure=none
KritaShape/KisToolMultiBrush=Q
KritaShape/KisToolRectangle=none
KritaShape/KisToolText=T
KritaTransform/KisToolMove=V
last_frame=none
layercolorspaceconversion=none
LayerGroupSwitcher/next=none
LayerGroupSwitcher/previous=none
layer_properties=none
layersize=none
layersplit=none
layer_style=none
lazy_frame=none
level_of_detail_mode=Shift+L
Macro_Open_Edit=none
Macro_Open_Play=none
mainToolBar=none
make_brush_color_bluer=none
make_brush_color_darker=K
make_brush_color_desaturated=none
make_brush_color_greener=none
make_brush_color_lighter=none
make_brush_color_redder=none
make_brush_color_saturated=none
make_brush_color_yellower=none
manage_bookmarks=none
manage_bundles=none
merge_layer=Ctrl+E
mirror_actions=none
mirror_canvas=M
mirrorImageHorizontal=none
mirrorImageVertical=none
mirrorNodeX=none
mirrorNodeY=none
move_layer_down=none
move_layer_up=none
movetool-move-down=Down
movetool-move-down-more=Shift+Down
movetool-move-left=Left
movetool-move-left-more=Shift+Left
movetool-move-right-more=Shift+Right
movetool-move-right=Right
movetool-move-up-more=Shift+Up
movetool-move-up=Up
next_favorite_preset=,
next_frame=none
next_keyframe=none
nonbreaking_hyphen=Ctrl+Shift+-
nonbreaking_space=Ctrl+Space
object_align_horizontal_center=none
object_align_horizontal_left=none
object_align_horizontal_right=none
object_align_vertical_bottom=none
object_align_vertical_center=none
object_align_vertical_top=none
object_group=none
object_order_back=Ctrl+Shift+[
object_order_front=Ctrl+Shift+]
object_order_lower=none
object_order_raise=none
object_ungroup=none
offsetimage=none
offsetlayer=none
open_resources_directory=none
options_configure=none
options_configure_toolbars=none
paintops=none
paste_at=none
paste_layer_from_clipboard=none
paste_new=Ctrl+Shift+N
path-break-point=none
path-break-segment=none
pathpoint-corner=none
pathpoint-curve=none
pathpoint-insert=none
pathpoint-join=J
pathpoint-line=none
pathpoint-merge=none
pathpoint-remove=Backspace
pathpoint-smooth=none
pathpoint-symmetric=none
pathsegment-curve=Shift+C
pathsegment-line=F
PathTool=none
patterns=none
preserve_alpha=none
previous_favorite_preset=.
previous_frame=none
previous_keyframe=none
previous_preset=/
quick_ungroup=Ctrl+Alt+G
rasterize_layer=none
Recording_Start_Recording_Macro=none
Recording_Stop_Recording_Macro=none
ReferencesTool=none
reload_preset_action=none
remove_layer=Shift+Del
rename_composition=none
RenameCurrentLayer=F2
repaint=none
reselect=Ctrl+Shift+D
reset_canvas_rotation=none
reset_fg_bg=D
resizeimagetolayer=none
resizeimagetoselection=none
ReviewTool=none
rotate_canvas_left=Ctrl+[
rotate_canvas_right=Ctrl+]
rotateImage180=none
rotateImageCCW90=none
rotateImageCW90=none
rotateimage=none
rotateLayer180=none
rotateLayerCCW90=none
rotateLayerCW90=none
rotatelayer=none
rulers_track_mouse=none
save_groups_as_images=none
save_incremental_backup=F4
save_incremental_version=Ctrl+Alt+S
save_node_as_image=none
select_all=Ctrl+A
select_all_layers=none
Select Behind Blending Mode=Alt+Shift+Q
Select Clear Blending Mode=Alt+Shift+R
Select Color Blending Mode=Alt+Shift+C
Select Color Burn Blending Mode=Alt+Shift+B
Select Color Dodge Blending Mode=Alt+Shift+D
Select Darken Blending Mode=Alt+Shift+K
Select Difference Blending Mode=Alt+Shift+E
Select Dissolve Blending Mode=Alt+Shift+I
Select Exclusion Blending Mode=Alt+Shift+X
Select Hard Light Blending Mode=Alt+Shift+H
Select Hard Mix Blending Mode=Alt+Shift+L
Select Hue Blending Mode=Alt+Shift+U
select_invisible_layers=none
selectionscale=none
Select Lighten Blending Mode=Alt+Shift+G
Select Linear Burn Blending Mode=Alt+Shift+A
Select Linear Dodge Blending Mode=Alt+Shift+W
Select Linear Light Blending Mode=Alt+Shift+J
select_locked_layers=none
Select Luminosity Blending Mode=Alt+Shift+Y
Select Multiply Blending Mode=Alt+Shift+M
Select Normal Blending Mode=Alt+Shift+N
select_opaque=none
selectopaque=none
Select Overlay Blending Mode=Alt+Shift+O
Select Pin Light Blending Mode=Alt+Shift+Z
Select Saturation Blending Mode=Alt+Shift+T
Select Screen Blending Mode=Alt+Shift+S
Select Soft Light Blending Mode=Alt+Shift+F
select_unlocked_layers=none
select_visible_layers=none
Select Vivid Light Blending Mode=Alt+Shift+V
separate=none
set_no_brush_smoothing=none
set_simple_brush_smoothing=none
set_stabilizer_brush_smoothing=none
settings_active_author=none
set_weighted_brush_smoothing=none
shearimage=none
shearlayer=none
shift_brush_color_clockwise=none
shift_brush_color_counter_clockwise=none
show_brush_editor=F5
show_brush_presets=F6
show_color_history=H
show_color_selector=Shift+I
show_common_colors=U
show-global-selection-mask=none
pin_to_timeline=none
show_minimal_shade_selector=Shift+N
show_mypaint_shade_selector=none
show_snap_options_popup=Shift+S
showStatusBar=none
show_tool_options=\\
shrinkselection=none
shrink_to_fit=none
smoothselection=none
soft_hyphen=none
split_alpha_into_mask=none
split_alpha_save_merged=none
split_alpha_write=none
split_sections=none
stroke_shapes=none
switch_application_language=none
tablet_debugger=Ctrl+Shift+T
TextTool=none
toggle_assistant=Ctrl+Shift+L
toggle_display_selection=Ctrl+H
toggle_fg_bg=X
toggle_onion_skin=none
toggle_playback=none
toggle-selection-overlay-mode=none
trim_to_image=none
undo_polygon_selection=Shift+Z
VectorTool=none
view_grid=Ctrl+Shift+'
view_lock_guides=none
view_newwindow=none
view_ruler=none
view_show_canvas_only=Tab
view_show_guides=none
view_snap_bounding_box=none
view_snap_extension=none
view_snap_image_bounds=none
view_snap_image_center=none
view_snap_intersection=none
view_snap_node=none
view_snap_orthogonal=none
view_snap_to_grid=Ctrl+Shift+;
view_snap_to_guides=none
view_toggle_assistant_previews=none
view_toggledockers=none
view_toggledockertitlebars=none
view_toggle_painting_assistants=none
view_zoom_in=Ctrl++
view_zoom_out=Ctrl+-
vmirror_action=none
windows_cascade=none
windows_next=Ctrl+Tab
windows_previous=none
windows_tile=none
workspaces=none
zoom_to_100pct=Ctrl+0
diff --git a/krita/krita.action b/krita/krita.action
index 481d9f6917..7a5333ecbb 100644
--- a/krita/krita.action
+++ b/krita/krita.action
@@ -1,3706 +1,3706 @@
General
Open Resources Folder
Opens a file browser at the location Krita saves resources such as brushes to.
Opens a file browser at the location Krita saves resources such as brushes to.
Open Resources Folder
0
0
false
C&ascade
Cascade
Cascade
10
0
false
&Tile
Tile
Tile
10
0
false
Create Resource Bundle...
Create Resource Bundle
Create Resource Bundle
0
0
false
Show File Toolbar
Show File Toolbar
Show File Toolbar
false
Show color selector
Show color selector
Show color selector
Shift+I
false
Show MyPaint shade selector
Show MyPaint shade selector
Show MyPaint shade selector
Shift+M
false
Show minimal shade selector
Show minimal shade selector
Show minimal shade selector
Shift+N
false
Show color history
Show color history
Show color history
H
false
Show common colors
Show common colors
Show common colors
U
false
Show Tool Options
Show Tool Options
Show Tool Options
\
false
Show Brush Editor
Show Brush Editor
Show Brush Editor
F5
false
Show Brush Presets
Show Brush Presets
Show Brush Presets
F6
false
Toggle Tablet Debugger
Toggle Tablet Debugger
Toggle Tablet Debugger
0
0
Ctrl+Shift+T
false
Show Krita log for bug reports.
Show Krita log for bug reports.
Show Krita log for bug reports.
false
Show system information for bug reports.
Show system information for bug reports.
Show system information for bug reports.
false
Rename Composition...
Rename Composition
Rename Composition
0
0
false
Update Composition
Update Composition
Update Composition
0
0
false
Use multiple of 2 for pixel scale
Use multiple of 2 for pixel scale
Use multiple of 2 for pixel scale
Use multiple of 2 for pixel scale
1
0
true
&Invert Selection
Invert current selection
Invert Selection
10000000000
100
Ctrl+Shift+I
false
Create Snapshot
Create Snapshot
1
0
false
Switch to Selected Snapshot
Switch to selected snapshot
1
0
false
Remove Selected Snapshot
Remove Selected Snapshot
1
0
false
Painting
lightness-increase
Make brush color lighter
Make brush color lighter
Make brush color lighter
0
0
L
false
lightness-decrease
Make brush color darker
Make brush color darker
Make brush color darker
0
0
K
false
Make brush color more saturated
Make brush color more saturated
Make brush color more saturated
false
Make brush color more desaturated
Make brush color more desaturated
Make brush color more desaturated
false
Shift brush color hue clockwise
Shift brush color hue clockwise
Shift brush color hue clockwise
false
Shift brush color hue counter-clockwise
Shift brush color hue counter-clockwise
Shift brush color hue counter-clockwise
false
Make brush color more red
Make brush color more red
Make brush color more red
false
Make brush color more green
Make brush color more green
Make brush color more green
false
Make brush color more blue
Make brush color more blue
Make brush color more blue
false
Make brush color more yellow
Make brush color more yellow
Make brush color more yellow
false
opacity-increase
Increase opacity
Increase opacity
Increase opacity
0
0
O
false
opacity-decrease
Decrease opacity
Decrease opacity
Decrease opacity
0
0
I
false
draw-eraser
Set eraser mode
Set eraser mode
Set eraser mode
10000
0
E
true
view-refresh
Reload Original Preset
Reload Original Preset
Reload Original Preset
10000
false
transparency-unlocked
Preserve Alpha
Preserve Alpha
Preserve Alpha
10000
true
transform_icons_penPressure
Use Pen Pressure
Use Pen Pressure
Use Pen Pressure
10000
true
symmetry-horizontal
Horizontal Mirror Tool
Horizontal Mirror Tool
Horizontal Mirror Tool
0
true
symmetry-vertical
Vertical Mirror Tool
Vertical Mirror Tool
Vertical Mirror Tool
0
true
Hide Mirror X Line
Hide Mirror X Line
Hide Mirror X Line
10000
true
Hide Mirror Y Line
Hide Mirror Y Line
Hide Mirror Y Line
10000
true
Lock
Lock X Line
Lock X Line
10000
true
Lock Y Line
Lock Y Line
Lock Y Line
10000
true
Move to Canvas Center X
Move to Canvas Center X
Move to Canvas Center X
10000
false
Move to Canvas Center Y
Move to Canvas Center Y
Move to Canvas Center Y
10000
false
&Toggle Selection Display Mode
Toggle Selection Display Mode
Toggle Selection Display Mode
0
0
false
Next Favourite Preset
Next Favourite Preset
Next Favourite Preset
,
false
Previous Favourite Preset
Previous Favourite Preset
Previous Favourite Preset
.
false
preset-switcher
Switch to Previous Preset
Switch to Previous Preset
Switch to Previous Preset
/
false
Hide Brushes and Stuff Toolbar
Hide Brushes and Stuff Toolbar
Hide Brushes and Stuff Toolbar
true
Reset Foreground and Background Color
Reset Foreground and Background Color
Reset Foreground and Background Color
D
false
Swap Foreground and Background Color
Swap Foreground and Background Color
Swap Foreground and Background Color
X
false
Selection Mode: Add
Selection Mode: Add
Selection Mode: Add
A
false
Selection Mode: Subtract
Selection Mode: Subtract
Selection Mode: Subtract
S
false
Selection Mode: Intersect
Selection Mode: Intersect
Selection Mode: Intersect
false
Selection Mode: Replace
Selection Mode: Replace
Selection Mode: Replace
R
false
smoothing-weighted
Brush Smoothing: Weighted
Brush Smoothing: Weighted
Brush Smoothing: Weighted
false
smoothing-no
Brush Smoothing: Disabled
Brush Smoothing: Disabled
Brush Smoothing: Disabled
false
smoothing-stabilizer
Brush Smoothing: Stabilizer
Brush Smoothing: Stabilizer
Brush Smoothing: Stabilizer
false
brushsize-decrease
Decrease Brush Size
Decrease Brush Size
Decrease Brush Size
0
0
[
false
smoothing-basic
Brush Smoothing: Basic
Brush Smoothing: Basic
Brush Smoothing: Basic
false
brushsize-increase
Increase Brush Size
Increase Brush Size
Increase Brush Size
0
0
]
false
Toggle Snap To Assistants
Toggle Snap to Assistants
ToggleAssistant
Ctrl+Shift+L
true
Undo Polygon Selection Points
Undo Polygon Selection Points
Undo Polygon Selection Points
Shift+Z
false
Fill with Foreground Color (Opacity)
Fill with Foreground Color (Opacity)
Fill with Foreground Color (Opacity)
10000
1
Ctrl+Shift+Backspace
false
Fill with Background Color (Opacity)
Fill with Background Color (Opacity)
Fill with Background Color (Opacity)
10000
1
Ctrl+Backspace
false
Fill with Pattern (Opacity)
Fill with Pattern (Opacity)
Fill with Pattern (Opacity)
10000
1
false
Convert &to Shape
Convert to Shape
Convert to Shape
10000000000
0
false
&Show Global Selection Mask
Shows global selection as a usual selection mask in <interface>Layers</interface> docker
Show Global Selection Mask
100000
100
true
Filters
color-to-alpha
&Color to Alpha...
Color to Alpha
Color to Alpha
10000
0
false
&Top Edge Detection
Top Edge Detection
Top Edge Detection
10000
0
false
&Index Colors...
Index Colors
Index Colors
10000
0
false
Emboss Horizontal &Only
Emboss Horizontal Only
Emboss Horizontal Only
10000
0
false
D&odge
Dodge
Dodge
10000
0
false
&Sharpen
Sharpen
Sharpen
10000
0
false
B&urn
Burn
Burn
10000
0
false
&Mean Removal
Mean Removal
Mean Removal
10000
0
false
&Gaussian Blur...
Gaussian Blur
Gaussian Blur
10000
0
false
Emboss &in All Directions
Emboss in All Directions
Emboss in All Directions
10000
0
false
&Small Tiles...
Small Tiles
Small Tiles
10000
0
false
&Levels...
Levels
Levels
10000
0
Ctrl+L
false
&Sobel...
Sobel
Sobel
10000
0
false
&Wave...
Wave
Wave
10000
0
false
&Motion Blur...
Motion Blur
Motion Blur
10000
0
false
&Invert
Invert
Invert
10000
0
Ctrl+I
false
&Color Adjustment curves...
Color Adjustment curves
Color Adjustment curves
10000
0
Ctrl+M
false
Pi&xelize...
Pixelize
Pixelize
10000
0
false
Emboss (&Laplacian)
Emboss (Laplacian)
Emboss (Laplacian)
10000
0
false
&Left Edge Detection
Left Edge Detection
Left Edge Detection
10000
0
false
&Blur...
Blur
Blur
10000
0
false
&Raindrops...
Raindrops
Raindrops
10000
0
false
&Bottom Edge Detection
Bottom Edge Detection
Bottom Edge Detection
10000
0
false
&Random Noise...
Random Noise
Random Noise
10000
0
false
&Brightness/Contrast curve...
Brightness/Contrast curve
Brightness/Contrast curve
10000
0
false
Colo&r Balance...
Color Balance
Color Balance
10000
0
Ctrl+B
false
&Phong Bumpmap...
Phong Bumpmap
Phong Bumpmap
10000
0
false
&Desaturate
Desaturate
Desaturate
10000
0
Ctrl+Shift+U
false
Color &Transfer...
Color Transfer
Color Transfer
10000
0
false
Emboss &Vertical Only
Emboss Vertical Only
Emboss Vertical Only
10000
0
false
&Lens Blur...
Lens Blur
Lens Blur
10000
0
false
M&inimize Channel
Minimize Channel
Minimize Channel
10000
0
false
M&aximize Channel
Maximize Channel
Maximize Channel
10000
0
false
&Oilpaint...
Oilpaint
Oilpaint
10000
0
false
&Right Edge Detection
Right Edge Detection
Right Edge Detection
10000
0
false
&Auto Contrast
Auto Contrast
Auto Contrast
10000
0
false
&Round Corners...
Round Corners
Round Corners
10000
0
false
&Unsharp Mask...
Unsharp Mask
Unsharp Mask
10000
0
false
&Emboss with Variable Depth...
Emboss with Variable Depth
Emboss with Variable Depth
10000
0
false
Emboss &Horizontal && Vertical
Emboss Horizontal & Vertical
Emboss Horizontal & Vertical
10000
0
false
Random &Pick...
Random Pick
Random Pick
10000
0
false
&Gaussian Noise Reduction...
Gaussian Noise Reduction
Gaussian Noise Reduction
10000
0
false
&Posterize...
Posterize
Posterize
10000
0
false
&Wavelet Noise Reducer...
Wavelet Noise Reducer
Wavelet Noise Reducer
10000
0
false
&HSV Adjustment...
HSV Adjustment
HSV Adjustment
10000
0
Ctrl+U
false
Tool Shortcuts
Dynamic Brush Tool
Dynamic Brush Tool
Dynamic Brush Tool
false
Crop Tool
Crop the image to an area
Crop the image to an area
C
false
Polygon Tool
Polygon Tool. Shift-mouseclick ends the polygon.
Polygon Tool. Shift-mouseclick ends the polygon.
false
Rectangle Tool
Rectangle Tool
Rectangle Tool
false
Multibrush Tool
Multibrush Tool
Multibrush Tool
Q
false
Colorize Mask Tool
Colorize Mask Tool
Colorize Mask Tool
Smart Patch Tool
Smart Patch Tool
Smart Patch Tool
Pan Tool
Pan Tool
Pan Tool
Select Shapes Tool
Select Shapes Tool
Select Shapes Tool
false
Color Picker
Select a color from the image or current layer
Select a color from the image or current layer
P
false
Outline Selection Tool
Outline Selection Tool
Outline Selection Tool
false
Bezier Curve Selection Tool
Bezier Curve Selection Tool
Bezier Curve Selection Tool
false
Similar Color Selection Tool
Similar Color Selection Tool
Similar Color Selection Tool
false
Fill Tool
Fill a contiguous area of color with a color, or fill a selection.
Fill a contiguous area of color with a color, or fill a selection.
F
false
Line Tool
Line Tool
Line Tool
false
Freehand Path Tool
Freehand Path Tool
Freehand Path Tool
false
Bezier Curve Tool
Bezier Curve Tool. Shift-mouseclick or double-click ends the curve.
Bezier Curve Tool. Shift-mouseclick or double-click ends the curve.
false
Ellipse Tool
Ellipse Tool
Ellipse Tool
false
Freehand Brush Tool
Freehand Brush Tool
Freehand Brush Tool
B
false
Create object
Create object
Create object
false
Elliptical Selection Tool
Elliptical Selection Tool
Elliptical Selection Tool
J
false
Contiguous Selection Tool
Contiguous Selection Tool
Contiguous Selection Tool
false
Pattern editing
Pattern editing
Pattern editing
false
Review
Review
Review
false
Draw a gradient.
Draw a gradient.
Draw a gradient.
G
false
Polygonal Selection Tool
Polygonal Selection Tool
Polygonal Selection Tool
false
Measurement Tool
Measure the distance between two points
Measure the distance between two points
false
Rectangular Selection Tool
Rectangular Selection Tool
Rectangular Selection Tool
Ctrl+R
false
Move Tool
Move a layer
Move a layer
T
false
Vector Image Tool
Vector Image (EMF/WMF/SVM/SVG) tool
Vector Image (EMF/WMF/SVM/SVG) tool
false
Calligraphy
Calligraphy
Calligraphy
false
Path editing
Path editing
Path editing
false
Zoom Tool
Zoom Tool
Zoom Tool
false
Polyline Tool
Polyline Tool. Shift-mouseclick ends the polyline.
Polyline Tool. Shift-mouseclick ends the polyline.
false
Transform Tool
Transform a layer or a selection
Transform a layer or a selection
Ctrl+T
false
Assistant Tool
Assistant Tool
Assistant Tool
false
Gradient Editing Tool
Gradient editing
Gradient editing
false
Reference Images Tool
Reference Images Tool
Reference Images Tool
false
Blending Modes
Select Normal Blending Mode
Select Normal Blending Mode
Select Normal Blending Mode
0
0
Alt+Shift+N
false
Select Dissolve Blending Mode
Select Dissolve Blending Mode
Select Dissolve Blending Mode
0
0
Alt+Shift+I
false
Select Behind Blending Mode
Select Behind Blending Mode
Select Behind Blending Mode
0
0
Alt+Shift+Q
false
Select Clear Blending Mode
Select Clear Blending Mode
Select Clear Blending Mode
0
0
Alt+Shift+R
false
Select Darken Blending Mode
Select Darken Blending Mode
Select Darken Blending Mode
0
0
Alt+Shift+K
false
Select Multiply Blending Mode
Select Multiply Blending Mode
Select Multiply Blending Mode
0
0
Alt+Shift+M
false
Select Color Burn Blending Mode
Select Color Burn Blending Mode
Select Color Burn Blending Mode
0
0
Alt+Shift+B
false
Select Linear Burn Blending Mode
Select Linear Burn Blending Mode
Select Linear Burn Blending Mode
0
0
Alt+Shift+A
false
Select Lighten Blending Mode
Select Lighten Blending Mode
Select Lighten Blending Mode
0
0
Alt+Shift+G
false
Select Screen Blending Mode
Select Screen Blending Mode
Select Screen Blending Mode
0
0
Alt+Shift+S
false
Select Color Dodge Blending Mode
Select Color Dodge Blending Mode
Select Color Dodge Blending Mode
0
0
Alt+Shift+D
false
Select Linear Dodge Blending Mode
Select Linear Dodge Blending Mode
Select Linear Dodge Blending Mode
0
0
Alt+Shift+W
false
Select Overlay Blending Mode
Select Overlay Blending Mode
Select Overlay Blending Mode
0
0
Alt+Shift+O
false
Select Hard Overlay Blending Mode
Select Hard Overlay Blending Mode
Select Hard Overlay Blending Mode
0
0
Alt+Shift+P
false
Select Soft Light Blending Mode
Select Soft Light Blending Mode
Select Soft Light Blending Mode
0
0
Alt+Shift+F
false
Select Hard Light Blending Mode
Select Hard Light Blending Mode
Select Hard Light Blending Mode
0
0
Alt+Shift+H
false
Select Vivid Light Blending Mode
Select Vivid Light Blending Mode
Select Vivid Light Blending Mode
0
0
Alt+Shift+V
false
Select Linear Light Blending Mode
Select Linear Light Blending Mode
Select Linear Light Blending Mode
0
0
Alt+Shift+J
false
Select Pin Light Blending Mode
Select Pin Light Blending Mode
Select Pin Light Blending Mode
0
0
Alt+Shift+Z
false
Select Hard Mix Blending Mode
Select Hard Mix Blending Mode
Select Hard Mix Blending Mode
0
0
Alt+Shift+L
false
Select Difference Blending Mode
Select Difference Blending Mode
Select Difference Blending Mode
0
0
Alt+Shift+E
false
Select Exclusion Blending Mode
Select Exclusion Blending Mode
Select Exclusion Blending Mode
0
0
Alt+Shift+X
false
Select Hue Blending Mode
Select Hue Blending Mode
Select Hue Blending Mode
0
0
Alt+Shift+U
false
Select Saturation Blending Mode
Select Saturation Blending Mode
Select Saturation Blending Mode
0
0
Alt+Shift+T
false
Select Color Blending Mode
Select Color Blending Mode
Select Color Blending Mode
0
0
Alt+Shift+C
false
Select Luminosity Blending Mode
Select Luminosity Blending Mode
Select Luminosity Blending Mode
0
0
Alt+Shift+Y
false
Animation
Previous frame
Move to previous frame
Move to previous frame
1
0
false
Next frame
Move to next frame
Move to next frame
1
0
false
Play / pause animation
Play / pause animation
Play / pause animation
1
0
false
addblankframe
Create Blank Frame
Add blank frame
Add blank frame
100000
0
false
addduplicateframe
Create Duplicate Frame
Add duplicate frame
Add duplicate frame
100000
0
false
Toggle onion skin
Toggle onion skin
Toggle onion skin
100000
0
false
Previous Keyframe
false
Next Keyframe
false
First Frame
false
Last Frame
false
Auto Frame Mode
true
true
Pin to Timeline
If checked, layer becomes pinned to the timeline, making it visible even when inactive.
true
Insert Keyframe Left
Insert keyframes to the left of selection, moving the tail of animation to the right.
100000
0
false
Insert Keyframe Right
Insert keyframes to the right of selection, moving the tail of animation to the right.
100000
0
false
Insert Multiple Keyframes
Insert several keyframes based on user parameters.
100000
0
false
Remove Frame and Pull
Remove keyframes moving the tail of animation to the left
100000
0
false
deletekeyframe
Remove Keyframe
Remove keyframes without moving anything around
100000
0
false
Insert Column Left
Insert column to the left of selection, moving the tail of animation to the right
100000
0
false
Insert Column Right
Insert column to the right of selection, moving the tail of animation to the right
100000
0
false
Insert Multiple Columns
Insert several columns based on user parameters.
100000
0
false
Remove Column and Pull
Remove columns moving the tail of animation to the left
100000
0
false
Remove Column
Remove columns without moving anything around
100000
0
false
Insert Hold Frame
Insert a hold frame after every keyframe
100000
0
false
Insert Multiple Hold Frames
Insert N hold frames after every keyframe
100000
0
false
Remove Hold Frame
Remove a hold frame after every keyframe
100000
0
false
Remove Multiple Hold Frames
Remove N hold frames after every keyframe
100000
0
false
Insert Hold Column
Insert a hold column into the frame at the current position
100000
0
false
Insert Multiple Hold Columns
Insert N hold columns into the frame at the current position
100000
0
false
Remove Hold Column
Remove a hold column from the frame at the current position
100000
0
false
Remove Multiple Hold Columns
Remove N hold columns from the frame at the current position
100000
0
false
Add opacity keyframe
Adds keyframe to control layer opacity
100000
0
false
Remove opacity keyframe
Removes keyframe to control layer opacity
100000
0
false
Mirror Frames
Mirror frames' position
100000
0
false
Mirror Columns
Mirror columns' position
100000
0
false
Copy to Clipboard
Copy frames to clipboard
100000
0
false
Cut to Clipboard
Cut frames to clipboard
100000
0
false
Paste from Clipboard
Paste frames from clipboard
100000
0
false
Copy Columns to Clipboard
Copy columns to clipboard
100000
0
false
Cut Columns to Clipboard
Cut columns to clipboard
100000
0
false
Paste Columns from Clipboard
Paste columns from clipboard
100000
0
false
Set Start Time
100000
0
false
Set End Time
100000
0
false
Update Playback Range
100000
0
false
Layers
Activate next layer
Activate next layer
Activate next layer
1000
0
PgUp
false
Activate next sibling layer, skipping over groups.
Activate next sibling layer
Activate next sibling layer
1000
0
false
Activate previous layer
Activate previous layer
Activate previous layer
1000
0
PgDown
false
Activate previous sibling layer, skipping over groups.
Activate previous sibling layer
Activate previous sibling layer
1000
0
false
Activate previously selected layer
Activate previously selected layer
Activate previously selected layer
1000
0
;
false
groupLayer
&Group Layer
Group Layer
Group Layer
1000
0
false
cloneLayer
&Clone Layer
Clone Layer
Clone Layer
1000
0
false
vectorLayer
&Vector Layer
Vector Layer
Vector Layer
1000
0
false
filterLayer
&Filter Layer...
Filter Layer
Filter Layer
1000
0
false
fillLayer
&Fill Layer...
Fill Layer
Fill Layer
1000
0
false
fileLayer
&File Layer...
File Layer
File Layer
1000
0
false
transparencyMask
&Transparency Mask
Transparency Mask
Transparency Mask
100000
0
false
filterMask
&Filter Mask...
Filter Mask
Filter Mask
100000
0
false
filterMask
&Colorize Mask
Colorize Mask
Colorize Mask
100000
0
false
transformMask
&Transform Mask...
Transform Mask
Transform Mask
100000
0
false
selectionMask
&Local Selection
Local Selection
Local Selection
100000
0
false
-
+
view-filter
- &Isolate Layer
+ &Isolate Active Layer
- Isolate Layer
- Isolate Layer
+ Isolate Active Layer
+ Isolate Active Layer
1000
0
true
layer-locked
&Toggle layer lock
Toggle layer lock
Toggle layer lock
1000
0
false
visible
Toggle layer &visibility
Toggle layer visibility
Toggle layer visibility
1000
0
false
transparency-locked
Toggle layer &alpha
Toggle layer alpha
Toggle layer alpha
1000
0
false
transparency-enabled
Toggle layer alpha &inheritance
Toggle layer alpha inheritance
Toggle layer alpha inheritance
1000
0
false
paintLayer
&Paint Layer
Paint Layer
Paint Layer
1000
0
Insert
false
&New Layer From Visible
New layer from visible
New layer from visible
1000
0
false
duplicatelayer
&Duplicate Layer or Mask
Duplicate Layer or Mask
Duplicate Layer or Mask
1000
0
Ctrl+J
false
&Cut Selection to New Layer
Cut Selection to New Layer
Cut Selection to New Layer
100000000
1
Ctrl+Shift+J
false
Copy &Selection to New Layer
Copy Selection to New Layer
Copy Selection to New Layer
100000000
0
Ctrl+Alt+J
false
Copy Layer
Copy layer to clipboard
Copy layer to clipboard
1000
0
false
Cut Layer
Cut layer to clipboard
Cut layer to clipboard
1000
0
false
Paste Layer
Paste layer from clipboard
Paste layer from clipboard
1000
0
false
Quick Group
Create a group layer containing selected layers
Quick Group
1000
0
Ctrl+G
false
Quick Ungroup
Remove grouping of the layers or remove one layer out of the group
Quick Ungroup
100000
0
Ctrl+Alt+G
false
Quick Clipping Group
Group selected layers and add a layer with clipped alpha channel
Quick Clipping Group
100000
0
Ctrl+Shift+G
false
All Layers
Select all layers
Select all layers
10000
0
false
Visible Layers
Select all visible layers
Select all visible layers
10000
0
false
Locked Layers
Select all locked layers
Select all locked layers
10000
0
false
Invisible Layers
Select all invisible layers
Select all invisible layers
10000
0
false
Unlocked Layers
Select all unlocked layers
Select all unlocked layers
10000
0
false
document-save
&Save Layer/Mask...
Save Layer/Mask
Save Layer/Mask
1000
0
false
document-save
Save Vector Layer as SVG...
Save Vector Layer as SVG
Save Vector Layer as SVG
1000
0
false
document-save
Save &Group Layers...
Save Group Layers
Save Group Layers
100000
0
false
Convert group to &animated layer
Convert child layers into animation frames
Convert child layers into animation frames
100000
0
false
Convert to &animated layer
Convert layer into animation frames
Convert layer into animation frames
100000
0
false
fileLayer
to &File Layer
Saves out the layers into a new image and then references that image.
Convert to File Layer
100000
0
false
I&mport Layer...
Import Layer
Import Layer
100000
0
false
paintLayer
&as Paint Layer...
as Paint Layer
as Paint Layer
1000
0
false
transparencyMask
as &Transparency Mask...
as Transparency Mask
as Transparency Mask
1000
0
false
filterMask
as &Filter Mask...
as Filter Mask
as Filter Mask
1000
0
false
selectionMask
as &Selection Mask...
as Selection Mask
as Selection Mask
1000
0
false
paintLayer
to &Paint Layer
to Paint Layer
to Paint Layer
1000
0
false
transparencyMask
to &Transparency Mask
to Transparency Mask
to Transparency Mask
1000
0
false
filterMask
to &Filter Mask...
to Filter Mask
to Filter Mask
1000
0
false
selectionMask
to &Selection Mask
to Selection Mask
to Selection Mask
1000
0
false
transparencyMask
&Alpha into Mask
Alpha into Mask
Alpha into Mask
100000
10
false
transparency-enabled
&Write as Alpha
Write as Alpha
Write as Alpha
1000000
1
false
document-save
&Save Merged...
Save Merged
Save Merged
1000000
0
false
split-layer
Split Layer...
Split Layer
Split Layer
1000
0
false
Wavelet Decompose ...
Wavelet Decompose
Wavelet Decompose
1000
1
false
symmetry-horizontal
Mirror Layer Hori&zontally
Mirror Layer Horizontally
Mirror Layer Horizontally
1000
1
false
symmetry-vertical
Mirror Layer &Vertically
Mirror Layer Vertically
Mirror Layer Vertically
1000
1
false
&Rotate Layer...
Rotate Layer
Rotate Layer
1000
1
false
object-rotate-right
Rotate &Layer 90° to the Right
Rotate Layer 90° to the Right
Rotate Layer 90° to the Right
1000
1
false
object-rotate-left
Rotate Layer &90° to the Left
Rotate Layer 90° to the Left
Rotate Layer 90° to the Left
1000
1
false
Rotate Layer &180°
Rotate Layer 180°
Rotate Layer 180°
1000
1
false
Scale &Layer to new Size...
Scale Layer to new Size
Scale Layer to new Size
100000
1
false
&Shear Layer...
Shear Layer
Shear Layer
1000
1
false
symmetry-horizontal
Mirror All Layers Hori&zontally
Mirror All Layers Horizontally
Mirror All Layers Horizontally
1000
1
false
symmetry-vertical
Mirror All Layers &Vertically
Mirror All Layers Vertically
Mirror All Layers Vertically
1000
1
false
&Rotate All Layers...
Rotate All Layers
Rotate All Layers
1000
1
false
object-rotate-right
Rotate All &Layers 90° to the Right
Rotate All Layers 90° to the Right
Rotate All Layers 90° to the Right
1000
1
false
object-rotate-left
Rotate All Layers &90° to the Left
Rotate All Layers 90° to the Left
Rotate All Layers 90° to the Left
1000
1
false
Rotate All Layers &180°
Rotate All Layers 180°
Rotate All Layers 180°
1000
1
false
Scale All &Layers to new Size...
Scale All Layers to new Size
Scale All Layers to new Size
100000
1
false
&Shear All Layers...
Shear All Layers
Shear All Layers
1000
1
false
&Offset Layer...
Offset Layer
Offset Layer
100000
1
false
Clones &Array...
Clones Array
Clones Array
100000
0
false
&Edit metadata...
Edit metadata
Edit metadata
100000
1
false
&Histogram...
Histogram
Histogram
100000
0
false
&Convert Layer Color Space...
Convert Layer Color Space
Convert Layer Color Space
100000
1
false
merge-layer-below
&Merge with Layer Below
Merge with Layer Below
Merge with Layer Below
100000
0
Ctrl+E
false
&Flatten Layer
Flatten Layer
Flatten Layer
100000
0
false
Ras&terize Layer
Rasterize Layer
Rasterize Layer
10000000
1
false
Flatten ima&ge
Flatten image
Flatten image
100000
0
Ctrl+Shift+E
false
La&yer Style...
Layer Style
Layer Style
100000
1
false
Move into previous group
Move into previous group
Move into previous group
0
0
false
Move into next group
Move into next group
Move into next group
0
0
false
Rename current layer
Rename current layer
Rename current layer
100000
0
F2
false
deletelayer
&Remove Layer
Remove Layer
Remove Layer
1000
1
Shift+Delete
false
arrowupblr
Move Layer or Mask Up
Move Layer or Mask Up
Ctrl+PgUp
false
arrowdown
Move Layer or Mask Down
Move Layer or Mask Down
Ctrl+PgDown
false
properties
&Properties...
Properties
Properties
1000
1
F3
false
Set Copy F&rom...
Set the source for the selected clone layer(s).
Set Copy From
1000
1
false
diff --git a/libs/image/kis_image.cc b/libs/image/kis_image.cc
index f4156e9a11..25dd4aa198 100644
--- a/libs/image/kis_image.cc
+++ b/libs/image/kis_image.cc
@@ -1,2321 +1,2328 @@
/*
* 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 "KoColorSpaceRegistry.h"
#include "KoColor.h"
#include "KoColorProfile.h"
#include
#include "KisProofingConfiguration.h"
#include "kis_adjustment_layer.h"
#include "kis_annotation.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_do_something_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"
#include "KisBusyWaitBroker.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.setSuspendResumeUpdatesStrokeStrategyFactory(
[=]() {
KisSuspendProjectionUpdatesStrokeStrategy::SharedDataSP data = KisSuspendProjectionUpdatesStrokeStrategy::createSharedData();
KisSuspendResumePair suspend(new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), true, data),
KisSuspendProjectionUpdatesStrokeStrategy::createSuspendJobsData(KisImageWSP(q)));
KisSuspendResumePair resume(new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), false, data),
KisSuspendProjectionUpdatesStrokeStrategy::createResumeJobsData(KisImageWSP(q)));
return std::make_pair(suspend, resume);
});
}
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;
// filters are applied in a reversed way, from rbegin() to rend()
QVector projectionUpdatesFilters;
QStack disabledUpdatesCookies;
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);
void convertImageColorSpaceImpl(const KoColorSpace *dstColorSpace,
bool convertLayers,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags);
struct SetImageProjectionColorSpace;
};
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()
{
/**
* 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 || rhs.m_d->overlaySelectionMask) {
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;
}
if (rhs.m_d->overlaySelectionMask &&
KisNodeSP(rhs.m_d->overlaySelectionMask) == refNode) {
m_d->targetOverlaySelectionMask = dynamic_cast(node.data());
m_d->overlaySelectionMask = m_d->targetOverlaySelectionMask;
m_d->rootLayer->notifyChildMaskChanged();
}
});
}
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->projectionUpdatesFilters.isEmpty());
KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableUIUpdateSignals);
KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableDirtyRequests);
m_d->blockLevelOfDetail = rhs.m_d->blockLevelOfDetail;
#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(QLatin1String("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, i18n("Selection Mask"));
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();
KisBusyWaitBroker::instance()->notifyWaitOnImageStarted(this);
m_d->scheduler.barrierLock();
KisBusyWaitBroker::instance()->notifyWaitOnImageEnded(this);
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();
KisBusyWaitBroker::instance()->notifyWaitOnImageStarted(this);
m_d->scheduler.lock();
KisBusyWaitBroker::instance()->notifyWaitOnImageEnded(this);
}
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::purgeUnusedData(bool isCancellable)
{
+ /**
+ * WARNING: don't use this function unless you know what you are doing!
+ *
+ * It breaks undo on layers! Therefore, after calling it, KisImage is not
+ * undo-capable anymore!
+ */
+
struct PurgeUnusedDataStroke : public KisRunnableBasedStrokeStrategy {
PurgeUnusedDataStroke(KisImageSP image, bool isCancellable)
: KisRunnableBasedStrokeStrategy(QLatin1String("purge-unused-data"),
kundo2_noi18n("purge-unused-data")),
m_image(image)
{
this->enableJob(JOB_INIT, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE);
this->enableJob(JOB_DOSTROKE, true);
setClearsRedoOnStart(false);
setRequestsOtherStrokesToEnd(!isCancellable);
setCanForgetAboutMe(isCancellable);
}
void initStrokeCallback() {
KisPaintDeviceList deviceList;
QVector jobsData;
KisLayerUtils::recursiveApplyNodes(m_image->root(),
[&deviceList](KisNodeSP node) {
deviceList << node->getLodCapableDevices();
});
Q_FOREACH (KisPaintDeviceSP device, deviceList) {
if (!device) continue;
KritaUtils::addJobConcurrent(jobsData,
[device] () {
const_cast(device.data())->purgeDefaultPixels();
});
}
addMutatedJobs(jobsData);
}
private:
KisImageSP m_image;
};
KisStrokeId id = startStroke(new PurgeUnusedDataStroke(this, isCancellable));
endStroke(id);
}
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 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();
}
struct KisImage::KisImagePrivate::SetImageProjectionColorSpace : public KisCommandUtils::FlipFlopCommand
{
SetImageProjectionColorSpace(const KoColorSpace *cs, KisImageWSP image,
State initialState, KUndo2Command *parent = 0)
: KisCommandUtils::FlipFlopCommand(initialState, parent),
m_cs(cs),
m_image(image)
{
}
void partA() override {
KisImageSP image = m_image;
if (image) {
image->setProjectionColorSpace(m_cs);
}
}
private:
const KoColorSpace *m_cs;
KisImageWSP m_image;
};
void KisImage::KisImagePrivate::convertImageColorSpaceImpl(const KoColorSpace *dstColorSpace,
bool convertLayers,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags)
{
const KoColorSpace *srcColorSpace = this->colorSpace;
if (!dstColorSpace || *srcColorSpace == *dstColorSpace) return;
const KUndo2MagicString actionName =
convertLayers ?
kundo2_i18n("Convert Image Color Space") :
kundo2_i18n("Convert Projection Color Space");
KisImageSignalVector emitSignals;
emitSignals << ColorSpaceChangedSignal;
emitSignals << ModifiedSignal;
KisProcessingApplicator applicator(q, this->rootLayer,
KisProcessingApplicator::RECURSIVE |
KisProcessingApplicator::NO_UI_UPDATES,
emitSignals, actionName);
applicator.applyCommand(
new KisImagePrivate::SetImageProjectionColorSpace(dstColorSpace,
KisImageWSP(q),
KisCommandUtils::FlipFlopCommand::INITIALIZING),
KisStrokeJobData::BARRIER);
if (convertLayers) {
applicator.applyVisitor(
new KisConvertColorSpaceProcessingVisitor(
srcColorSpace, dstColorSpace,
renderingIntent, conversionFlags),
KisStrokeJobData::CONCURRENT);
} else {
applicator.applyCommand(
new KisDoSomethingCommand<
KisDoSomethingCommandOps::ResetOp, KisGroupLayerSP>
(this->rootLayer, false));
applicator.applyCommand(
new KisDoSomethingCommand<
KisDoSomethingCommandOps::ResetOp, KisGroupLayerSP>
(this->rootLayer, true));
}
applicator.applyCommand(
new KisImagePrivate::SetImageProjectionColorSpace(srcColorSpace,
KisImageWSP(q),
KisCommandUtils::FlipFlopCommand::FINALIZING),
KisStrokeJobData::BARRIER);
applicator.end();
}
void KisImage::convertImageColorSpace(const KoColorSpace *dstColorSpace,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags)
{
m_d->convertImageColorSpaceImpl(dstColorSpace, true, renderingIntent, conversionFlags);
}
void KisImage::convertImageProjectionColorSpace(const KoColorSpace *dstColorSpace)
{
m_d->convertImageColorSpaceImpl(dstColorSpace, false,
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
}
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, bool blockAllUpdates)
{
if (!profile) return false;
const KoColorSpace *srcColorSpace = m_d->colorSpace;
bool imageProfileIsSame = *srcColorSpace->profile() == *profile;
imageProfileIsSame &=
!KisLayerUtils::recursiveFindNode(m_d->rootLayer,
[profile] (KisNodeSP node) {
return *node->colorSpace()->profile() != *profile;
});
if (imageProfileIsSame) {
dbgImage << "Trying to set the same image profile again" << ppVar(srcColorSpace->profile()->name()) << ppVar(profile->name());
return true;
}
KUndo2MagicString actionName = kundo2_i18n("Assign Profile");
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 |
(!blockAllUpdates ?
KisProcessingApplicator::NO_UI_UPDATES :
KisProcessingApplicator::NO_IMAGE_UPDATES),
emitSignals, actionName);
applicator.applyCommand(
new KisImagePrivate::SetImageProjectionColorSpace(dstColorSpace,
KisImageWSP(this),
KisCommandUtils::FlipFlopCommand::INITIALIZING),
KisStrokeJobData::BARRIER);
applicator.applyVisitor(
new KisAssignProfileProcessingVisitor(
srcColorSpace, dstColorSpace),
KisStrokeJobData::CONCURRENT);
applicator.applyCommand(
new KisImagePrivate::SetImageProjectionColorSpace(srcColorSpace,
KisImageWSP(this),
KisCommandUtils::FlipFlopCommand::FINALIZING),
KisStrokeJobData::BARRIER);
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();
KisBusyWaitBroker::instance()->notifyWaitOnImageStarted(this);
m_d->scheduler.waitForDone();
KisBusyWaitBroker::instance()->notifyWaitOnImageEnded(this);
}
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(QLatin1String("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(QLatin1String("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)
{
requestProjectionUpdateNoFilthy(pseudoFilthy, rc, cropRect, true);
}
void KisImage::requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect, const bool resetAnimationCache)
{
KIS_ASSERT_RECOVER_RETURN(pseudoFilthy);
if (resetAnimationCache) {
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();
}
KisProjectionUpdatesFilterCookie KisImage::addProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter)
{
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(filter, KisProjectionUpdatesFilterCookie());
m_d->projectionUpdatesFilters.append(filter);
return KisProjectionUpdatesFilterCookie(filter.data());
}
KisProjectionUpdatesFilterSP KisImage::removeProjectionUpdatesFilter(KisProjectionUpdatesFilterCookie cookie)
{
KIS_SAFE_ASSERT_RECOVER_NOOP(cookie);
KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->projectionUpdatesFilters.last() == cookie);
auto it = std::find(m_d->projectionUpdatesFilters.begin(), m_d->projectionUpdatesFilters.end(), cookie);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(it != m_d->projectionUpdatesFilters.end(), KisProjectionUpdatesFilterSP());
KisProjectionUpdatesFilterSP filter = *it;
m_d->projectionUpdatesFilters.erase(it);
return filter;
}
KisProjectionUpdatesFilterCookie KisImage::currentProjectionUpdatesFilter() const
{
return !m_d->projectionUpdatesFilters.isEmpty() ?
m_d->projectionUpdatesFilters.last().data() :
KisProjectionUpdatesFilterCookie();
}
void KisImage::disableDirtyRequests()
{
m_d->disabledUpdatesCookies.push(
addProjectionUpdatesFilter(toQShared(new KisDropAllProjectionUpdatesFilter())));
}
void KisImage::enableDirtyRequests()
{
KIS_SAFE_ASSERT_RECOVER_RETURN(!m_d->disabledUpdatesCookies.isEmpty());
removeProjectionUpdatesFilter(m_d->disabledUpdatesCookies.pop());
}
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)
{
/**
* We iterate through the filters in a reversed way. It makes the most nested filters
* to execute first.
*/
for (auto it = m_d->projectionUpdatesFilters.rbegin();
it != m_d->projectionUpdatesFilters.rend();
++it) {
KIS_SAFE_ASSERT_RECOVER(*it) { continue; }
if ((*it)->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_keyframe_channel.cpp b/libs/image/kis_keyframe_channel.cpp
index afc32c374e..5d81b4bd1b 100644
--- a/libs/image/kis_keyframe_channel.cpp
+++ b/libs/image/kis_keyframe_channel.cpp
@@ -1,676 +1,687 @@
/*
* Copyright (c) 2015 Jouni Pentikäinen
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_keyframe_channel.h"
#include "KoID.h"
#include "kis_global.h"
#include "kis_node.h"
#include "kis_time_range.h"
#include "kundo2command.h"
#include "kis_keyframe_commands.h"
#include
const KoID KisKeyframeChannel::Content = KoID("content", ki18n("Content"));
const KoID KisKeyframeChannel::Opacity = KoID("opacity", ki18n("Opacity"));
const KoID KisKeyframeChannel::TransformArguments = KoID("transform_arguments", ki18n("Transform"));
const KoID KisKeyframeChannel::TransformPositionX = KoID("transform_pos_x", ki18n("Position (X)"));
const KoID KisKeyframeChannel::TransformPositionY = KoID("transform_pos_y", ki18n("Position (Y)"));
const KoID KisKeyframeChannel::TransformScaleX = KoID("transform_scale_x", ki18n("Scale (X)"));
const KoID KisKeyframeChannel::TransformScaleY = KoID("transform_scale_y", ki18n("Scale (Y)"));
const KoID KisKeyframeChannel::TransformShearX = KoID("transform_shear_x", ki18n("Shear (X)"));
const KoID KisKeyframeChannel::TransformShearY = KoID("transform_shear_y", ki18n("Shear (Y)"));
const KoID KisKeyframeChannel::TransformRotationX = KoID("transform_rotation_x", ki18n("Rotation (X)"));
const KoID KisKeyframeChannel::TransformRotationY = KoID("transform_rotation_y", ki18n("Rotation (Y)"));
const KoID KisKeyframeChannel::TransformRotationZ = KoID("transform_rotation_z", ki18n("Rotation (Z)"));
struct KisKeyframeChannel::Private
{
Private() {}
Private(const Private &rhs) {
id = rhs.id;
haveBrokenFrameTimeBug = rhs.haveBrokenFrameTimeBug;
}
KeyframesMap keys;
KisNodeWSP node;
KoID id;
KisDefaultBoundsBaseSP defaultBounds;
bool haveBrokenFrameTimeBug = false;
};
KisKeyframeChannel::KisKeyframeChannel(const KoID &id, KisNodeWSP parent)
: m_d(new Private)
{
m_d->id = id;
m_d->node = parent;
m_d->defaultBounds = KisDefaultBoundsNodeWrapperSP( new KisDefaultBoundsNodeWrapper( parent ));
}
+KisKeyframeChannel::KisKeyframeChannel(const KoID &id, KisDefaultBoundsBaseSP bounds)
+ : m_d(new Private)
+{
+ m_d->id = id;
+ m_d->node = nullptr;
+ m_d->defaultBounds = bounds;
+}
+
KisKeyframeChannel::KisKeyframeChannel(const KisKeyframeChannel &rhs, KisNodeWSP newParent)
: m_d(new Private(*rhs.m_d))
{
KIS_ASSERT_RECOVER_NOOP(&rhs != this);
m_d->node = newParent;
m_d->defaultBounds = KisDefaultBoundsNodeWrapperSP( new KisDefaultBoundsNodeWrapper( newParent ));
Q_FOREACH(KisKeyframeSP keyframe, rhs.m_d->keys) {
m_d->keys.insert(keyframe->time(), keyframe->cloneFor(this));
}
}
KisKeyframeChannel::~KisKeyframeChannel()
{}
QString KisKeyframeChannel::id() const
{
return m_d->id.id();
}
QString KisKeyframeChannel::name() const
{
return m_d->id.name();
}
void KisKeyframeChannel::setNode(KisNodeWSP node)
{
m_d->node = node;
m_d->defaultBounds = KisDefaultBoundsNodeWrapperSP( new KisDefaultBoundsNodeWrapper( node ));
}
KisNodeWSP KisKeyframeChannel::node() const
{
return m_d->node;
}
int KisKeyframeChannel::keyframeCount() const
{
return m_d->keys.count();
}
KisKeyframeChannel::KeyframesMap& KisKeyframeChannel::keys()
{
return m_d->keys;
}
const KisKeyframeChannel::KeyframesMap& KisKeyframeChannel::constKeys() const
{
return m_d->keys;
}
#define LAZY_INITIALIZE_PARENT_COMMAND(cmd) \
QScopedPointer __tempCommand; \
if (!parentCommand) { \
__tempCommand.reset(new KUndo2Command()); \
cmd = __tempCommand.data(); \
}
KisKeyframeSP KisKeyframeChannel::addKeyframe(int time, KUndo2Command *parentCommand)
{
LAZY_INITIALIZE_PARENT_COMMAND(parentCommand);
return insertKeyframe(time, KisKeyframeSP(), parentCommand);
}
KisKeyframeSP KisKeyframeChannel::copyKeyframe(const KisKeyframeSP keyframe, int newTime, KUndo2Command *parentCommand)
{
LAZY_INITIALIZE_PARENT_COMMAND(parentCommand);
return insertKeyframe(newTime, keyframe, parentCommand);
}
KisKeyframeSP KisKeyframeChannel::insertKeyframe(int time, const KisKeyframeSP copySrc, KUndo2Command *parentCommand)
{
KisKeyframeSP keyframe = keyframeAt(time);
if (keyframe) {
deleteKeyframeImpl(keyframe, parentCommand, false);
}
Q_ASSERT(parentCommand);
keyframe = createKeyframe(time, copySrc, parentCommand);
KUndo2Command *cmd = new KisReplaceKeyframeCommand(this, keyframe->time(), keyframe, parentCommand);
cmd->redo();
return keyframe;
}
bool KisKeyframeChannel::deleteKeyframe(KisKeyframeSP keyframe, KUndo2Command *parentCommand)
{
return deleteKeyframeImpl(keyframe, parentCommand, true);
}
bool KisKeyframeChannel::moveKeyframe(KisKeyframeSP keyframe, int newTime, KUndo2Command *parentCommand)
{
LAZY_INITIALIZE_PARENT_COMMAND(parentCommand);
if (newTime == keyframe->time()) return false;
KisKeyframeSP other = keyframeAt(newTime);
if (other) {
deleteKeyframeImpl(other, parentCommand, false);
}
const int srcTime = keyframe->time();
KUndo2Command *cmd = new KisMoveFrameCommand(this, keyframe, srcTime, newTime, parentCommand);
cmd->redo();
if (srcTime == 0) {
addKeyframe(srcTime, parentCommand);
}
return true;
}
bool KisKeyframeChannel::swapFrames(int lhsTime, int rhsTime, KUndo2Command *parentCommand)
{
LAZY_INITIALIZE_PARENT_COMMAND(parentCommand);
if (lhsTime == rhsTime) return false;
KisKeyframeSP lhsFrame = keyframeAt(lhsTime);
KisKeyframeSP rhsFrame = keyframeAt(rhsTime);
if (!lhsFrame && !rhsFrame) return false;
if (lhsFrame && !rhsFrame) {
moveKeyframe(lhsFrame, rhsTime, parentCommand);
} else if (!lhsFrame && rhsFrame) {
moveKeyframe(rhsFrame, lhsTime, parentCommand);
} else {
KUndo2Command *cmd = new KisSwapFramesCommand(this, lhsFrame, rhsFrame, parentCommand);
cmd->redo();
}
return true;
}
bool KisKeyframeChannel::deleteKeyframeImpl(KisKeyframeSP keyframe, KUndo2Command *parentCommand, bool recreate)
{
LAZY_INITIALIZE_PARENT_COMMAND(parentCommand);
Q_ASSERT(parentCommand);
KUndo2Command *cmd = new KisReplaceKeyframeCommand(this, keyframe->time(), KisKeyframeSP(), parentCommand);
cmd->redo();
destroyKeyframe(keyframe, parentCommand);
if (recreate && keyframe->time() == 0) {
addKeyframe(0, parentCommand);
}
return true;
}
void KisKeyframeChannel::moveKeyframeImpl(KisKeyframeSP keyframe, int newTime)
{
KIS_ASSERT_RECOVER_RETURN(keyframe);
KIS_ASSERT_RECOVER_RETURN(!keyframeAt(newTime));
KisTimeRange rangeSrc = affectedFrames(keyframe->time());
QRect rectSrc = affectedRect(keyframe);
emit sigKeyframeAboutToBeMoved(keyframe, newTime);
m_d->keys.remove(keyframe->time());
int oldTime = keyframe->time();
keyframe->setTime(newTime);
m_d->keys.insert(newTime, keyframe);
emit sigKeyframeMoved(keyframe, oldTime);
KisTimeRange rangeDst = affectedFrames(keyframe->time());
QRect rectDst = affectedRect(keyframe);
requestUpdate(rangeSrc, rectSrc);
requestUpdate(rangeDst, rectDst);
}
void KisKeyframeChannel::swapKeyframesImpl(KisKeyframeSP lhsKeyframe, KisKeyframeSP rhsKeyframe)
{
KIS_ASSERT_RECOVER_RETURN(lhsKeyframe);
KIS_ASSERT_RECOVER_RETURN(rhsKeyframe);
KisTimeRange rangeLhs = affectedFrames(lhsKeyframe->time());
KisTimeRange rangeRhs = affectedFrames(rhsKeyframe->time());
const QRect rectLhsSrc = affectedRect(lhsKeyframe);
const QRect rectRhsSrc = affectedRect(rhsKeyframe);
const int lhsTime = lhsKeyframe->time();
const int rhsTime = rhsKeyframe->time();
emit sigKeyframeAboutToBeMoved(lhsKeyframe, rhsTime);
emit sigKeyframeAboutToBeMoved(rhsKeyframe, lhsTime);
m_d->keys.remove(lhsTime);
m_d->keys.remove(rhsTime);
rhsKeyframe->setTime(lhsTime);
lhsKeyframe->setTime(rhsTime);
m_d->keys.insert(lhsTime, rhsKeyframe);
m_d->keys.insert(rhsTime, lhsKeyframe);
emit sigKeyframeMoved(lhsKeyframe, lhsTime);
emit sigKeyframeMoved(rhsKeyframe, rhsTime);
const QRect rectLhsDst = affectedRect(lhsKeyframe);
const QRect rectRhsDst = affectedRect(rhsKeyframe);
requestUpdate(rangeLhs, rectLhsSrc | rectRhsDst);
requestUpdate(rangeRhs, rectRhsSrc | rectLhsDst);
}
KisKeyframeSP KisKeyframeChannel::replaceKeyframeAt(int time, KisKeyframeSP newKeyframe)
{
Q_ASSERT(newKeyframe.isNull() || time == newKeyframe->time());
KisKeyframeSP existingKeyframe = keyframeAt(time);
if (!existingKeyframe.isNull()) {
removeKeyframeLogical(existingKeyframe);
}
if (!newKeyframe.isNull()) {
insertKeyframeLogical(newKeyframe);
}
return existingKeyframe;
}
void KisKeyframeChannel::insertKeyframeLogical(KisKeyframeSP keyframe)
{
const int time = keyframe->time();
emit sigKeyframeAboutToBeAdded(keyframe);
m_d->keys.insert(time, keyframe);
emit sigKeyframeAdded(keyframe);
QRect rect = affectedRect(keyframe);
KisTimeRange range = affectedFrames(time);
requestUpdate(range, rect);
}
void KisKeyframeChannel::removeKeyframeLogical(KisKeyframeSP keyframe)
{
QRect rect = affectedRect(keyframe);
KisTimeRange range = affectedFrames(keyframe->time());
emit sigKeyframeAboutToBeRemoved(keyframe);
m_d->keys.remove(keyframe->time());
emit sigKeyframeRemoved(keyframe);
requestUpdate(range, rect);
}
KisKeyframeSP KisKeyframeChannel::keyframeAt(int time) const
{
KeyframesMap::const_iterator i = m_d->keys.constFind(time);
if (i != m_d->keys.constEnd()) {
return i.value();
}
return KisKeyframeSP();
}
KisKeyframeSP KisKeyframeChannel::activeKeyframeAt(int time) const
{
KeyframesMap::const_iterator i = activeKeyIterator(time);
if (i != m_d->keys.constEnd()) {
return i.value();
}
return KisKeyframeSP();
}
KisKeyframeSP KisKeyframeChannel::currentlyActiveKeyframe() const
{
return activeKeyframeAt(currentTime());
}
KisKeyframeSP KisKeyframeChannel::firstKeyframe() const
{
if (m_d->keys.isEmpty()) return KisKeyframeSP();
return m_d->keys.first();
}
KisKeyframeSP KisKeyframeChannel::nextKeyframe(KisKeyframeSP keyframe) const
{
KeyframesMap::const_iterator i = m_d->keys.constFind(keyframe->time());
if (i == m_d->keys.constEnd()) return KisKeyframeSP(0);
i++;
if (i == m_d->keys.constEnd()) return KisKeyframeSP(0);
return i.value();
}
KisKeyframeSP KisKeyframeChannel::previousKeyframe(KisKeyframeSP keyframe) const
{
KeyframesMap::const_iterator i = m_d->keys.constFind(keyframe->time());
if (i == m_d->keys.constBegin() || i == m_d->keys.constEnd()) return KisKeyframeSP(0);
i--;
return i.value();
}
KisKeyframeSP KisKeyframeChannel::lastKeyframe() const
{
if (m_d->keys.isEmpty()) return KisKeyframeSP(0);
return (m_d->keys.end()-1).value();
}
int KisKeyframeChannel::framesHash() const
{
KeyframesMap::const_iterator it = m_d->keys.constBegin();
KeyframesMap::const_iterator end = m_d->keys.constEnd();
int hash = 0;
while (it != end) {
hash += it.key();
++it;
}
return hash;
}
QSet KisKeyframeChannel::allKeyframeIds() const
{
QSet frames;
KeyframesMap::const_iterator it = m_d->keys.constBegin();
KeyframesMap::const_iterator end = m_d->keys.constEnd();
while (it != end) {
frames.insert(it.key());
++it;
}
return frames;
}
KisTimeRange KisKeyframeChannel::affectedFrames(int time) const
{
if (m_d->keys.isEmpty()) return KisTimeRange::infinite(0);
KeyframesMap::const_iterator active = activeKeyIterator(time);
KeyframesMap::const_iterator next;
+ // ie. time is before the first keyframe
+ const bool noActiveKeyframe = (active == m_d->keys.constEnd());
+
int from;
- if (active == m_d->keys.constEnd()) {
- // No active keyframe, ie. time is before the first keyframe
+ if (noActiveKeyframe) {
from = 0;
next = m_d->keys.constBegin();
} else {
from = active.key();
next = active + 1;
}
- const KisKeyframe::InterpolationMode activeMode = active->data()->interpolationMode();
-
if (next == m_d->keys.constEnd()) {
return KisTimeRange::infinite(from);
} else {
- if (activeMode == KisKeyframe::InterpolationMode::Constant) {
+ const KisKeyframe::InterpolationMode activeMode = noActiveKeyframe ? KisKeyframe::Constant :
+ active->data()->interpolationMode();
+
+ if (activeMode == KisKeyframe::Constant) {
return KisTimeRange::fromTime(from, next.key() - 1);
} else {
return KisTimeRange::fromTime(from, from);
}
}
}
KisTimeRange KisKeyframeChannel::identicalFrames(int time) const
{
KeyframesMap::const_iterator active = activeKeyIterator(time);
if (active != m_d->keys.constEnd() && (active+1) != m_d->keys.constEnd()) {
if (active->data()->interpolationMode() != KisKeyframe::Constant) {
return KisTimeRange::fromTime(time, time);
}
}
return affectedFrames(time);
}
int KisKeyframeChannel::keyframeRowIndexOf(KisKeyframeSP keyframe) const
{
KeyframesMap::const_iterator it = m_d->keys.constBegin();
KeyframesMap::const_iterator end = m_d->keys.constEnd();
int row = 0;
for (; it != end; ++it) {
if (it.value().data() == keyframe) {
return row;
}
row++;
}
return -1;
}
KisKeyframeSP KisKeyframeChannel::keyframeAtRow(int row) const
{
KeyframesMap::const_iterator it = m_d->keys.constBegin();
KeyframesMap::const_iterator end = m_d->keys.constEnd();
for (; it != end; ++it) {
if (row <= 0) {
return it.value();
}
row--;
}
return KisKeyframeSP();
}
int KisKeyframeChannel::keyframeInsertionRow(int time) const
{
KeyframesMap::const_iterator it = m_d->keys.constBegin();
KeyframesMap::const_iterator end = m_d->keys.constEnd();
int row = 0;
for (; it != end; ++it) {
if (it.value()->time() > time) {
break;
}
row++;
}
return row;
}
QDomElement KisKeyframeChannel::toXML(QDomDocument doc, const QString &layerFilename)
{
QDomElement channelElement = doc.createElement("channel");
channelElement.setAttribute("name", id());
Q_FOREACH (KisKeyframeSP keyframe, m_d->keys.values()) {
QDomElement keyframeElement = doc.createElement("keyframe");
keyframeElement.setAttribute("time", keyframe->time());
keyframeElement.setAttribute("color-label", keyframe->colorLabel());
saveKeyframe(keyframe, keyframeElement, layerFilename);
channelElement.appendChild(keyframeElement);
}
return channelElement;
}
void KisKeyframeChannel::loadXML(const QDomElement &channelNode)
{
for (QDomElement keyframeNode = channelNode.firstChildElement(); !keyframeNode.isNull(); keyframeNode = keyframeNode.nextSiblingElement()) {
if (keyframeNode.nodeName().toUpper() != "KEYFRAME") continue;
KisKeyframeSP keyframe = loadKeyframe(keyframeNode);
KIS_SAFE_ASSERT_RECOVER(keyframe) { continue; }
if (keyframeNode.hasAttribute("color-label")) {
keyframe->setColorLabel(keyframeNode.attribute("color-label").toUInt());
}
m_d->keys.insert(keyframe->time(), keyframe);
}
}
bool KisKeyframeChannel::swapExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, int dstTime, KUndo2Command *parentCommand)
{
if (srcChannel->id() != id()) {
warnKrita << "Cannot copy frames from different ids:" << ppVar(srcChannel->id()) << ppVar(id());
return KisKeyframeSP();
}
LAZY_INITIALIZE_PARENT_COMMAND(parentCommand);
KisKeyframeSP srcFrame = srcChannel->keyframeAt(srcTime);
KisKeyframeSP dstFrame = keyframeAt(dstTime);
if (!dstFrame && srcFrame) {
copyExternalKeyframe(srcChannel, srcTime, dstTime, parentCommand);
srcChannel->deleteKeyframe(srcFrame, parentCommand);
} else if (dstFrame && !srcFrame) {
srcChannel->copyExternalKeyframe(this, dstTime, srcTime, parentCommand);
deleteKeyframe(dstFrame, parentCommand);
} else if (dstFrame && srcFrame) {
const int fakeFrameTime = -1;
KisKeyframeSP newKeyframe = createKeyframe(fakeFrameTime, KisKeyframeSP(), parentCommand);
uploadExternalKeyframe(srcChannel, srcTime, newKeyframe);
srcChannel->copyExternalKeyframe(this, dstTime, srcTime, parentCommand);
// do not recreate frame!
deleteKeyframeImpl(dstFrame, parentCommand, false);
newKeyframe->setTime(dstTime);
KUndo2Command *cmd = new KisReplaceKeyframeCommand(this, newKeyframe->time(), newKeyframe, parentCommand);
cmd->redo();
}
return true;
}
KisKeyframeSP KisKeyframeChannel::copyExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, int dstTime, KUndo2Command *parentCommand)
{
if (srcChannel->id() != id()) {
warnKrita << "Cannot copy frames from different ids:" << ppVar(srcChannel->id()) << ppVar(id());
return KisKeyframeSP();
}
LAZY_INITIALIZE_PARENT_COMMAND(parentCommand);
KisKeyframeSP dstFrame = keyframeAt(dstTime);
if (dstFrame) {
deleteKeyframeImpl(dstFrame, parentCommand, false);
}
KisKeyframeSP newKeyframe = createKeyframe(dstTime, KisKeyframeSP(), parentCommand);
uploadExternalKeyframe(srcChannel, srcTime, newKeyframe);
KUndo2Command *cmd = new KisReplaceKeyframeCommand(this, newKeyframe->time(), newKeyframe, parentCommand);
cmd->redo();
return newKeyframe;
}
KisKeyframeChannel::KeyframesMap::const_iterator
KisKeyframeChannel::activeKeyIterator(int time) const
{
KeyframesMap::const_iterator i = const_cast(&m_d->keys)->upperBound(time);
if (i == m_d->keys.constBegin()) return m_d->keys.constEnd();
return --i;
}
void KisKeyframeChannel::requestUpdate(const KisTimeRange &range, const QRect &rect)
{
if (m_d->node) {
m_d->node->invalidateFrames(range, rect);
int currentTime = m_d->defaultBounds->currentTime();
if (range.contains(currentTime)) {
m_d->node->setDirty(rect);
}
}
}
void KisKeyframeChannel::workaroundBrokenFrameTimeBug(int *time)
{
/**
* Between Krita 4.1 and 4.4 Krita had a bug which resulted in creating frames
* with negative time stamp. The bug has been fixed, but there might be some files
* still in the wild.
*
* TODO: remove this workaround in Krita 5.0, when no such file are left :)
*/
if (*time < 0) {
qWarning() << "WARNING: Loading a file with negative animation frames!";
qWarning() << " The file has been saved with a buggy version of Krita.";
qWarning() << " All the frames with negative ids will be dropped!";
qWarning() << " " << ppVar(this->id()) << ppVar(*time);
m_d->haveBrokenFrameTimeBug = true;
*time = 0;
}
if (m_d->haveBrokenFrameTimeBug) {
while (keyframeAt(*time)) {
(*time)++;
}
}
}
int KisKeyframeChannel::currentTime() const
{
return m_d->defaultBounds->currentTime();
}
qreal KisKeyframeChannel::minScalarValue() const
{
return 0;
}
qreal KisKeyframeChannel::maxScalarValue() const
{
return 0;
}
qreal KisKeyframeChannel::scalarValue(const KisKeyframeSP keyframe) const
{
Q_UNUSED(keyframe);
return 0;
}
void KisKeyframeChannel::setScalarValue(KisKeyframeSP keyframe, qreal value, KUndo2Command *parentCommand)
{
Q_UNUSED(keyframe);
Q_UNUSED(value);
Q_UNUSED(parentCommand);
}
diff --git a/libs/image/kis_keyframe_channel.h b/libs/image/kis_keyframe_channel.h
index 50a8077cd2..a503843d0b 100644
--- a/libs/image/kis_keyframe_channel.h
+++ b/libs/image/kis_keyframe_channel.h
@@ -1,172 +1,173 @@
/*
* Copyright (c) 2015 Jouni Pentikäinen
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_KEYFRAME_CHANNEL_H
#define KIS_KEYFRAME_CHANNEL_H
#include
#include
#include
#include "kis_types.h"
#include "KoID.h"
#include "kritaimage_export.h"
#include "kis_keyframe.h"
#include "kis_default_bounds.h"
#include "kis_default_bounds_node_wrapper.h"
class KisTimeRange;
class KRITAIMAGE_EXPORT KisKeyframeChannel : public QObject
{
Q_OBJECT
public:
// Standard Keyframe Ids
static const KoID Content;
static const KoID Opacity;
static const KoID TransformArguments;
static const KoID TransformPositionX;
static const KoID TransformPositionY;
static const KoID TransformScaleX;
static const KoID TransformScaleY;
static const KoID TransformShearX;
static const KoID TransformShearY;
static const KoID TransformRotationX;
static const KoID TransformRotationY;
static const KoID TransformRotationZ;
public:
KisKeyframeChannel(const KoID& id, KisNodeWSP parent = 0);
+ KisKeyframeChannel(const KoID& id, KisDefaultBoundsBaseSP bounds );
KisKeyframeChannel(const KisKeyframeChannel &rhs, KisNodeWSP newParent);
~KisKeyframeChannel() override;
QString id() const;
QString name() const;
void setNode(KisNodeWSP node);
KisNodeWSP node() const;
KisKeyframeSP addKeyframe(int time, KUndo2Command *parentCommand = 0);
bool deleteKeyframe(KisKeyframeSP keyframe, KUndo2Command *parentCommand = 0);
bool moveKeyframe(KisKeyframeSP keyframe, int newTime, KUndo2Command *parentCommand = 0);
bool swapFrames(int lhsTime, int rhsTime, KUndo2Command *parentCommand = 0);
KisKeyframeSP copyKeyframe(const KisKeyframeSP keyframe, int newTime, KUndo2Command *parentCommand = 0);
KisKeyframeSP copyExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, int dstTime, KUndo2Command *parentCommand = 0);
bool swapExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, int dstTime, KUndo2Command *parentCommand = 0);
KisKeyframeSP keyframeAt(int time) const;
KisKeyframeSP activeKeyframeAt(int time) const;
KisKeyframeSP currentlyActiveKeyframe() const;
KisKeyframeSP firstKeyframe() const;
KisKeyframeSP nextKeyframe(KisKeyframeSP keyframe) const;
KisKeyframeSP previousKeyframe(KisKeyframeSP keyframe) const;
KisKeyframeSP lastKeyframe() const;
/**
* Calculates a pseudo-unique keyframes hash. The hash changes
* every time any frame is added/removed/moved
*/
int framesHash() const;
QSet allKeyframeIds() const;
/**
* Get the set of frames affected by any changes to the value
* of the active keyframe at the given time.
*/
KisTimeRange affectedFrames(int time) const;
/**
* Get a set of frames for which the channel gives identical
* results, compared to the given frame.
*
* Note: this set may be different than the set of affected frames
* due to interpolation.
*/
KisTimeRange identicalFrames(int time) const;
int keyframeCount() const;
int keyframeRowIndexOf(KisKeyframeSP keyframe) const;
KisKeyframeSP keyframeAtRow(int row) const;
int keyframeInsertionRow(int time) const;
virtual bool hasScalarValue() const = 0;
virtual qreal minScalarValue() const;
virtual qreal maxScalarValue() const;
virtual qreal scalarValue(const KisKeyframeSP keyframe) const;
virtual void setScalarValue(KisKeyframeSP keyframe, qreal value, KUndo2Command *parentCommand = 0);
virtual QDomElement toXML(QDomDocument doc, const QString &layerFilename);
virtual void loadXML(const QDomElement &channelNode);
int currentTime() const;
Q_SIGNALS:
void sigKeyframeAboutToBeAdded(KisKeyframeSP keyframe);
void sigKeyframeAdded(KisKeyframeSP keyframe);
void sigKeyframeAboutToBeRemoved(KisKeyframeSP keyframe);
void sigKeyframeRemoved(KisKeyframeSP keyframe);
void sigKeyframeAboutToBeMoved(KisKeyframeSP keyframe, int toTime);
void sigKeyframeMoved(KisKeyframeSP keyframe, int fromTime);
void sigKeyframeChanged(KisKeyframeSP keyframe);
protected:
typedef QMap KeyframesMap;
KeyframesMap &keys();
const KeyframesMap &constKeys() const;
KeyframesMap::const_iterator activeKeyIterator(int time) const;
virtual KisKeyframeSP createKeyframe(int time, const KisKeyframeSP copySrc, KUndo2Command *parentCommand) = 0;
virtual void destroyKeyframe(KisKeyframeSP key, KUndo2Command *parentCommand) = 0;
virtual void uploadExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, KisKeyframeSP dstFrame) = 0;
virtual QRect affectedRect(KisKeyframeSP key) = 0;
virtual void requestUpdate(const KisTimeRange &range, const QRect &rect);
virtual KisKeyframeSP loadKeyframe(const QDomElement &keyframeNode) = 0;
virtual void saveKeyframe(KisKeyframeSP keyframe, QDomElement keyframeElement, const QString &layerFilename) = 0;
void workaroundBrokenFrameTimeBug(int *time);
private:
KisKeyframeSP replaceKeyframeAt(int time, KisKeyframeSP newKeyframe);
void insertKeyframeLogical(KisKeyframeSP keyframe);
void removeKeyframeLogical(KisKeyframeSP keyframe);
bool deleteKeyframeImpl(KisKeyframeSP keyframe, KUndo2Command *parentCommand, bool recreate);
void moveKeyframeImpl(KisKeyframeSP keyframe, int newTime);
void swapKeyframesImpl(KisKeyframeSP lhsKeyframe, KisKeyframeSP rhsKeyframe);
friend class KisMoveFrameCommand;
friend class KisReplaceKeyframeCommand;
friend class KisSwapFramesCommand;
private:
KisKeyframeSP insertKeyframe(int time, const KisKeyframeSP copySrc, KUndo2Command *parentCommand);
struct Private;
QScopedPointer m_d;
};
#endif // KIS_KEYFRAME_CHANNEL_H
diff --git a/libs/image/kis_paint_device.cc b/libs/image/kis_paint_device.cc
index dc59d92dfe..e8d6de9337 100644
--- a/libs/image/kis_paint_device.cc
+++ b/libs/image/kis_paint_device.cc
@@ -1,2238 +1,2244 @@
/*
* 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
#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, 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());
KIS_SAFE_ASSERT_RECOVER_NOOP(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);
KisRegion 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:
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)
{
/**
* The result of currentData() depends on the current
* level of detail and animation frame index. So we
* should first connect the device to the new
* default bounds object, and only after that ask
* currentData() to start cloning.
*/
q->setDefaultPixel(KoColor(srcData->dataManager()->defaultPixel(), colorSpace()));
q->setDefaultBounds(src->defaultBounds());
currentData()->prepareClone(srcData);
}
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;
};
KisRegion 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
}
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);
}
class KisPaintDevice::Private::DeviceChangeProfileCommand : public KUndo2Command
{
public:
DeviceChangeProfileCommand(KisPaintDeviceSP device, KUndo2Command *parent = 0)
: KUndo2Command(parent),
m_device(device)
{
}
virtual void emitNotifications()
{
m_device->emitProfileChanged();
}
void redo() override
{
if (m_firstRun) {
m_firstRun = false;
return;
}
KUndo2Command::redo();
emitNotifications();
}
void undo() override
{
KUndo2Command::undo();
emitNotifications();
}
protected:
KisPaintDeviceSP m_device;
private:
bool m_firstRun {true};
};
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, 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, 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);
}
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 KisRegion ®ion)
{
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();
}
KisRegion 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;
}
KisRegion KisPaintDevice::regionExact() const
{
QVector sourceRects = region().rects();
QVector resultRects;
const KoColor defaultPixel = this->defaultPixel();
Impl::CheckNonDefault compareOp(pixelSize(), defaultPixel.data());
Q_FOREACH (const QRect &rc1, sourceRects) {
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()) {
resultRects << result;
}
}
}
return KisRegion(std::move(resultRects));
}
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, KUndo2Command *parentCommand)
{
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, this->parentNode()));
+ if (m_d->parent.isValid()) {
+ m_d->contentChannel.reset(new KisRasterKeyframeChannel(id, this, m_d->parent));
+ } else {
+ //fallback when paint device is isolated / does not belong to a node.
+ ENTER_FUNCTION() << ppVar(this) << ppVar(m_d->defaultBounds);
+ 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()
{
}
KisRegion 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_raster_keyframe_channel.cpp b/libs/image/kis_raster_keyframe_channel.cpp
index d4b97836fd..a1259e6b05 100644
--- a/libs/image/kis_raster_keyframe_channel.cpp
+++ b/libs/image/kis_raster_keyframe_channel.cpp
@@ -1,316 +1,322 @@
/*
* Copyright (c) 2015 Jouni Pentikäinen
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_raster_keyframe_channel.h"
#include "kis_node.h"
#include "kis_dom_utils.h"
#include "kis_global.h"
#include "kis_paint_device.h"
#include "kis_paint_device_frames_interface.h"
#include "kis_time_range.h"
#include "kundo2command.h"
#include "kis_onion_skin_compositor.h"
class KisRasterKeyframe : public KisKeyframe
{
public:
KisRasterKeyframe(KisRasterKeyframeChannel *channel, int time, int frameId)
: KisKeyframe(channel, time)
, frameId(frameId)
{}
KisRasterKeyframe(const KisRasterKeyframe *rhs, KisKeyframeChannel *channel)
: KisKeyframe(rhs, channel)
, frameId(rhs->frameId)
{}
int frameId;
KisKeyframeSP cloneFor(KisKeyframeChannel *channel) const override
{
return toQShared(new KisRasterKeyframe(this, channel));
}
bool hasContent() const override {
KisRasterKeyframeChannel *channel = dynamic_cast(this->channel());
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(channel, true);
return channel->keyframeHasContent(this);
}
};
struct KisRasterKeyframeChannel::Private
{
Private(KisPaintDeviceWSP paintDevice, const QString filenameSuffix)
: paintDevice(paintDevice),
filenameSuffix(filenameSuffix),
onionSkinsEnabled(false)
{}
KisPaintDeviceWSP paintDevice;
QMap frameFilenames;
QString filenameSuffix;
bool onionSkinsEnabled;
};
KisRasterKeyframeChannel::KisRasterKeyframeChannel(const KoID &id, const KisPaintDeviceWSP paintDevice, KisNodeWSP parent)
: KisKeyframeChannel(id, parent),
m_d(new Private(paintDevice, QString()))
{
}
+KisRasterKeyframeChannel::KisRasterKeyframeChannel(const KoID &id, const KisPaintDeviceWSP paintDevice, const KisDefaultBoundsBaseSP bounds)
+ : KisKeyframeChannel(id, bounds ),
+ m_d(new Private(paintDevice, QString()))
+{
+}
+
KisRasterKeyframeChannel::KisRasterKeyframeChannel(const KisRasterKeyframeChannel &rhs, KisNodeWSP newParent, const KisPaintDeviceWSP newPaintDevice)
: KisKeyframeChannel(rhs, newParent),
m_d(new Private(newPaintDevice, rhs.m_d->filenameSuffix))
{
KIS_ASSERT_RECOVER_NOOP(&rhs != this);
m_d->frameFilenames = rhs.m_d->frameFilenames;
m_d->onionSkinsEnabled = rhs.m_d->onionSkinsEnabled;
}
KisRasterKeyframeChannel::~KisRasterKeyframeChannel()
{
}
int KisRasterKeyframeChannel::frameId(KisKeyframeSP keyframe) const
{
return frameId(keyframe.data());
}
int KisRasterKeyframeChannel::frameId(const KisKeyframe *keyframe) const
{
const KisRasterKeyframe *key = dynamic_cast(keyframe);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(key, -1);
return key->frameId;
}
int KisRasterKeyframeChannel::frameIdAt(int time) const
{
KisKeyframeSP activeKey = activeKeyframeAt(time);
if (activeKey.isNull()) return -1;
return frameId(activeKey);
}
void KisRasterKeyframeChannel::fetchFrame(KisKeyframeSP keyframe, KisPaintDeviceSP targetDevice)
{
m_d->paintDevice->framesInterface()->fetchFrame(frameId(keyframe), targetDevice);
}
void KisRasterKeyframeChannel::importFrame(int time, KisPaintDeviceSP sourceDevice, KUndo2Command *parentCommand)
{
KisKeyframeSP keyframe = addKeyframe(time, parentCommand);
const int frame = frameId(keyframe);
m_d->paintDevice->framesInterface()->uploadFrame(frame, sourceDevice);
}
QRect KisRasterKeyframeChannel::frameExtents(KisKeyframeSP keyframe)
{
return m_d->paintDevice->framesInterface()->frameBounds(frameId(keyframe));
}
QString KisRasterKeyframeChannel::frameFilename(int frameId) const
{
return m_d->frameFilenames.value(frameId, QString());
}
void KisRasterKeyframeChannel::setFilenameSuffix(const QString &suffix)
{
m_d->filenameSuffix = suffix;
}
void KisRasterKeyframeChannel::setFrameFilename(int frameId, const QString &filename)
{
Q_ASSERT(!m_d->frameFilenames.contains(frameId));
m_d->frameFilenames.insert(frameId, filename);
}
QString KisRasterKeyframeChannel::chooseFrameFilename(int frameId, const QString &layerFilename)
{
QString filename;
if (m_d->frameFilenames.isEmpty()) {
// Use legacy naming convention for first keyframe
filename = layerFilename + m_d->filenameSuffix;
} else {
filename = layerFilename + m_d->filenameSuffix + ".f" + QString::number(frameId);
}
setFrameFilename(frameId, filename);
return filename;
}
KisKeyframeSP KisRasterKeyframeChannel::createKeyframe(int time, const KisKeyframeSP copySrc, KUndo2Command *parentCommand)
{
KisRasterKeyframe *keyframe;
KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->paintDevice->defaultBounds()->currentTime() == this->currentTime());
if (!copySrc) {
int frameId = m_d->paintDevice->framesInterface()->createFrame(false, 0, QPoint(), parentCommand);
keyframe = new KisRasterKeyframe(this, time, frameId);
} else {
int srcFrame = frameId(copySrc);
int frameId = m_d->paintDevice->framesInterface()->createFrame(true, srcFrame, QPoint(), parentCommand);
KisRasterKeyframe *srcKeyframe = dynamic_cast(copySrc.data());
Q_ASSERT(srcKeyframe);
keyframe = new KisRasterKeyframe(srcKeyframe, this);
keyframe->setTime(time);
keyframe->frameId = frameId;
}
return toQShared(keyframe);
}
void KisRasterKeyframeChannel::destroyKeyframe(KisKeyframeSP key, KUndo2Command *parentCommand)
{
m_d->paintDevice->framesInterface()->deleteFrame(frameId(key), parentCommand);
}
void KisRasterKeyframeChannel::uploadExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, KisKeyframeSP dstFrame)
{
KisRasterKeyframeChannel *srcRasterChannel = dynamic_cast(srcChannel);
KIS_ASSERT_RECOVER_RETURN(srcRasterChannel);
const int srcId = srcRasterChannel->frameIdAt(srcTime);
const int dstId = frameId(dstFrame);
m_d->paintDevice->framesInterface()->
uploadFrame(srcId,
dstId,
srcRasterChannel->m_d->paintDevice);
}
QRect KisRasterKeyframeChannel::affectedRect(KisKeyframeSP key)
{
KeyframesMap::iterator it = keys().find(key->time());
QRect rect;
// Calculate changed area as the union of the current and previous keyframe.
// This makes sure there are no artifacts left over from the previous frame
// where the new one doesn't cover the area.
if (it == keys().begin()) {
// Using the *next* keyframe at the start of the timeline avoids artifacts
// when deleting or moving the first key
it++;
} else {
it--;
}
if (it != keys().end()) {
rect = m_d->paintDevice->framesInterface()->frameBounds(frameId(it.value()));
}
rect |= m_d->paintDevice->framesInterface()->frameBounds(frameId(key));
if (m_d->onionSkinsEnabled) {
const QRect dirtyOnionSkinsRect =
KisOnionSkinCompositor::instance()->calculateFullExtent(m_d->paintDevice);
rect |= dirtyOnionSkinsRect;
}
return rect;
}
QDomElement KisRasterKeyframeChannel::toXML(QDomDocument doc, const QString &layerFilename)
{
m_d->frameFilenames.clear();
return KisKeyframeChannel::toXML(doc, layerFilename);
}
void KisRasterKeyframeChannel::loadXML(const QDomElement &channelNode)
{
m_d->frameFilenames.clear();
KisKeyframeChannel::loadXML(channelNode);
}
void KisRasterKeyframeChannel::saveKeyframe(KisKeyframeSP keyframe, QDomElement keyframeElement, const QString &layerFilename)
{
int frame = frameId(keyframe);
QString filename = frameFilename(frame);
if (filename.isEmpty()) {
filename = chooseFrameFilename(frame, layerFilename);
}
keyframeElement.setAttribute("frame", filename);
QPoint offset = m_d->paintDevice->framesInterface()->frameOffset(frame);
KisDomUtils::saveValue(&keyframeElement, "offset", offset);
}
KisKeyframeSP KisRasterKeyframeChannel::loadKeyframe(const QDomElement &keyframeNode)
{
int time = keyframeNode.attribute("time").toInt();
workaroundBrokenFrameTimeBug(&time);
QPoint offset;
KisDomUtils::loadValue(keyframeNode, "offset", &offset);
QString frameFilename = keyframeNode.attribute("frame");
KisKeyframeSP keyframe;
if (m_d->frameFilenames.isEmpty()) {
// First keyframe loaded: use the existing frame
KIS_SAFE_ASSERT_RECOVER_NOOP(keyframeCount() == 1);
keyframe = constKeys().begin().value();
// Remove from keys. It will get reinserted with new time once we return
keys().remove(keyframe->time());
keyframe->setTime(time);
m_d->paintDevice->framesInterface()->setFrameOffset(frameId(keyframe), offset);
} else {
KUndo2Command tempCommand;
int frameId = m_d->paintDevice->framesInterface()->createFrame(false, 0, offset, &tempCommand);
keyframe = toQShared(new KisRasterKeyframe(this, time, frameId));
}
setFrameFilename(frameId(keyframe), frameFilename);
return keyframe;
}
bool KisRasterKeyframeChannel::keyframeHasContent(const KisKeyframe *keyframe) const
{
return !m_d->paintDevice->framesInterface()->frameBounds(frameId(keyframe)).isEmpty();
}
bool KisRasterKeyframeChannel::hasScalarValue() const
{
return false;
}
void KisRasterKeyframeChannel::setOnionSkinsEnabled(bool value)
{
m_d->onionSkinsEnabled = value;
}
bool KisRasterKeyframeChannel::onionSkinsEnabled() const
{
return m_d->onionSkinsEnabled;
}
diff --git a/libs/image/kis_raster_keyframe_channel.h b/libs/image/kis_raster_keyframe_channel.h
index a1c67300a9..1b77d4d9c0 100644
--- a/libs/image/kis_raster_keyframe_channel.h
+++ b/libs/image/kis_raster_keyframe_channel.h
@@ -1,96 +1,97 @@
/*
* Copyright (c) 2015 Jouni Pentikäinen
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_RASTER_KEYFRAME_CHANNEL_H
#define _KIS_RASTER_KEYFRAME_CHANNEL_H
#include "kis_keyframe_channel.h"
class KRITAIMAGE_EXPORT KisRasterKeyframeChannel : public KisKeyframeChannel
{
Q_OBJECT
public:
KisRasterKeyframeChannel(const KoID& id, const KisPaintDeviceWSP paintDevice, KisNodeWSP parent);
+ KisRasterKeyframeChannel(const KoID& id, const KisPaintDeviceWSP paintDevice, const KisDefaultBoundsBaseSP bounds);
KisRasterKeyframeChannel(const KisRasterKeyframeChannel &rhs, KisNodeWSP newParent, const KisPaintDeviceWSP newPaintDevice);
~KisRasterKeyframeChannel() override;
public:
/**
* Return the ID of the active frame at a given time. The active frame is
* defined by the keyframe at the given time or the last keyframe before it.
* @param time
* @return active frame id
*/
int frameIdAt(int time) const;
/**
* Copy the active frame at given time to target device.
* @param keyframe keyframe to copy from
* @param targetDevice device to copy the frame to
*/
void fetchFrame(KisKeyframeSP keyframe, KisPaintDeviceSP targetDevice);
/**
* Copy the content of the sourceDevice into a new keyframe at given time
* @param time position of new keyframe
* @param sourceDevice source for content
* @param parentCommand parent command used for stacking
*/
void importFrame(int time, KisPaintDeviceSP sourceDevice, KUndo2Command *parentCommand);
QRect frameExtents(KisKeyframeSP keyframe);
QString frameFilename(int frameId) const;
/**
* When choosing filenames for frames, this will be appended to the node filename
*/
void setFilenameSuffix(const QString &suffix);
bool hasScalarValue() const override;
QDomElement toXML(QDomDocument doc, const QString &layerFilename) override;
void loadXML(const QDomElement &channelNode) override;
void setOnionSkinsEnabled(bool value);
bool onionSkinsEnabled() const;
protected:
KisKeyframeSP createKeyframe(int time, const KisKeyframeSP copySrc, KUndo2Command *parentCommand) override;
void destroyKeyframe(KisKeyframeSP key, KUndo2Command *parentCommand) override;
void uploadExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, KisKeyframeSP dstFrame) override;
QRect affectedRect(KisKeyframeSP key) override;
void saveKeyframe(KisKeyframeSP keyframe, QDomElement keyframeElement, const QString &layerFilename) override;
KisKeyframeSP loadKeyframe(const QDomElement &keyframeNode) override;
friend class KisRasterKeyframe;
bool keyframeHasContent(const KisKeyframe *keyframe) const;
private:
void setFrameFilename(int frameId, const QString &filename);
QString chooseFrameFilename(int frameId, const QString &layerFilename);
int frameId(KisKeyframeSP keyframe) const;
int frameId(const KisKeyframe *keyframe) const;
struct Private;
QScopedPointer m_d;
};
#endif
diff --git a/libs/image/tiles3/kis_memento_manager.cc b/libs/image/tiles3/kis_memento_manager.cc
index 1e86e2ddab..4aaafc493b 100644
--- a/libs/image/tiles3/kis_memento_manager.cc
+++ b/libs/image/tiles3/kis_memento_manager.cc
@@ -1,425 +1,433 @@
/*
* Copyright (c) 2009 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include
#include "kis_memento_manager.h"
#include "kis_memento.h"
//#define DEBUG_MM
#ifdef DEBUG_MM
#define DEBUG_LOG_TILE_ACTION(action, tile, col, row) \
printf("### MementoManager (0x%X): %s " \
"\ttile:\t0x%X (%d, %d) ###\n", (quintptr)this, action, \
(quintptr)tile, col, row)
#define DEBUG_LOG_SIMPLE_ACTION(action) \
printf("### MementoManager (0x%X): %s\n", (quintptr)this, action)
#define DEBUG_DUMP_MESSAGE(action) do { \
printf("\n### MementoManager (0x%X): %s \t\t##########\n", \
(quintptr)this, action); \
debugPrintInfo(); \
printf("##################################################################\n\n"); \
} while(0)
#else
#define DEBUG_LOG_TILE_ACTION(action, tile, col, row)
#define DEBUG_LOG_SIMPLE_ACTION(action)
#define DEBUG_DUMP_MESSAGE(action)
#endif
/**
* The class is supposed to store the changes of the paint device
* it is associated with. The history of changes is presented in form
* of transactions (revisions). If you purge the history of one
* transaction (revision) with purgeHistory() we won't be able to undo
* the changes made by this transactions.
*
* The Memento Manager can be in two states:
* - Named Transaction is in progress - it means the caller
* has explicitly requested creation of a new transaction.
* The handle for the transaction is stored on a side of
* the caller. And the history will be automatically purged
* when the handler dies.
* - Anonymous Transaction is in progress - the caller isn't
* bothered about transactions at all. We pretend as we do
* not support any versioning and do not have any historical
* information. The history of such transactions is not purged
* automatically, but it is free'd when younger named transaction
* is purged.
*/
#define blockRegistration() (m_registrationBlocked = true)
#define unblockRegistration() (m_registrationBlocked = false)
#define registrationBlocked() (m_registrationBlocked)
#define namedTransactionInProgress() ((bool)m_currentMemento)
KisMementoManager::KisMementoManager()
: m_index(0),
m_headsHashTable(0),
m_registrationBlocked(false)
{
/**
* Tile change/delete registration is enabled for all
* devices by default. It can't be delayed.
*/
}
KisMementoManager::KisMementoManager(const KisMementoManager& rhs)
: m_index(rhs.m_index, 0),
m_revisions(rhs.m_revisions),
m_cancelledRevisions(rhs.m_cancelledRevisions),
m_headsHashTable(rhs.m_headsHashTable, 0),
m_currentMemento(rhs.m_currentMemento),
m_registrationBlocked(rhs.m_registrationBlocked)
{
Q_ASSERT_X(!m_registrationBlocked,
"KisMementoManager", "(impossible happened) "
"The device has been copied while registration was blocked");
}
KisMementoManager::~KisMementoManager()
{
// Nothing to be done here. Happily...
// Everything is done by QList and KisSharedPtr...
DEBUG_LOG_SIMPLE_ACTION("died\n");
}
/**
* NOTE: We don't assume that the registerTileChange/Delete
* can be called once a commit only. Reverse can happen when we
* do sequential clears of the device. In such a case the tiles
* will be removed and added several times during a commit.
*
* TODO: There is an 'uncomfortable' state for the tile possible
* 1) Imagine we have a clear device
* 2) Then we painted something in a tile
* 3) It registered itself using registerTileChange()
* 4) Then we called clear() and getMemento() [==commit()]
* 5) The tile will be registered as deleted and successfully
* committed to a revision. That means the states of the memento
* manager at stages 1 and 5 do not coincide.
* This will not lead to any memory leaks or bugs seen, it just
* not good from a theoretical perspective.
*/
void KisMementoManager::registerTileChange(KisTile *tile)
{
if (registrationBlocked()) return;
DEBUG_LOG_TILE_ACTION("reg. [C]", tile, tile->col(), tile->row());
KisMementoItemSP mi = m_index.getExistingTile(tile->col(), tile->row());
if(!mi) {
mi = new KisMementoItem();
mi->changeTile(tile);
m_index.addTile(mi);
if(namedTransactionInProgress())
m_currentMemento->updateExtent(mi->col(), mi->row());
}
else {
mi->reset();
mi->changeTile(tile);
}
}
void KisMementoManager::registerTileDeleted(KisTile *tile)
{
if (registrationBlocked()) return;
DEBUG_LOG_TILE_ACTION("reg. [D]", tile, tile->col(), tile->row());
KisMementoItemSP mi = m_index.getExistingTile(tile->col(), tile->row());
if(!mi) {
mi = new KisMementoItem();
mi->deleteTile(tile, m_headsHashTable.defaultTileData());
m_index.addTile(mi);
if(namedTransactionInProgress())
m_currentMemento->updateExtent(mi->col(), mi->row());
}
else {
mi->reset();
mi->deleteTile(tile, m_headsHashTable.defaultTileData());
}
}
void KisMementoManager::commit()
{
if (m_index.isEmpty()) {
if(namedTransactionInProgress()) {
//warnTiles << "Named Transaction is empty";
/**
* We still need to continue commit, because
* a named transaction may be reverted by the user
*/
}
else {
m_currentMemento = 0;
return;
}
}
KisMementoItemList revisionList;
KisMementoItemSP mi;
KisMementoItemSP parentMI;
bool newTile;
KisMementoItemHashTableIterator iter(&m_index);
while ((mi = iter.tile())) {
parentMI = m_headsHashTable.getTileLazy(mi->col(), mi->row(), newTile);
mi->setParent(parentMI);
mi->commit();
revisionList.append(mi);
m_headsHashTable.deleteTile(mi->col(), mi->row());
iter.moveCurrentToHashTable(&m_headsHashTable);
//iter.next(); // previous line does this for us
}
KisHistoryItem hItem;
hItem.itemList = revisionList;
hItem.memento = m_currentMemento.data();
m_revisions.append(hItem);
m_currentMemento = 0;
- Q_ASSERT(m_index.isEmpty());
+ KIS_ASSERT(m_index.isEmpty());
DEBUG_DUMP_MESSAGE("COMMIT_DONE");
// Waking up pooler to prepare copies for us
KisTileDataStore::instance()->kickPooler();
}
KisTileSP KisMementoManager::getCommitedTile(qint32 col, qint32 row, bool &existingTile)
{
/**
* Our getOldTile mechanism is supposed to return current
* tile, if the history is disabled. So we return zero if
* no named transaction is in progress.
*/
if(!namedTransactionInProgress())
return KisTileSP();
KisMementoItemSP mi = m_headsHashTable.getReadOnlyTileLazy(col, row, existingTile);
- Q_ASSERT(mi);
return mi->tile(0);
}
KisMementoSP KisMementoManager::getMemento()
{
/**
* We do not allow nested transactions
*/
KIS_SAFE_ASSERT_RECOVER_NOOP(!namedTransactionInProgress());
+ KIS_SAFE_ASSERT_RECOVER_NOOP(m_index.isEmpty());
// Clear redo() information
m_cancelledRevisions.clear();
commit();
m_currentMemento = new KisMemento(this);
DEBUG_LOG_SIMPLE_ACTION("GET_MEMENTO_DONE");
return m_currentMemento;
}
KisMementoSP KisMementoManager::currentMemento() {
return m_currentMemento;
}
#define forEachReversed(iter, list) \
for(iter=list.end(); iter-- != list.begin();)
-void KisMementoManager::rollback(KisTileHashTable *ht)
+void KisMementoManager::rollback(KisTileHashTable *ht, KisMementoSP memento)
{
commit();
if (! m_revisions.size()) return;
KisHistoryItem changeList = m_revisions.takeLast();
+ // SANITY CHECK: the transaction's memento must be in sync with
+ // the revisions list we have locally
+ KIS_SAFE_ASSERT_RECOVER_NOOP(changeList.memento == memento);
+
KisMementoItemSP mi;
KisMementoItemSP parentMI;
KisMementoItemList::iterator iter;
blockRegistration();
forEachReversed(iter, changeList.itemList) {
mi=*iter;
parentMI = mi->parent();
if (mi->type() == KisMementoItem::CHANGED)
ht->deleteTile(mi->col(), mi->row());
if (parentMI->type() == KisMementoItem::CHANGED)
ht->addTile(parentMI->tile(this));
m_headsHashTable.deleteTile(parentMI->col(), parentMI->row());
m_headsHashTable.addTile(parentMI);
// This is not necessary
//mi->setParent(0);
}
/**
* NOTE: tricky hack alert.
* We have just deleted some tiles from the original hash table.
* And they accurately reported to us about their death. Should
* have reported... But we have prevented their registration with
* explicitly blocking the process. So all the dead tiles are
* going to /dev/null :)
*
* PS: It could cause some race condition... But we insist on
* serialization of rollback()/rollforward() requests. There is
* not much sense in calling rollback() concurrently.
*/
unblockRegistration();
// We have just emulated a commit so:
m_currentMemento = 0;
- Q_ASSERT(!namedTransactionInProgress());
+ KIS_ASSERT(!namedTransactionInProgress());
m_cancelledRevisions.prepend(changeList);
DEBUG_DUMP_MESSAGE("UNDONE");
// Waking up pooler to prepare copies for us
KisTileDataStore::instance()->kickPooler();
}
-void KisMementoManager::rollforward(KisTileHashTable *ht)
+void KisMementoManager::rollforward(KisTileHashTable *ht, KisMementoSP memento)
{
- Q_ASSERT(m_index.isEmpty());
+ KIS_SAFE_ASSERT_RECOVER_RETURN(m_index.isEmpty());
if (!m_cancelledRevisions.size()) return;
KisHistoryItem changeList = m_cancelledRevisions.takeFirst();
+ // SANITY CHECK: the transaction's memento must be in sync with
+ // the revisions list we have locally
+ KIS_SAFE_ASSERT_RECOVER_NOOP(changeList.memento == memento);
+
KisMementoItemSP mi;
blockRegistration();
Q_FOREACH (mi, changeList.itemList) {
if (mi->parent()->type() == KisMementoItem::CHANGED)
ht->deleteTile(mi->col(), mi->row());
if (mi->type() == KisMementoItem::CHANGED)
ht->addTile(mi->tile(this));
m_index.addTile(mi);
}
// see comment in rollback()
m_currentMemento = changeList.memento;
commit();
unblockRegistration();
DEBUG_DUMP_MESSAGE("REDONE");
}
void KisMementoManager::purgeHistory(KisMementoSP oldestMemento)
{
if (m_currentMemento == oldestMemento) {
commit();
}
qint32 revisionIndex = findRevisionByMemento(oldestMemento);
if (revisionIndex < 0) return;
for(; revisionIndex > 0; revisionIndex--) {
resetRevisionHistory(m_revisions.first().itemList);
m_revisions.removeFirst();
}
- Q_ASSERT(m_revisions.first().memento == oldestMemento);
+ KIS_ASSERT(m_revisions.first().memento == oldestMemento);
resetRevisionHistory(m_revisions.first().itemList);
DEBUG_DUMP_MESSAGE("PURGE_HISTORY");
}
qint32 KisMementoManager::findRevisionByMemento(KisMementoSP memento) const
{
qint32 index = -1;
for(qint32 i = 0; i < m_revisions.size(); i++) {
if (m_revisions[i].memento == memento) {
index = i;
break;
}
}
return index;
}
void KisMementoManager::resetRevisionHistory(KisMementoItemList list)
{
KisMementoItemSP parentMI;
KisMementoItemSP mi;
Q_FOREACH (mi, list) {
parentMI = mi->parent();
if(!parentMI) continue;
while (parentMI->parent()) {
parentMI = parentMI->parent();
}
mi->setParent(parentMI);
}
}
void KisMementoManager::setDefaultTileData(KisTileData *defaultTileData)
{
m_headsHashTable.setDefaultTileData(defaultTileData);
m_index.setDefaultTileData(defaultTileData);
}
void KisMementoManager::debugPrintInfo()
{
printf("KisMementoManager stats:\n");
printf("Index list\n");
KisMementoItemSP mi;
KisMementoItemHashTableIteratorConst iter(&m_index);
while ((mi = iter.tile())) {
mi->debugPrintInfo();
iter.next();
}
printf("Revisions list:\n");
qint32 i = 0;
Q_FOREACH (const KisHistoryItem &changeList, m_revisions) {
printf("--- revision #%d ---\n", i++);
Q_FOREACH (mi, changeList.itemList) {
mi->debugPrintInfo();
}
}
printf("\nCancelled revisions list:\n");
i = 0;
Q_FOREACH (const KisHistoryItem &changeList, m_cancelledRevisions) {
printf("--- revision #%d ---\n", m_revisions.size() + i++);
Q_FOREACH (mi, changeList.itemList) {
mi->debugPrintInfo();
}
}
printf("----------------\n");
m_headsHashTable.debugPrintInfo();
}
diff --git a/libs/image/tiles3/kis_memento_manager.h b/libs/image/tiles3/kis_memento_manager.h
index cb9761acf1..7d196d41f5 100644
--- a/libs/image/tiles3/kis_memento_manager.h
+++ b/libs/image/tiles3/kis_memento_manager.h
@@ -1,174 +1,174 @@
/*
* Copyright (c) 2009 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_MEMENTO_MANAGER_
#define KIS_MEMENTO_MANAGER_
#include
#include "kis_memento_item.h"
#include "config-hash-table-implementaion.h"
typedef QList KisMementoItemList;
typedef QListIterator KisMementoItemListIterator;
class KisMemento;
struct KisHistoryItem {
KisMemento* memento;
KisMementoItemList itemList;
};
typedef QList KisHistoryList;
class KisMemento;
typedef KisSharedPtr KisMementoSP;
#ifdef USE_LOCK_FREE_HASH_TABLE
#include "kis_tile_hash_table2.h"
typedef KisTileHashTableTraits2 KisMementoItemHashTable;
typedef KisTileHashTableIteratorTraits2 KisMementoItemHashTableIterator;
typedef KisTileHashTableIteratorTraits2 KisMementoItemHashTableIteratorConst;
#else
#include "kis_tile_hash_table.h"
typedef KisTileHashTableTraits KisMementoItemHashTable;
typedef KisTileHashTableIteratorTraits KisMementoItemHashTableIterator;
typedef KisTileHashTableIteratorTraits KisMementoItemHashTableIteratorConst;
#endif // USE_LOCK_FREE_HASH_TABLE
class KRITAIMAGE_EXPORT KisMementoManager
{
public:
KisMementoManager();
KisMementoManager(const KisMementoManager& rhs);
~KisMementoManager();
/**
* Most tricky part. This function is called by a tile, when it gets new
* tile-data through COW. The Memento Manager wraps this tile-data into
* KisMementoItem class and waits until commit() order given. By this
* time KisMementoItem doesn't take part in COW mechanism. It only holds
* tileData->m_refCount counter to ensure tile isn't deleted from memory.
* When commit() comes, KisMementoItem grabs tileData->m_usersCount and
* since that moment it is a rightful co-owner of the tileData and COW
* participant. It means that tileData won't be ever changed since then.
* Every write request to the original tile will lead to duplicating
* tileData and registering it here again...
*/
void registerTileChange(KisTile *tile);
/**
* Called when a tile deleted. Creates empty KisMementoItem showing that
* there was a tile one day
*/
void registerTileDeleted(KisTile *tile);
/**
* Commits changes, made in INDEX: appends m_index into m_revisions list
* and owes all modified tileDatas.
*/
void commit();
/**
* Undo and Redo stuff respectively.
*
* When calling them, INDEX list should be empty, so to say, "working
* copy should be clean".
*/
- void rollback(KisTileHashTable *ht);
- void rollforward(KisTileHashTable *ht);
+ void rollback(KisTileHashTable *ht, KisMementoSP memento);
+ void rollforward(KisTileHashTable *ht, KisMementoSP memento);
/**
* Get old tile, whose memento is in the HEAD revision.
* \p existingTile returns if the tile is actually an existing
* non-default tile or it was created on the fly
* from the default tile data
*/
KisTileSP getCommitedTile(qint32 col, qint32 row, bool &existingTile);
KisMementoSP getMemento();
bool hasCurrentMemento() {
return m_currentMemento;
}
KisMementoSP currentMemento();
void setDefaultTileData(KisTileData *defaultTileData);
void debugPrintInfo();
/**
* Removes all the history that precedes the revision
* pointed by oldestMemento. That is after calling to
* purgeHistory(someMemento) you won't be able to do
* rollback(someMemento) anymore.
*/
void purgeHistory(KisMementoSP oldestMemento);
protected:
qint32 findRevisionByMemento(KisMementoSP memento) const;
void resetRevisionHistory(KisMementoItemList list);
protected:
/**
* INDEX of tiles to be committed with next commit()
* We use a hash table to be able to check that
* we have the only memento item for a tile
* per commit efficiently
*/
KisMementoItemHashTable m_index;
/**
* Main list that stores every commit ever done
*/
KisHistoryList m_revisions;
/**
* List of revisions temporarily undone while rollback()
*/
KisHistoryList m_cancelledRevisions;
/**
* A hash table, that stores the most recently updated
* versions of tiles. Say, HEAD revision :)
*/
KisMementoItemHashTable m_headsHashTable;
/**
* Stores extent of current INDEX.
* It is the "name" of current named transaction
*/
KisMementoSP m_currentMemento;
/**
* The flag that blocks registration of changes on tiles.
* This is a temporary state of the memento manager, that
* is used for traveling in history
*
* \see rollback()
* \see rollforward()
*/
bool m_registrationBlocked;
};
#endif /* KIS_MEMENTO_MANAGER_ */
diff --git a/libs/image/tiles3/kis_tiled_data_manager.h b/libs/image/tiles3/kis_tiled_data_manager.h
index cbf22e348a..d77a534429 100644
--- a/libs/image/tiles3/kis_tiled_data_manager.h
+++ b/libs/image/tiles3/kis_tiled_data_manager.h
@@ -1,430 +1,430 @@
/*
* Copyright (c) 2004 Boudewijn Rempt
* (c) 2009 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_TILEDDATAMANAGER_H_
#define KIS_TILEDDATAMANAGER_H_
#include
#include
#include
#include
#include
#include "config-hash-table-implementaion.h"
//#include "kis_debug.h"
#include "kritaimage_export.h"
#ifdef USE_LOCK_FREE_HASH_TABLE
#include "kis_tile_hash_table2.h"
#else
#include "kis_tile_hash_table.h"
#endif // USE_LOCK_FREE_HASH_TABLE
#include "kis_memento_manager.h"
#include "kis_memento.h"
#include "KisTiledExtentManager.h"
class KisTiledDataManager;
typedef KisSharedPtr KisTiledDataManagerSP;
class KisTiledIterator;
class KisTiledRandomAccessor;
class KisPaintDeviceWriter;
class QIODevice;
/**
* KisTiledDataManager implements the interface that KisDataManager defines
*
* The interface definition is enforced by KisDataManager calling all the methods
* which must also be defined in KisTiledDataManager. It is not allowed to change the interface
* as other datamangers may also rely on the same interface.
*
* * Storing undo/redo data
* * Offering ordered and unordered iterators over rects of pixels
* * (eventually) efficiently loading and saving data in a format
* that may allow deferred loading.
*
* A datamanager knows nothing about the type of pixel data except
* how many quint8's a single pixel takes.
*/
class KRITAIMAGE_EXPORT KisTiledDataManager : public KisShared
{
private:
static const qint32 LEGACY_VERSION = 1;
static const qint32 CURRENT_VERSION = 2;
protected:
/*FIXME:*/
public:
KisTiledDataManager(quint32 pixelSize, const quint8 *defPixel);
virtual ~KisTiledDataManager();
KisTiledDataManager(const KisTiledDataManager &dm);
KisTiledDataManager & operator=(const KisTiledDataManager &dm);
protected:
// Allow the baseclass of iterators access to the interior
// derived iterator classes must go through KisTiledIterator
friend class KisTiledIterator;
friend class KisBaseIterator;
friend class KisTiledRandomAccessor;
friend class KisRandomAccessor2;
friend class KisStressJob;
public:
void setDefaultPixel(const quint8 *defPixel);
const quint8 *defaultPixel() const {
return m_defaultPixel;
}
/**
* Every iterator fetches both types of tiles all the time: old and new.
* For projection devices these tiles are **always** the same, but doing
* two distinct calls makes double pressure on the read-write lock in the
* hash table.
*
* Merging two calls into one allows us to avoid additional tile fetch from
* the hash table and therefore reduce waiting time.
*/
inline void getTilesPair(qint32 col, qint32 row, bool writable, KisTileSP *tile, KisTileSP *oldTile) {
*tile = getTile(col, row, writable);
bool unused;
*oldTile = m_mementoManager->getCommitedTile(col, row, unused);
if (!*oldTile) {
*oldTile = *tile;
}
}
inline KisTileSP getTile(qint32 col, qint32 row, bool writable) {
if (writable) {
bool newTile;
KisTileSP tile = m_hashTable->getTileLazy(col, row, newTile);
if (newTile) {
m_extentManager.notifyTileAdded(col, row);
}
return tile;
} else {
bool unused;
return m_hashTable->getReadOnlyTileLazy(col, row, unused);
}
}
inline KisTileSP getReadOnlyTileLazy(qint32 col, qint32 row, bool &existingTile) {
return m_hashTable->getReadOnlyTileLazy(col, row, existingTile);
}
inline KisTileSP getOldTile(qint32 col, qint32 row, bool &existingTile) {
KisTileSP tile = m_mementoManager->getCommitedTile(col, row, existingTile);
return tile ? tile : getReadOnlyTileLazy(col, row, existingTile);
}
inline KisTileSP getOldTile(qint32 col, qint32 row) {
bool unused;
return getOldTile(col, row, unused);
}
KisMementoSP getMemento() {
QWriteLocker locker(&m_lock);
KisMementoSP memento = m_mementoManager->getMemento();
memento->saveOldDefaultPixel(m_defaultPixel, m_pixelSize);
return memento;
}
/**
* Finishes having already started transaction
*/
void commit() {
QWriteLocker locker(&m_lock);
KisMementoSP memento = m_mementoManager->currentMemento();
if(memento) {
memento->saveNewDefaultPixel(m_defaultPixel, m_pixelSize);
}
m_mementoManager->commit();
}
void rollback(KisMementoSP memento) {
commit();
QWriteLocker locker(&m_lock);
- m_mementoManager->rollback(m_hashTable);
+ m_mementoManager->rollback(m_hashTable, memento);
const quint8 *defaultPixel = memento->oldDefaultPixel();
if(memcmp(m_defaultPixel, defaultPixel, m_pixelSize)) {
setDefaultPixelImpl(defaultPixel);
}
recalculateExtent();
}
void rollforward(KisMementoSP memento) {
commit();
QWriteLocker locker(&m_lock);
- m_mementoManager->rollforward(m_hashTable);
+ m_mementoManager->rollforward(m_hashTable, memento);
const quint8 *defaultPixel = memento->newDefaultPixel();
if(memcmp(m_defaultPixel, defaultPixel, m_pixelSize)) {
setDefaultPixelImpl(defaultPixel);
}
recalculateExtent();
}
bool hasCurrentMemento() const {
return m_mementoManager->hasCurrentMemento();
//return true;
}
/**
* Removes all the history that preceds the revision
* pointed by oldestMemento. That is after calling to
* purgeHistory(someMemento) you won't be able to do
* rollback(someMemento) anymore.
*/
void purgeHistory(KisMementoSP oldestMemento) {
QWriteLocker locker(&m_lock);
m_mementoManager->purgeHistory(oldestMemento);
}
static void releaseInternalPools();
protected:
/**
* Reads and writes the tiles
*/
bool write(KisPaintDeviceWriter &store);
bool read(QIODevice *stream);
void purge(const QRect& area);
inline quint32 pixelSize() const {
return m_pixelSize;
}
/* FIXME:*/
public:
void extent(qint32 &x, qint32 &y, qint32 &w, qint32 &h) const;
void setExtent(qint32 x, qint32 y, qint32 w, qint32 h);
QRect extent() const;
void setExtent(QRect newRect);
KisRegion region() const;
void clear(QRect clearRect, quint8 clearValue);
void clear(QRect clearRect, const quint8 *clearPixel);
void clear(qint32 x, qint32 y, qint32 w, qint32 h, quint8 clearValue);
void clear(qint32 x, qint32 y, qint32 w, qint32 h, const quint8 *clearPixel);
void clear();
/**
* Clones rect from another datamanager. The cloned area will be
* shared between both datamanagers as much as possible using
* copy-on-write. Parts of the rect that cannot be shared
* (cross tiles) are deep-copied,
*/
void bitBlt(KisTiledDataManager *srcDM, const QRect &rect);
/**
* The same as \ref bitBlt(), but reads old data
*/
void bitBltOldData(KisTiledDataManager *srcDM, const QRect &rect);
/**
* Clones rect from another datamanager in a rough and fast way.
* All the tiles touched by rect will be shared, between both
* managers, 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.
*/
void bitBltRough(KisTiledDataManager *srcDM, const QRect &rect);
/**
* The same as \ref bitBltRough(), but reads old data
*/
void bitBltRoughOldData(KisTiledDataManager *srcDM, const QRect &rect);
/**
* write the specified data to x, y. There is no checking on pixelSize!
*/
void setPixel(qint32 x, qint32 y, const quint8 * data);
/**
* Copy the bytes in the specified rect to a vector. The caller is responsible
* for managing the vector.
*
* \param bytes the bytes
* \param x x of top left corner
* \param y y of top left corner
* \param w width
* \param h height
* \param dataRowStride is the step (in bytes) which should be
* added to \p bytes pointer to get to the
* next row
*/
void readBytes(quint8 * bytes,
qint32 x, qint32 y,
qint32 w, qint32 h,
qint32 dataRowStride = -1) const;
/**
* Copy the bytes in the vector to the specified rect. If there are bytes left
* in the vector after filling the rect, they will be ignored. If there are
* not enough bytes, the rest of the rect will be filled with the default value
* given (by default, 0);
*
* \param bytes the bytes
* \param x x of top left corner
* \param y y of top left corner
* \param w width
* \param h height
* \param dataRowStride is the step (in bytes) which should be
* added to \p bytes pointer to get to the
* next row
*/
void writeBytes(const quint8 * bytes,
qint32 x, qint32 y,
qint32 w, qint32 h,
qint32 dataRowStride = -1);
/**
* 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(QVector channelsizes, qint32 x, qint32 y, qint32 w, qint32 h) const;
/**
* Write the data in the separate arrays to the channels. 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.
*/
void writePlanarBytes(QVector planes, QVector channelsizes, qint32 x, qint32 y, qint32 w, qint32 h);
/**
* Get the number of contiguous columns starting at x, valid for all values
* of y between minY and maxY.
*/
qint32 numContiguousColumns(qint32 x, qint32 minY, qint32 maxY) const;
/**
* Get the number of contiguous rows starting at y, valid for all values
* of x between minX and maxX.
*/
qint32 numContiguousRows(qint32 y, qint32 minX, qint32 maxX) const;
/**
* Get the row stride at pixel (x, y). This is the number of bytes to add to a
* pointer to pixel (x, y) to access (x, y + 1).
*/
qint32 rowStride(qint32 x, qint32 y) const;
private:
KisTileHashTable *m_hashTable;
KisMementoManager *m_mementoManager;
quint8* m_defaultPixel;
qint32 m_pixelSize;
KisTiledExtentManager m_extentManager;
mutable QReadWriteLock m_lock;
private:
// Allow compression routines to calculate (col,row) coordinates
// and pixel size
friend class KisAbstractTileCompressor;
friend class KisTileDataWrapper;
qint32 xToCol(qint32 x) const;
qint32 yToRow(qint32 y) const;
private:
void setDefaultPixelImpl(const quint8 *defPixel);
bool writeTilesHeader(KisPaintDeviceWriter &store, quint32 numTiles);
bool processTilesHeader(QIODevice *stream, quint32 &numTiles);
qint32 divideRoundDown(qint32 x, const qint32 y) const;
void recalculateExtent();
quint8* duplicatePixel(qint32 num, const quint8 *pixel);
template
void bitBltImpl(KisTiledDataManager *srcDM, const QRect &rect);
template
void bitBltRoughImpl(KisTiledDataManager *srcDM, const QRect &rect);
void writeBytesBody(const quint8 *data,
qint32 x, qint32 y,
qint32 width, qint32 height,
qint32 dataRowStride = -1);
void readBytesBody(quint8 *data,
qint32 x, qint32 y,
qint32 width, qint32 height,
qint32 dataRowStride = -1) const;
template
void writePlanarBytesBody(QVector planes,
QVector channelsizes,
qint32 x, qint32 y, qint32 w, qint32 h);
QVector readPlanarBytesBody(QVector channelsizes,
qint32 x, qint32 y,
qint32 w, qint32 h) const;
public:
void debugPrintInfo() {
m_mementoManager->debugPrintInfo();
}
};
inline qint32 KisTiledDataManager::divideRoundDown(qint32 x, const qint32 y) const
{
/**
* Equivalent to the following:
* -(( -x + (y-1) ) / y)
*/
return x >= 0 ?
x / y :
-(((-x - 1) / y) + 1);
}
inline qint32 KisTiledDataManager::xToCol(qint32 x) const
{
return divideRoundDown(x, KisTileData::WIDTH);
}
inline qint32 KisTiledDataManager::yToRow(qint32 y) const
{
return divideRoundDown(y, KisTileData::HEIGHT);
}
// during development the following line helps to check the interface is correct
// it should be safe to keep it here even during normal compilation
//#include "kis_datamanager.h"
#endif // KIS_TILEDDATAMANAGER_H_
diff --git a/libs/libkis/Node.cpp b/libs/libkis/Node.cpp
index 2118b76489..3f92eb5571 100644
--- a/libs/libkis/Node.cpp
+++ b/libs/libkis/Node.cpp
@@ -1,676 +1,680 @@
/*
* 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 "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.isNull()) {
+ return 0;
+ }
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);
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;
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::setPinnedToTimeline(bool pinned) const
{
if (!d->node) return;
d->node->setPinnedToTimeline(pinned);
}
bool Node::isPinnedToTimeline() const
{
if (!d->node) return false;
return d->node->isPinnedToTimeline();
}
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;
+ if (!d->node->parent()) 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/pigment/compositeops/KoCompositeOpCopy.h b/libs/pigment/compositeops/KoCompositeOpCopy.h
deleted file mode 100644
index 0b0016fdfc..0000000000
--- a/libs/pigment/compositeops/KoCompositeOpCopy.h
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (c) 2006 Cyrille Berger
- * Copyright (c) 2007 Emanuele Tamponi
- * Copyright (c) 2010 Lukáš Tvrdý
- *
- * This library 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.1 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser 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.
-*/
-
-#ifndef KO_COMPOSITE_OP_COPY_H
-#define KO_COMPOSITE_OP_COPY_H
-
-/**
- * Generic implementation of the COPY composite op.
- * Used automatically by all colorspaces that derive from KoColorSpaceAbstract.
- */
-class KoCompositeOpCopy : public KoCompositeOp
-{
-
- using KoCompositeOp::composite;
-
-public:
-
- explicit KoCompositeOpCopy(KoColorSpace * cs)
- : KoCompositeOp(cs, COMPOSITE_COPY, i18n("Copy"), KoCompositeOp::categoryMix()) {
- }
-
-public:
-
- void composite(quint8 *dstRowStart,
- qint32 dstRowStride,
- const quint8 *srcRowStart,
- qint32 srcRowStride,
- const quint8 *maskRowStart,
- qint32 maskRowStride,
- qint32 rows,
- qint32 numColumns,
- quint8 opacity,
- const QBitArray & channelFlags) const {
-
- Q_UNUSED(channelFlags);
- Q_UNUSED(opacity);
-
- const KoColorSpace* cs = colorSpace();
- qint32 bytesPerPixel = cs->pixelSize();
-
- qint32 srcInc = (srcRowStride == 0) ? 0 : bytesPerPixel;
-
- quint8 *dst = dstRowStart;
- const quint8 *src = srcRowStart;
- const quint8 *mask = maskRowStart;
-
- if (maskRowStart != 0){
- while (rows > 0) {
- quint8* dstN = dst;
- const quint8* srcN = src;
- const quint8* maskN = mask;
- qint32 columns = numColumns;
-
- while (columns > 0) {
- if (*maskN != 0){
- memcpy(dstN, srcN, bytesPerPixel);
- }
-
- dstN += bytesPerPixel;
- srcN += srcInc; // if srcRowStride == 0, don't move the pixel
- maskN++; // byte
- columns--;
- }
-
- dst += dstRowStride;
- src += srcRowStride;
- mask += maskRowStride;
- --rows;
- }
- }
- else {
- while (rows > 0) {
- if (srcInc == 0) {
- quint8* dstN = dst;
- qint32 columns = numColumns;
- while (columns > 0) {
- memcpy(dstN, src, bytesPerPixel);
- dstN += bytesPerPixel;
- columns--;
- }
- } else {
- memcpy(dst, src, numColumns * bytesPerPixel);
- }
-
- // XXX: what is the reason for this code? I think we should copy the alpha channel as well.
- //if (opacity != OPACITY_OPAQUE) {
- // cs->multiplyAlpha(dst, opacity, numColumns);
- //}
-
- dst += dstRowStride;
- src += srcRowStride;
- --rows;
- }
- }
- }
-};
-
-#endif
diff --git a/libs/pigment/resources/KisSwatchGroup.h b/libs/pigment/resources/KisSwatchGroup.h
index 7794938449..66dac18d9f 100644
--- a/libs/pigment/resources/KisSwatchGroup.h
+++ b/libs/pigment/resources/KisSwatchGroup.h
@@ -1,127 +1,131 @@
/* This file is part of the KDE project
Copyright (c) 2005 Boudewijn Rempt
Copyright (c) 2016 L. E. Segovia
Copyright (c) 2018 Michael Zhou
This library 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.1 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef KISSWATCHGROUP_H
#define KISSWATCHGROUP_H
#include "KisSwatch.h"
#include "kritapigment_export.h"
#include
#include
#include
#include
/**
* @brief The KisSwatchGroup class stores a matrix of color swatches
* swatches can accessed using (x, y) coordinates.
* x is the column number from left to right and y is the row number from top
* to bottom.
* Both x and y start at 0
* there could be empty entries, so the checkEntry(int, int) method must used
* whenever you want to get an entry from the matrix
*/
class KRITAPIGMENT_EXPORT KisSwatchGroup
{
public /* struct */:
struct SwatchInfo {
QString group;
KisSwatch swatch;
int row;
int column;
};
public:
KisSwatchGroup();
~KisSwatchGroup();
KisSwatchGroup(const KisSwatchGroup &rhs);
KisSwatchGroup &operator =(const KisSwatchGroup &rhs);
public /* methods */:
void setName(const QString &name);
QString name() const;
void setColumnCount(int columnCount);
int columnCount() const;
void setRowCount(int newRowCount);
int rowCount() const;
int colorCount() const;
+ /**
+ * @brief getColors
+ * @return the list of colors in this SwatchGroup, in no specific order.
+ */
QList infoList() const;
/**
* @brief checkEntry
* checks if position @p column and @p row has a valid entry
* both @p column and @p row start from 0
* @param column
* @param row
* @return true if there is a valid entry at position (column, row)
*/
bool checkEntry(int column, int row) const;
/**
* @brief setEntry
* sets the entry at position (@p column, @p row) to be @p e
* @param e
* @param column
* @param row
*/
void setEntry(const KisSwatch &e, int column, int row);
/**
* @brief getEntry
* used to get the swatch entry at position (@p column, @p row)
* there is an assertion to make sure that this position isn't empty,
* so checkEntry(int, int) must be used before this method to ensure
* a valid entry can be found
* @param column
* @param row
* @return the swatch entry at position (column, row)
*/
KisSwatch getEntry(int column, int row) const;
/**
* @brief removeEntry
* removes the entry at position (@p column, @p row)
* @param column
* @param row
* @return true if these is an entry at (column, row)
*/
bool removeEntry(int column, int row);
/**
* @brief addEntry
* adds the entry e to the right of the rightmost entry in the last row
* if the rightmost entry in the last row is in the right most column,
* add e to the leftmost column of a new row
*
* when column is set to 0, resize number of columns to default
* @param e
*/
void addEntry(const KisSwatch &e);
void clear();
private /* member variables */:
struct Private;
QScopedPointer d;
};
#endif // KISSWATCHGROUP_H
diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp
index 2a83c77533..73a6a804b2 100644
--- a/libs/ui/KisDocument.cpp
+++ b/libs/ui/KisDocument.cpp
@@ -1,2338 +1,2344 @@
/* 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