Merge branch 'master' into tilemap-editor

This commit is contained in:
David Capello 2020-05-18 20:24:22 -03:00
commit a80af2b304
274 changed files with 6254 additions and 2571 deletions

3
.gitmodules vendored
View File

@ -63,3 +63,6 @@
[submodule "third_party/lua"]
path = third_party/lua
url = https://github.com/aseprite/lua
[submodule "src/tga"]
path = src/tga
url = https://github.com/aseprite/tga.git

View File

@ -11,7 +11,7 @@ matrix:
addons:
apt:
packages:
- libpixman-1-dev libfreetype6-dev libharfbuzz-dev libx11-dev libxcursor-dev ninja-build
- libpixman-1-dev libfreetype6-dev libharfbuzz-dev libx11-dev libxcursor-dev libxi-dev ninja-build
env:
- ENABLE_UI=OFF
- XVFB=xvfb-run
@ -19,7 +19,7 @@ matrix:
addons:
apt:
packages:
- libpixman-1-dev libfreetype6-dev libharfbuzz-dev libx11-dev libxcursor-dev ninja-build
- libpixman-1-dev libfreetype6-dev libharfbuzz-dev libx11-dev libxcursor-dev libxi-dev ninja-build
env:
- ENABLE_SCRIPTING=OFF
- XVFB=xvfb-run
@ -27,7 +27,7 @@ matrix:
addons:
apt:
packages:
- libpixman-1-dev libfreetype6-dev libharfbuzz-dev libx11-dev libxcursor-dev ninja-build
- libpixman-1-dev libfreetype6-dev libharfbuzz-dev libx11-dev libxcursor-dev libxi-dev ninja-build
env:
- ENABLE_SCRIPTING=OFF
- ENABLE_UI=OFF
@ -37,7 +37,7 @@ matrix:
addons:
apt:
packages:
- libpixman-1-dev libfreetype6-dev libharfbuzz-dev libx11-dev libxcursor-dev ninja-build
- libpixman-1-dev libfreetype6-dev libharfbuzz-dev libx11-dev libxcursor-dev libxi-dev ninja-build
env:
- ENABLE_UI=ON
- XVFB=xvfb-run
@ -47,7 +47,7 @@ matrix:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-7 libpixman-1-dev libfreetype6-dev libharfbuzz-dev libx11-dev libxcursor-dev ninja-build
- g++-7 libpixman-1-dev libfreetype6-dev libharfbuzz-dev libx11-dev libxcursor-dev libxi-dev ninja-build
env:
- MATRIX_EVAL="CC=gcc-7 && CXX=g++-7"
- ENABLE_UI=ON

View File

@ -219,7 +219,7 @@ add_definitions(-DPNG_NO_MMX_CODE) # Do not use MMX optimizations in PNG code
# libwebp
if(WITH_WEBP_SUPPORT)
set(WEBP_LIBRARIES webp webpdemux webpmux)
set(WEBP_LIBRARIES webp)
set(WEBP_INCLUDE_DIR ${LIBWEBP_DIR}/src)
include_directories(${WEBP_INCLUDE_DIR})
endif()

View File

@ -72,11 +72,11 @@ versions might work).
You will need the following dependencies on Ubuntu/Debian:
sudo apt-get install -y g++ cmake ninja-build libx11-dev libxcursor-dev libgl1-mesa-dev libfontconfig1-dev
sudo apt-get install -y g++ cmake ninja-build libx11-dev libxcursor-dev libxi-dev libgl1-mesa-dev libfontconfig1-dev
On Fedora:
sudo dnf install -y gcc-c++ cmake ninja-build libX11-devel libXcursor-devel mesa-libGL-devel fontconfig-devel
sudo dnf install -y gcc-c++ cmake ninja-build libX11-devel libXcursor-devel libXi-devel mesa-libGL-devel fontconfig-devel
# Compiling

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -418,6 +418,7 @@
<part id="outline_vertical" x="192" y="224" w="13" h="15" />
<part id="outline_empty_pixel" x="208" y="224" w="5" h="5" />
<part id="outline_full_pixel" x="214" y="224" w="5" h="5" />
<part id="dynamics" x="176" y="144" w="16" h="16" />
<part id="tiles" x="144" y="208" w="6" h="6" />
<part id="tiles_manual" x="150" y="208" w="6" h="6" />
<part id="tiles_auto" x="156" y="208" w="6" h="6" />
@ -961,5 +962,16 @@
<icon part="aseprite_face_mouse" state="mouse" />
<icon part="aseprite_face_pushed" state="mouse selected" />
</style>
<style id="slider" border-top="4" border-bottom="5">
<background part="slider_empty" />
<background part="slider_empty_focused" state="focus" />
<text color="slider_empty_text" align="center middle" />
<text color="slider_empty_text" align="center middle" state="focus" y="1" />
</style>
<style id="mini_slider" extends="slider" font="mini" padding-top="1" padding-bottom="3">
<background part="mini_slider_empty" />
<text color="slider_empty_text" align="center middle" />
<text color="slider_empty_text" align="center middle" state="focus" />
</style>
</styles>
</theme>

View File

@ -629,56 +629,56 @@
<!-- main bar menu -->
<menu id="main_menu">
<menu text="@.file">
<item command="NewFile" text="@.file_new" />
<item command="OpenFile" text="@.file_open" />
<menu text="@.file_open_recent">
<item command="ReopenClosedFile" text="@.file_reopen_closed" />
<separator id="recent_files_placeholder" />
<item command="NewFile" text="@.file_new" group="file_new" />
<item command="OpenFile" text="@.file_open" group="file_open" />
<menu text="@.file_open_recent" group="file_recent">
<item command="ReopenClosedFile" text="@.file_reopen_closed" group="file_recent_reopen" />
<separator id="recent_files_placeholder" group="file_recent_list" />
<separator />
<item command="ClearRecentFiles" text="@.file_clear_recent_files" />
<item command="ClearRecentFiles" text="@.file_clear_recent_files" group="file_recent_clear" />
</menu>
<separator />
<item command="SaveFile" text="@.file_save" />
<item command="SaveFileAs" text="@.file_save_as" />
<item command="SaveFileCopyAs" text="@.file_export" />
<item command="SaveFileCopyAs" text="@.file_export" group="file_save" />
<item command="CloseFile" text="@.file_close" />
<item command="CloseAllFiles" text="@.file_close_all" />
<item command="CloseAllFiles" text="@.file_close_all" group="file_close" />
<separator />
<item command="ImportSpriteSheet" text="@.file_import_sprite_sheet" />
<item command="ExportSpriteSheet" text="@.file_export_sprite_sheet" />
<item command="ImportSpriteSheet" text="@.file_import_sprite_sheet" group="file_import" />
<item command="ExportSpriteSheet" text="@.file_export_sprite_sheet" group="file_export" />
<item command="RepeatLastExport" text="@.file_repeat_last_export" />
<separator id="scripts_menu_separator" />
<menu id="scripts_menu" text="@.file_scripts">
<menu id="scripts_menu" text="@.file_scripts" group="file_scripts">
<item command="OpenScriptFolder" text="@.file_open_script_folder" />
</menu>
<separator />
<separator group="file_app" />
<item command="Exit" text="@.file_exit" />
</menu>
<menu text="@.edit">
<item command="Undo" text="@.edit_undo" />
<item command="Redo" text="@.edit_redo" />
<item command="UndoHistory" text="@.edit_undo_history" />
<separator />
<item command="UndoHistory" text="@.edit_undo_history" group="edit_undo" />
<separator group="edit_clipboard" />
<item command="Cut" text="@.edit_cut" />
<item command="Copy" text="@.edit_copy" />
<item command="CopyMerged" text="@.edit_copy_merged" />
<item command="CopyMerged" text="@.edit_copy_merged" group="edit_copy" />
<item command="Paste" text="@.edit_paste" />
<menu text="@.edit_paste_special">
<menu text="@.edit_paste_special" group="edit_paste">
<item command="NewFile" text="@.edit_paste_special_new_sprite">
<param name="fromClipboard" value="true" />
</item>
<item command="NewLayer" text="@.edit_paste_special_new_layer">
<param name="fromClipboard" value="true" />
</item>
<item command="NewLayer" text="@.edit_paste_special_new_ref_layer">
<item command="NewLayer" text="@.edit_paste_special_new_ref_layer" group="edit_paste_special_new">
<param name="reference" value="true" />
<param name="fromClipboard" value="true" />
</item>
</menu>
<item command="Clear" text="@.edit_clear" />
<item command="Clear" text="@.edit_clear" group="edit_clear" />
<separator />
<item command="Fill" text="@.edit_fill" />
<item command="Stroke" text="@.edit_stroke" />
<item command="Stroke" text="@.edit_stroke" group="edit_fill" />
<separator />
<menu text="@.edit_rotate">
<item command="Rotate" text="@.edit_rotate_180">
@ -703,7 +703,7 @@
<param name="orientation" value="vertical" />
</item>
<item command="MaskContent" text="@.edit_transform" />
<menu text="@.edit_shift">
<menu text="@.edit_shift" group="edit_transform">
<item command="MoveMask" text="@.edit_shift_left">
<param name="target" value="content" />
<param name="direction" value="left" />
@ -735,7 +735,7 @@
</menu>
<separator />
<item command="NewBrush" text="@.edit_new_brush" />
<item command="NewSpriteFromSelection" text="@.edit_new_sprite_from_selection" />
<item command="NewSpriteFromSelection" text="@.edit_new_sprite_from_selection" group="edit_new" />
<separator />
<item command="ReplaceColor" text="@.edit_replace_color" />
<item command="InvertColor" text="@.edit_invert_color" />
@ -747,16 +747,16 @@
<menu text="@.edit_fx" id="fx_popup_menu">
<item command="Outline" text="@.edit_fx_outline" />
<item command="ConvolutionMatrix" text="@.edit_fx_convolution_matrix" />
<item command="Despeckle" text="@.edit_fx_despeckle" />
<item command="Despeckle" text="@.edit_fx_despeckle" group="edit_fx" />
</menu>
<item command="PasteText" text="@.edit_insert_text" />
<item command="PasteText" text="@.edit_insert_text" group="edit_insert" />
<separator />
<item command="KeyboardShortcuts" text="@.edit_keyboard_shortcuts" />
<item command="Options" text="@.edit_preferences" />
</menu>
<menu text="@.sprite">
<item command="SpriteProperties" text="@.sprite_properties" />
<menu text="@.sprite_color_mode">
<item command="SpriteProperties" text="@.sprite_properties" group="sprite_properties" />
<menu text="@.sprite_color_mode" group="sprite_color">
<item command="ChangePixelFormat" text="@.sprite_color_mode_rgb">
<param name="format" value="rgb" />
</item>
@ -770,10 +770,10 @@
<item command="ChangePixelFormat" text="@.sprite_color_mode_more_options" />
</menu>
<separator />
<item command="DuplicateSprite" text="@.sprite_duplicate" />
<item command="DuplicateSprite" text="@.sprite_duplicate" group="sprite_duplicate" />
<separator />
<item command="SpriteSize" text="@.sprite_sprite_size" />
<item command="CanvasSize" text="@.sprite_canvas_size" />
<item command="CanvasSize" text="@.sprite_canvas_size" group="sprite_size" />
<menu text="@.sprite_rotate_canvas">
<item command="Rotate" text="@.sprite_rotate_180">
<param name="target" value="canvas" />
@ -792,20 +792,20 @@
<param name="target" value="canvas" />
<param name="orientation" value="horizontal" />
</item>
<item command="Flip" text="@.sprite_flip_canvas_vertical">
<item command="Flip" text="@.sprite_flip_canvas_vertical" group="sprite_transformation">
<param name="target" value="canvas" />
<param name="orientation" value="vertical" />
</item>
</menu>
<separator />
<item command="CropSprite" text="@.sprite_crop" />
<item command="AutocropSprite" text="@.sprite_trim" />
<item command="AutocropSprite" text="@.sprite_trim" group="sprite_crop" />
</menu>
<menu text="@.layer">
<item command="LayerProperties" text="@.layer_properties" />
<item command="LayerVisibility" text="@.layer_visible" />
<item command="LayerLock" text="@.layer_lock_layers" />
<item command="OpenGroup" text="@.layer_open_group" />
<item command="OpenGroup" text="@.layer_open_group" group="layer_properties" />
<separator />
<menu text="@.layer_new">
<item command="NewLayer" text="@.layer_new_layer" />
@ -820,7 +820,7 @@
<param name="viaCut" value="true" />
</item>
<separator />
<item command="NewLayer" text="@.layer_new_reference_layer_from_file">
<item command="NewLayer" text="@.layer_new_reference_layer_from_file" group="layer_new">
<param name="reference" value="true" />
<param name="fromFile" value="true" />
</item>
@ -829,14 +829,14 @@
<param name="ask" value="true" />
</item>
</menu>
<item command="RemoveLayer" text="@.layer_delete_layer" />
<item command="RemoveLayer" text="@.layer_delete_layer" group="layer_remove" />
<item command="BackgroundFromLayer" text="@.layer_background_from_layer" />
<item command="LayerFromBackground" text="@.layer_layer_from_background" />
<item command="LayerFromBackground" text="@.layer_layer_from_background" group="layer_background" />
<separator />
<item command="DuplicateLayer" text="@.layer_duplicate" />
<item command="DuplicateLayer" text="@.layer_duplicate" group="layer_duplicate" />
<item command="MergeDownLayer" text="@.layer_merge_down" />
<item command="FlattenLayers" text="@.layer_flatten" />
<item command="FlattenLayers" text="@.layer_flatten_visible">
<item command="FlattenLayers" text="@.layer_flatten_visible" group="layer_merge">
<param name="visibleOnly" value="true" />
</item>
</menu>
@ -844,7 +844,7 @@
<item command="FrameProperties" text="@.frame_properties">
<param name="frame" value="current" />
</item>
<item command="CelProperties" text="@.frame_cel_properties" />
<item command="CelProperties" text="@.frame_cel_properties" group="cel_properties" />
<separator />
<item command="NewFrame" text="@.frame_new_frame" />
<item command="NewFrame" text="@.frame_new_empty_frame">
@ -853,10 +853,10 @@
<item command="NewFrame" text="@.frame_duplicate_cels">
<param name="content" value="celcopies" />
</item>
<item command="NewFrame" text="@.frame_duplicate_linked_cels">
<item command="NewFrame" text="@.frame_duplicate_linked_cels" group="cel_new">
<param name="content" value="cellinked" />
</item>
<item command="RemoveFrame" text="@.frame_delete_frame" />
<item command="RemoveFrame" text="@.frame_delete_frame" group="cel_delete" />
<separator />
<menu text="@.frame_tags">
<item command="FrameTagProperties" text="@.frame_tags_tag_properties" />
@ -875,21 +875,21 @@
<separator />
<item command="GotoFrame" text="@.frame_go_to_frame" />
</menu>
<item command="PlayAnimation" text="@.frame_play_animation" />
<item command="PlayAnimation" text="@.frame_play_animation" group="cel_animation" />
<separator />
<item command="FrameProperties" text="@.frame_constant_frame_rate">
<param name="frame" value="all" />
</item>
<item command="ReverseFrames" text="@.frame_reverse_frames" />
<item command="ReverseFrames" text="@.frame_reverse_frames" group="cel_frames" />
</menu>
<menu text="@.select">
<item command="MaskAll" text="@.select_all" />
<item command="DeselectMask" text="@.select_deselect" />
<item command="ReselectMask" text="@.select_reselect" />
<item command="InvertMask" text="@.select_inverse" />
<item command="InvertMask" text="@.select_inverse" group="select_simple" />
<separator />
<item command="MaskByColor" text="@.select_color_range" />
<menu text="@.select_modify">
<menu text="@.select_modify" group="select_complex">
<item command="ModifySelection" text="@.select_modify_border">
<param name="modifier" value="border" />
</item>
@ -902,13 +902,13 @@
</menu>
<separator />
<item command="LoadMask" text="@.select_load_from_file" />
<item command="SaveMask" text="@.select_save_to_file" />
<item command="SaveMask" text="@.select_save_to_file" group="select_files" />
</menu>
<menu text="@.view">
<item command="DuplicateView" text="@.view_duplicate_view" />
<item command="DuplicateView" text="@.view_duplicate_view" group="view_new" />
<separator />
<item command="ShowExtras" text="@.view_show_extras" />
<menu text="@.view_show">
<menu text="@.view_show" group="view_extras">
<item command="ShowLayerEdges" text="@.view_show_layer_edges" />
<item command="ShowSelectionEdges" text="@.view_show_selection_edges" />
<item command="ShowGrid" text="@.view_show_grid" />
@ -938,10 +938,10 @@
<param name="axis" value="y" />
</item>
</menu>
<item command="SymmetryMode" text="@.view_symmetry_options" />
<item command="SymmetryMode" text="@.view_symmetry_options" group="view_canvas_helpers" />
<separator />
<item command="SetLoopSection" text="@.view_set_loop_section" />
<item command="ShowOnionSkin" text="@.view_show_onion_skin" />
<item command="ShowOnionSkin" text="@.view_show_onion_skin" group="view_animation_helpers" />
<separator />
<item command="Timeline" text="@.view_timeline">
<param name="switch" value="true" />
@ -949,12 +949,12 @@
<item command="TogglePreview" text="@.view_preview" />
<item command="AdvancedMode" text="@.view_full_screen_mode" />
<item command="FullscreenPreview" text="@.view_full_screen_preview" />
<item command="Home" text="@.view_home" />
<item command="Home" text="@.view_home" group="view_controls" />
<separator />
<item command="Refresh" text="@.view_refresh" />
<item command="Refresh" text="@.view_refresh" group="view_screen" />
</menu>
<menu text="@.help" id="help_menu">
<item command="OpenBrowser" text="@.help_readme">
<item command="OpenBrowser" text="@.help_readme" group="help_readme">
<param name="filename" value="README.md" />
</item>
<separator />
@ -966,7 +966,7 @@
<param name="type" value="url" />
<param name="path" value="/docs/" />
</item>
<item command="Launch" text="@.help_tutorial">
<item command="Launch" text="@.help_tutorial" group="help_docs">
<param name="type" value="url" />
<param name="path" value="/tutorial/" />
</item>
@ -975,48 +975,48 @@
<param name="type" value="url" />
<param name="path" value="/release-notes/" />
</item>
<item command="Launch" text="@.help_twitter">
<item command="Launch" text="@.help_twitter" group="help_news">
<param name="type" value="url" />
<param name="path" value="http://twitter.com/aseprite" />
</item>
<separator />
<item command="About" text="@.help_about" />
<item command="About" text="@.help_about" group="help_about" />
</menu>
</menu>
<menu id="tab_popup_menu">
<item command="CloseFile" text="@.close" />
<item command="CloseFile" text="@.close" group="tab_close" />
</menu>
<menu id="document_tab_popup_menu">
<item command="CloseFile" text="@tab_popup_menu.close" />
<item command="DuplicateView" text="@.duplicate_view" />
<item command="CloseFile" text="@tab_popup_menu.close" group="document_tab_close" />
<item command="DuplicateView" text="@.duplicate_view" group="document_tab_view" />
<separator />
<item command="OpenWithApp" text="@.open_with_os" />
<item command="OpenInFolder" text="@.open_in_folder" />
<item command="OpenInFolder" text="@.open_in_folder" group="document_tab_open" />
</menu>
<menu id="layer_popup_menu">
<item command="LayerProperties" text="@main_menu.layer_properties" />
<item command="LayerProperties" text="@main_menu.layer_properties" group="layer_popup_properties" />
<separator />
<item command="NewLayer" text="@main_menu.layer_new_layer" />
<item command="NewLayer" text="@main_menu.layer_new_group">
<item command="NewLayer" text="@main_menu.layer_new_group" group="layer_popup_new">
<param name="group" value="true" />
</item>
<item command="RemoveLayer" text="@main_menu.layer_delete_layer" />
<item command="BackgroundFromLayer" text="@main_menu.layer_background_from_layer" />
<item command="LayerFromBackground" text="@main_menu.layer_layer_from_background" />
<item command="LayerFromBackground" text="@main_menu.layer_layer_from_background" group="layer_popup_background" />
<separator />
<item command="DuplicateLayer" text="@main_menu.layer_duplicate" />
<item command="MergeDownLayer" text="@main_menu.layer_merge_down" />
<item command="FlattenLayers" text="@main_menu.layer_flatten" />
<item command="FlattenLayers" text="@main_menu.layer_flatten_visible">
<item command="FlattenLayers" text="@main_menu.layer_flatten_visible" group="layer_popup_merge">
<param name="visibleOnly" value="true" />
</item>
</menu>
<menu id="frame_popup_menu">
<item command="FrameProperties" text="@main_menu.frame_properties">
<item command="FrameProperties" text="@main_menu.frame_properties" group="frame_popup_properties">
<param name="frame" value="current" />
</item>
<separator />
@ -1024,25 +1024,25 @@
<item command="NewFrame" text="@main_menu.frame_new_empty_frame">
<param name="content" value="empty" />
</item>
<item command="NewFrameTag" text="@main_menu.frame_tags_new_tag" />
<item command="RemoveFrame" text="@main_menu.frame_delete_frame" />
<item command="NewFrameTag" text="@main_menu.frame_tags_new_tag" group="frame_popup_new" />
<item command="RemoveFrame" text="@main_menu.frame_delete_frame" group="frame_popup_delete" />
<separator />
<item command="SetLoopSection" text="@main_menu.view_set_loop_section" />
<item command="SetLoopSection" text="@main_menu.view_set_loop_section" group="frame_popup_loop" />
<separator />
<item command="ReverseFrames" text="@main_menu.frame_reverse_frames" />
<item command="ReverseFrames" text="@main_menu.frame_reverse_frames" group="frame_popup_reverse" />
</menu>
<menu id="cel_popup_menu">
<item command="CelProperties" text="@main_menu.frame_cel_properties" />
<separator />
<item command="ClearCel" text="@.clear" />
<item command="CelProperties" text="@main_menu.frame_cel_properties" group="cel_popup_properties" />
<separator group="cel_popup_edit" />
<item command="ClearCel" text="@.clear" group="cel_popup_clear" />
<item command="UnlinkCel" text="@.unlink" />
<item command="LinkCels" text="@.link_cels" />
<item command="LinkCels" text="@.link_cels" group="cel_popup_links" />
<separator />
<item command="NewFrame" text="@main_menu.frame_duplicate_cels">
<param name="content" value="celcopies" />
</item>
<item command="NewFrame" text="@main_menu.frame_duplicate_linked_cels">
<item command="NewFrame" text="@main_menu.frame_duplicate_linked_cels" group="cel_popup_new">
<param name="content" value="cellinked" />
</item>
</menu>
@ -1057,18 +1057,18 @@
</menu>
<menu id="tag_popup_menu">
<item command="FrameTagProperties" text="@main_menu.frame_tags_tag_properties" />
<item command="RemoveFrameTag" text="@main_menu.frame_tags_delete_tag" />
<item command="FrameTagProperties" text="@main_menu.frame_tags_tag_properties" group="tag_popup_properties" />
<item command="RemoveFrameTag" text="@main_menu.frame_tags_delete_tag" group="tag_popup_delete" />
</menu>
<menu id="slice_popup_menu">
<item command="SliceProperties" text="@.properties" />
<item command="RemoveSlice" text="@.delete" />
<item command="SliceProperties" text="@.properties" grou="slice_popup_properties" />
<item command="RemoveSlice" text="@.delete" group="slice_popup_delete" />
</menu>
<menu id="palette_popup_menu">
<item command="PaletteEditor" text="@.edit_palette" />
<item command="PaletteSize" text="@.palette_size" />
<item command="PaletteSize" text="@.palette_size" group="palette_main" />
<separator />
<item command="SetPaletteEntrySize" text="@.small_size">
<param name="size" value="7" />
@ -1076,7 +1076,7 @@
<item command="SetPaletteEntrySize" text="@.medium_size">
<param name="size" value="11" />
</item>
<item command="SetPaletteEntrySize" text="@.large_size">
<item command="SetPaletteEntrySize" text="@.large_size" group="palette_view">
<param name="size" value="17" />
</item>
<separator />
@ -1092,7 +1092,7 @@
<item command="SetColorSelector" text="@.ryb_color_wheel">
<param name="type" value="ryb-wheel" />
</item>
<item command="SetColorSelector" text="@.normal_map_color_wheel">
<item command="SetColorSelector" text="@.normal_map_color_wheel" group="palette_selector">
<param name="type" value="normal-map-wheel" />
</item>
<separator />
@ -1101,11 +1101,11 @@
<item command="LoadPalette" text="@.load_default_palette">
<param name="preset" value="default" />
</item>
<item command="SavePalette" text="@.save_as_default_palette">
<item command="SavePalette" text="@.save_as_default_palette" group="palette_files">
<param name="preset" value="default" />
</item>
<separator />
<item command="ColorQuantization" text="@.create_palette_from_current_sprite" />
<item command="ColorQuantization" text="@.create_palette_from_current_sprite" group="palette_generation" />
</menu>
<menu id="ink_popup_menu">
@ -1121,11 +1121,11 @@
<item command="SetInkType" text="@inks.lock_alpha">
<param name="type" value="lock-alpha" />
</item>
<item command="SetInkType" text="@inks.shading">
<item command="SetInkType" text="@inks.shading" group="ink_types">
<param name="type" value="shading" />
</item>
<separator />
<item command="SetSameInk" text="@.same_in_all_tools" />
<item command="SetSameInk" text="@.same_in_all_tools" group="ink_options" />
</menu>
</menus>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Aseprite -->
<!-- Copyright (C) 2018-2019 Igara Studio S.A. -->
<!-- Copyright (C) 2018-2020 Igara Studio S.A. -->
<!-- Copyright (C) 2014-2018 David Capello -->
<preferences>
@ -101,10 +101,6 @@
<value id="LEFT" value="1" />
<value id="RIGHT" value="2" />
</enum>
<enum id="HueSaturationMode">
<value id="HSV" value="0" />
<value id="HSL" value="1" />
</enum>
<enum id="ColorProfileBehavior">
<value id="DISABLE" value="0" />
<value id="EMBEDDED" value="1" />
@ -117,6 +113,12 @@
<value id="SRGB" value="1" />
<value id="SPECIFIC" value="2" />
</enum>
<enum id="ToGrayAlgorithm">
<value id="DEFAULT" value="0" />
<value id="LUMA" value="0" />
<value id="HSV" value="1" />
<value id="HSL" value="2" />
</enum>
</types>
<global>
@ -157,9 +159,14 @@
<option id="auto_scroll" type="bool" default="true" />
<option id="right_click_mode" type="RightClickMode" default="RightClickMode::PAINT_BGCOLOR" />
<option id="auto_select_layer" type="bool" default="false" />
<option id="auto_select_layer_quick" type="bool" default="true" />
<option id="straight_line_preview" type="bool" default="true" />
<option id="play_once" type="bool" default="false" />
<option id="play_all" type="bool" default="false" />
<!-- TODO this would be nice to be "true" but we have to fix
some performance issue rendering huge sprites with small
zoom levels -->
<option id="auto_fit" type="bool" default="false" />
</section>
<section id="cursor">
<option id="use_native_cursor" type="bool" default="false" />
@ -254,6 +261,7 @@
<option id="with_alpha" type="bool" default="true" />
<option id="dithering_algorithm" type="std::string" />
<option id="dithering_factor" type="int" default="100" />
<option id="to_gray" type="ToGrayAlgorithm" default="ToGrayAlgorithm::DEFAULT" />
</section>
<section id="eyedropper" text="Editor">
<option id="channel" type="EyedropperChannel" default="EyedropperChannel::COLOR_ALPHA" />
@ -326,6 +334,11 @@
<option id="show_alert" type="bool" default="true" />
<option id="pixel_scale" type="int" default="1" />
</section>
<section id="tga">
<option id="show_alert" type="bool" default="true" />
<option id="bits_per_pixel" type="int" default="0" />
<option id="compress" type="bool" default="true" />
</section>
<section id="webp">
<option id="show_alert" type="bool" default="true" />
<option id="loop" type="bool" default="true" />
@ -336,7 +349,7 @@
<option id="image_preset" type="int" default="0" />
</section>
<section id="hue_saturation">
<option id="mode" type="HueSaturationMode" default="HueSaturationMode::HSL" />
<option id="mode" type="filters::HueSaturationFilter::Mode" default="filters::HueSaturationFilter::Mode::HSL_MUL" />
</section>
<section id="filters">
<option id="cels_target" type="CelsTarget" default="CelsTarget::Selected" />

View File

@ -456,6 +456,7 @@ SetSameInk = Same Ink in All Tools
ShowAutoGuides = Show Auto Guides
ShowBrushPreview = Show Brush Preview
ShowBrushes = Show Brushes
ShowDynamics = Show Dynamics
ShowExtras = Show Extras
ShowGrid = Show Grid
ShowLayerEdges = Show Layer Edges
@ -559,6 +560,33 @@ duplicate = Duplicate:
as = As:
merged_layers = Duplicate merged layers only
[dynamics]
pressure = Pressure
pressure_tooltip = Control parameters through the pen pressure sensor
velocity = Velocity
velocity_tooltip = Control parameters through the mouse velocity
size = Size
size_tooltip = <<<END
Change the brush size
depending on the sensor value
END
angle = Angle
angle_tooltip = <<<END
Change the brush angle
depending on the sensor value
END
min_size_tooltip = Brush size when the sensor has its minimum value
max_size_tooltip = Brush size when the sensor has its maximum value
min_angle_tooltip = Brush angle when the sensor has its minimum value
max_angle_tooltip = Brush angle when the sensor has its maximum value
gradient = Gradient
gradient_tooltip = <<<END
Gradient between foreground
and background colors
END
max_point_value = Min/Max Values
sensors_tweaks = Sensor Threshold
[export_file]
title = Export File
output_file = Output File:
@ -1114,6 +1142,7 @@ zoom_from_center_with_keys = Zoom from center with keys
show_scrollbars = Show scroll-bars in sprite editor
show_scrollbars_tooltip = Show scroll-bars in all sprite editors.
auto_scroll = Auto-scroll on editor edges
auto_fit = Auto-fit on screen when a sprite is opened
straight_line_preview = Preview straight line immediately on Pencil tool
straight_line_preview_tooltip = <<<END
On Pencil tool you can draw straight lines
@ -1232,9 +1261,7 @@ file_format_doesnt_support_alert = Show warning when saving a file with unsuppor
export_animation_in_sequence_alert = Show warning when saving an animation as a sequence of static images
overwrite_files_on_export_alert = Show warning when overwriting files on File > Export
overwrite_files_on_export_sprite_sheet_alert = Show warning when overwriting files on Export Sprite Sheet
gif_options_alert = Show GIF options when saving .gif files
jpeg_options_alert = Show JPEG options when saving .jpeg files
svg_options_alert = Show SVG options when saving .svg files
image_format_alerts = Show options when saving files:
advanced_mode_alert = Show alert when we enter to Advanced Mode
invalid_fg_bg_color_alert = Show alert when drawing with index out of palette bounds
run_script_alert = Show alert when we try to run a script
@ -1256,6 +1283,12 @@ available_themes = Available Themes
select_theme = &Select
download_themes = Download Themes
open_theme_folder = Open &Folder
language_extensions = Languages
theme_extensions = Themes
script_extensions = Scripts
palette_extensions = Palettes
dithering_matrix_extensions = Dithering Matrices
multiple_extensions = Multiple Categories
add_extension = &Add Extension
add_extension_title = Add Extension
enable_extension = &Enable
@ -1472,6 +1505,11 @@ to = To:
color = Color:
ani_dir = Animation Direction:
[tga_options]
title = TGA Options
bits_per_pixel = Bits Per Pixel
compress = Compress
[timeline_conf]
position = Position:
left = &Left

View File

@ -1,5 +1,5 @@
<!-- Aseprite -->
<!-- Copyright (C) 2019 Igara Studio S.A. -->
<!-- Copyright (C) 2019-2020 Igara Studio S.A. -->
<!-- Copyright (C) 2017 David Capello -->
<gui>
<window id="color_mode" text="@.title">
@ -13,6 +13,11 @@
<slider min="0" max="100" id="factor" minwidth="100" />
<label text="%" />
</hbox>
<combobox id="to_gray_combobox">
<listitem text="!Luminance" />
<listitem text="!HSV" />
<listitem text="!HSL" />
</combobox>
<check text="@.flatten" id="flatten" />
<separator horizontal="true" />
<hbox>

55
data/widgets/dynamics.xml Normal file
View File

@ -0,0 +1,55 @@
<!-- Aseprite -->
<!-- Copyright (C) 2020 Igara Studio S.A. -->
<gui>
<vbox id="dynamics">
<hbox>
<buttonset id="values" columns="3">
<item text="" />
<item text="@.pressure" tooltip="@.pressure_tooltip" tooltip_dir="bottom" />
<item text="@.velocity" tooltip="@.velocity_tooltip" tooltip_dir="bottom" />
<item text="@.size" tooltip="@.size_tooltip" tooltip_dir="right" />
<item text="" maxheight="1" />
<item text="" maxheight="1" />
<item text="@.angle" tooltip="@.angle_tooltip" tooltip_dir="right" />
<item text="" maxheight="1" />
<item text="" maxheight="1" />
<item text="@.gradient" tooltip="@.gradient_tooltip" tooltip_dir="right" />
<item text="" maxheight="1" />
<item text="" maxheight="1" />
</buttonset>
</hbox>
<grid id="options" columns="2" childspacing="0" expansive="true">
<separator id="separator" text="@.max_point_value" horizontal="true" cell_hspan="2" />
<label id="size_label" text="@.size" style="mini_label" />
<slider id="min_size" value="1" min="1" max="64" cell_align="horizontal" style="mini_slider"
tooltip="@.min_size_tooltip" tooltip_dir="left" />
<boxfiller />
<slider id="max_size" value="64" min="1" max="64" cell_align="horizontal" style="mini_slider"
tooltip="@.max_size_tooltip" tooltip_dir="left" />
<label id="angle_label" text="@.angle" style="mini_label" />
<slider id="min_angle" value="0" min="-180" max="+180" cell_align="horizontal" style="mini_slider"
tooltip="@.min_angle_tooltip" tooltip_dir="left" />
<boxfiller />
<slider id="max_angle" value="0" min="-180" max="+180" cell_align="horizontal" style="mini_slider"
tooltip="@.max_angle_tooltip" tooltip_dir="left" />
<label id="gradient_label" text="@.gradient" style="mini_label" />
<hbox id="gradient_placeholder" cell_vspan="2" />
<link id="gradient_from_to" style="mini_label" />
<separator id="separator2" text="@.sensors_tweaks" horizontal="true" cell_hspan="2" />
<label id="pressure_label" text="@.pressure" style="mini_label" />
<hbox id="pressure_placeholder" cell_align="horizontal" />
<label id="velocity_label" text="@.velocity" style="mini_label" />
<hbox id="velocity_placeholder" cell_align="horizontal" />
</grid>
</vbox>
</gui>

View File

@ -1,5 +1,5 @@
<!-- Aseprite -->
<!-- Copyright (C) 2018-2019 Igara Studio S.A. -->
<!-- Copyright (C) 2018-2020 Igara Studio S.A. -->
<!-- Copyright (C) 2001-2018 David Capello -->
<gui>
<window id="options" text="@.title">
@ -203,6 +203,8 @@
<check text="@.zoom_from_center_with_keys" id="zoom_from_center_with_keys" />
<check text="@.show_scrollbars" id="show_scrollbars" tooltip="@.show_scrollbars_tooltip" />
<check text="@.auto_scroll" id="auto_scroll" />
<check text="@.auto_fit" id="auto_fit"
pref="editor.auto_fit" />
<check text="@.straight_line_preview" id="straight_line_preview" tooltip="@.straight_line_preview_tooltip" />
<check text="@.discard_brush" id="discard_brush" />
<hbox>
@ -408,18 +410,19 @@
pref="export_file.show_overwrite_files_alert" />
<check id="overwrite_files_on_export_sprite_sheet_alert" text="@.overwrite_files_on_export_sprite_sheet_alert"
pref="sprite_sheet.show_overwrite_files_alert" />
<check id="gif_options_alert" text="@.gif_options_alert"
pref="gif.show_alert" />
<check id="jpeg_options_alert" text="@.jpeg_options_alert"
pref="jpeg.show_alert" />
<check id="svg_options_alert" text="@.svg_options_alert"
pref="svg.show_alert" />
<check id="advanced_mode_alert" text="@.advanced_mode_alert"
pref="advanced_mode.show_alert" />
<check id="invalid_fg_bg_color_alert" text="@.invalid_fg_bg_color_alert"
pref="color_bar.show_invalid_fg_bg_color_alert" />
<check id="run_script_alert" text="@.run_script_alert"
pref="scripts.show_run_script_alert" />
<hbox>
<label text="@.image_format_alerts" />
<check id="gif_options_alert" text="!gif" pref="gif.show_alert" />
<check id="jpeg_options_alert" text="!jpeg" pref="jpeg.show_alert" />
<check id="svg_options_alert" text="!svg" pref="svg.show_alert" />
<check id="tga_options_alert" text="!tga" pref="tga.show_alert" />
</hbox>
<separator horizontal="true" />
<hbox>
<hbox expansive="true" />

View File

@ -0,0 +1,23 @@
<!-- Aseprite -->
<!-- Copyright (C) 2020 Igara Studio S.A. -->
<gui>
<window id="tga_options" text="@.title">
<grid columns="2">
<label text="@.bits_per_pixel" id="bits_per_pixel_label" />
<combobox id="bits_per_pixel" cell_align="horizontal" />
<check text="@.compress" id="compress" cell_hspan="2" />
<separator horizontal="true" cell_hspan="2" />
<hbox cell_hspan="2">
<check text="@general.dont_show" id="dont_show" tooltip="@general.dont_show_tooltip" />
<boxfiller />
<hbox homogeneous="true">
<button text="@general.ok" closewindow="true" id="ok" magnet="true" minwidth="60" />
<button text="@general.cancel" closewindow="true" />
</hbox>
</hbox>
</grid>
</window>
</gui>

View File

@ -85,9 +85,10 @@ We are using some C++11 features, mainly:
* Use range-based for loops (`for (const auto& item : values) { ... }`)
* Use template alias (`template<typename T> alias = orig<T>;`)
* Use non-generic lambda functions
* Use `std::shared_ptr` and `std::unique_ptr` (currently we're using
`base::SharedPtr` and `base::UniquePtr` but you should use the STL
ones now)
* Use `std::shared_ptr` and `std::unique_ptr`
* Use `base::clamp` (no `std::clamp` yet)
* Use `static constexpr T v = ...;`
* You can use `<atomic>`, `<thread>`, `<mutex>`, and `<condition_variable>`
* We use GCC 4.8 on Linux, so check the features available since GCC 4.8 in
* We can use `using T = ...;` instead of `typedef ... T`
* We use gcc 9.2 or clang 9.0 on Linux, so check the features available in
https://developer.mozilla.org/en-US/docs/Mozilla/Using_CXX_in_Mozilla_code

2
laf

@ -1 +1 @@
Subproject commit 98506341d68a318f4916a891ab12351a5db05f98
Subproject commit 8032d186a751326d0fc6436d69570ad4a3c4aaf1

View File

@ -19,6 +19,7 @@ if(MSVC)
add_definitions(-D_SCL_SECURE_NO_WARNINGS)
add_definitions(-wd4267) # disable warnings about signed/unsigned comparison
add_definitions(-wd4244) # disable warnings about possible loss of data
else()
# disable warnings about signed/unsigned comparison
add_definitions(-Wno-sign-compare)
@ -96,6 +97,7 @@ add_subdirectory(doc)
add_subdirectory(filters)
add_subdirectory(fixmath)
add_subdirectory(flic)
add_subdirectory(tga)
add_subdirectory(render)
add_subdirectory(dio)
add_subdirectory(ui)

View File

@ -171,6 +171,7 @@ if(ENABLE_SCRIPTING)
script/palette_class.cpp
script/palettes_class.cpp
script/pixel_color_object.cpp
script/plugin_class.cpp
script/point_class.cpp
script/preferences_object.cpp
script/range_class.cpp
@ -224,7 +225,6 @@ if(ENABLE_UI)
commands/cmd_eyedropper.cpp
commands/cmd_fill_and_stroke.cpp
commands/cmd_fit_screen.cpp
commands/cmd_flip.cpp
commands/cmd_frame_properties.cpp
commands/cmd_frame_tag_properties.cpp
commands/cmd_fullscreen_preview.cpp
@ -324,6 +324,7 @@ if(ENABLE_UI)
ui/dithering_selector.cpp
ui/doc_view.cpp
ui/drop_down_button.cpp
ui/dynamics_popup.cpp
ui/editor/brush_preview.cpp
ui/editor/drawing_state.cpp
ui/editor/editor.cpp
@ -512,6 +513,7 @@ add_library(app-lib
commands/cmd_crop.cpp
commands/cmd_export_sprite_sheet.cpp
commands/cmd_flatten_layers.cpp
commands/cmd_flip.cpp
commands/cmd_layer_from_background.cpp
commands/cmd_load_palette.cpp
commands/cmd_merge_down_layer.cpp
@ -601,6 +603,7 @@ add_library(app-lib
tools/symmetries.cpp
tools/tool_box.cpp
tools/tool_loop_manager.cpp
tools/velocity.cpp
transaction.cpp
transformation.cpp
ui/editor/tool_loop_impl.cpp
@ -612,6 +615,7 @@ add_library(app-lib
util/filetoks.cpp
util/freetype_utils.cpp
util/layer_boundaries.cpp
util/layer_utils.cpp
util/msk_file.cpp
util/new_image_from_mask.cpp
util/pal_ops.cpp
@ -642,6 +646,7 @@ target_link_libraries(app-lib
filters-lib
fixmath-lib
flic-lib
tga-lib
laf-gfx
render-lib
laf-ft

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -12,6 +12,7 @@
#include "app/doc.h"
#include "app/doc_event.h"
#include "app/site.h"
#include "app/util/layer_utils.h"
#include "doc/layer.h"
namespace app {
@ -64,6 +65,7 @@ void ActiveSiteHandler::getActiveSiteForDoc(Doc* doc, Site* site)
site->sprite(doc->sprite());
site->layer(doc::get<doc::Layer>(data.layer));
site->frame(data.frame);
site->range(data.range);
site->selectedColors(data.selectedColors);
site->selectedTiles(data.selectedTiles);
}
@ -80,6 +82,28 @@ void ActiveSiteHandler::setActiveFrameInDoc(Doc* doc, doc::frame_t frame)
data.frame = frame;
}
void ActiveSiteHandler::setRangeInDoc(Doc* doc, const DocRange& range)
{
Data& data = getData(doc);
data.range = range;
// Select at least the active layer
if (data.range.selectedLayers().empty()) {
if (auto layer = doc::get<Layer>(data.layer)) {
data.range.selectLayer(layer);
}
}
// Select at least the active frame
if (data.range.selectedFrames().empty()) {
SelectedFrames frames;
frames.insert(data.frame);
data.range.setSelectedFrames(frames);
}
data.range.setType(range.type());
}
void ActiveSiteHandler::setSelectedColorsInDoc(Doc* doc, const doc::PalettePicks& picks)
{
Data& data = getData(doc);
@ -104,30 +128,20 @@ void ActiveSiteHandler::onAddFrame(DocEvent& ev)
data.frame = ev.frame();
}
// TODO similar to Timeline::onBeforeRemoveLayer()
// TODO similar to Timeline::onBeforeRemoveLayer() and Editor::onBeforeRemoveLayer()
void ActiveSiteHandler::onBeforeRemoveLayer(DocEvent& ev)
{
Data& data = getData(ev.document());
doc::Layer* selectedLayer = (data.layer != doc::NullId ?
doc::get<doc::Layer>(data.layer):
nullptr);
if (!selectedLayer)
return;
Layer* layer = ev.layer();
// If the layer that was removed is the selected one
ASSERT(layer);
if (layer && data.layer == layer->id()) {
LayerGroup* parent = layer->parent();
Layer* layer_select = nullptr;
// Select previous layer, or next layer, or the parent (if it is
// not the main layer of sprite set).
if (layer->getPrevious()) {
layer_select = layer->getPrevious();
}
else if (layer->getNext())
layer_select = layer->getNext();
else if (parent != layer->sprite()->root())
layer_select = parent;
data.layer = (layer_select ? layer_select->id(): 0);
doc::Layer* layerToSelect = candidate_if_layer_is_deleted(selectedLayer, ev.layer());
if (selectedLayer != layerToSelect) {
data.layer = (layerToSelect ? layerToSelect->id():
doc::NullId);
}
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -9,6 +9,7 @@
#pragma once
#include "app/doc_observer.h"
#include "app/doc_range.h"
#include "doc/frame.h"
#include "doc/object_id.h"
#include "doc/palette_picks.h"
@ -38,6 +39,7 @@ namespace app {
void getActiveSiteForDoc(Doc* doc, Site* site);
void setActiveLayerInDoc(Doc* doc, doc::Layer* layer);
void setActiveFrameInDoc(Doc* doc, doc::frame_t frame);
void setRangeInDoc(Doc* doc, const DocRange& range);
void setSelectedColorsInDoc(Doc* doc, const doc::PalettePicks& picks);
void setSelectedTilesInDoc(Doc* doc, const doc::PalettePicks& picks);
@ -52,6 +54,7 @@ namespace app {
struct Data {
doc::ObjectId layer;
doc::frame_t frame;
DocRange range;
doc::PalettePicks selectedColors;
doc::PalettePicks selectedTiles;
};

View File

@ -217,6 +217,8 @@ App::App(AppMod* mod)
int App::initialize(const AppOptions& options)
{
os::System* system = os::instance();
#ifdef ENABLE_UI
m_isGui = options.startUI() && !options.previewCLI();
#else
@ -228,12 +230,13 @@ int App::initialize(const AppOptions& options)
#ifdef _WIN32
if (options.disableWintab() ||
!preferences().experimental.loadWintabDriver()) {
os::instance()->useWintabAPI(false);
system->useWintabAPI(false);
}
#endif
os::instance()->setAppMode(m_isGui ? os::AppMode::GUI:
os::AppMode::CLI);
system->setAppName(get_app_name());
system->setAppMode(m_isGui ? os::AppMode::GUI:
os::AppMode::CLI);
if (m_isGui)
m_uiSystem.reset(new ui::UISystem);
@ -309,6 +312,12 @@ int App::initialize(const AppOptions& options)
}
#endif // ENABLE_UI
#ifdef ENABLE_SCRIPTING
// Call the init() function from all plugins
LOG("APP: Initializing scripts...\n");
extensions().executeInitActions();
#endif
// Process options
LOG("APP: Processing options...\n");
{
@ -324,7 +333,7 @@ int App::initialize(const AppOptions& options)
return code;
}
os::instance()->finishLaunching();
system->finishLaunching();
return 0;
}
@ -395,7 +404,7 @@ void App::run()
// we've to print errors).
Console console;
#ifdef ENABLE_SCRIPTING
// Use the app::Console() for script erros
// Use the app::Console() for script errors
ConsoleEngineDelegate delegate;
script::ScopedEngineDelegate setEngineDelegate(m_engine.get(), &delegate);
#endif
@ -414,6 +423,13 @@ void App::run()
}
#endif // ENABLE_SCRIPTING
// ----------------------------------------------------------------------
#ifdef ENABLE_SCRIPTING
// Call the exit() function from all plugins
extensions().executeExitActions();
#endif
#ifdef ENABLE_UI
if (isGui()) {
// Select no document

View File

@ -38,8 +38,12 @@
#include "tinyxml.h"
#include <cctype>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <cstdlib>
#define MENUS_TRACE(...) // TRACEARGS
namespace app {
@ -61,6 +65,8 @@ const int kUnicodeRight = 0xF703; // NSRightArrowFunctionKey
const int kUnicodeUp = 0xF700; // NSUpArrowFunctionKey
const int kUnicodeDown = 0xF701; // NSDownArrowFunctionKey
const char* kFileRecentListGroup = "file_recent_list";
void destroy_instance(AppMenus* instance)
{
delete instance;
@ -328,10 +334,24 @@ AppMenus::~AppMenus()
void AppMenus::reload()
{
MENUS_TRACE("MENUS: AppMenus::reload()");
XmlDocumentRef doc(GuiXml::instance()->doc());
TiXmlHandle handle(doc.get());
const char* path = GuiXml::instance()->filename();
////////////////////////////////////////
// Remove all menu items added to groups from recent files and
// scripts so we can re-add them later in the new menus.
for (auto& it : m_groups) {
GroupInfo& group = it.second;
MENUS_TRACE("MENUS: - groups", it.first, "with", group.items.size(), "item(s)");
group.end = nullptr; // This value will be restored later
for (auto& item : group.items)
item->parent()->removeChild(item);
}
////////////////////////////////////////
// Load menus
@ -370,10 +390,39 @@ void AppMenus::reload()
if (scriptsMenu) {
delete scriptsMenu;
delete m_rootMenu->findItemById("scripts_menu_separator");
// Remove scripts group
auto it = m_groups.find("file_scripts");
if (it != m_groups.end())
m_groups.erase(it);
}
#endif
}
////////////////////////////////////////
// Re-add menu items in groups (recent files & scripts)
for (auto& it : m_groups) {
GroupInfo& group = it.second;
if (group.end) {
MENUS_TRACE("MENUS: - re-adding group ", it.first, "with", group.items.size(), "item(s)");
auto menu = group.end->parent();
int insertIndex = menu->getChildIndex(group.end);
for (auto& item : group.items) {
menu->insertChild(++insertIndex, item);
group.end = item;
}
}
// Delete items that don't have a group now
else {
MENUS_TRACE("MENUS: - deleting group ", it.first, "with", group.items.size(), "item(s)");
for (auto& item : group.items)
item->deferDelete();
group.items.clear();
}
}
////////////////////////////////////////
// Load keyboard shortcuts for commands
@ -458,6 +507,8 @@ void AppMenus::initTheme()
bool AppMenus::rebuildRecentList()
{
MENUS_TRACE("MENUS: AppMenus::rebuildRecentList m_recentFilesPlaceholder=", m_recentFilesPlaceholder);
if (!m_recentFilesPlaceholder)
return true;
@ -469,13 +520,10 @@ bool AppMenus::rebuildRecentList()
if (!owner || owner->hasSubmenuOpened())
return false;
int insertIndex = menu->getChildIndex(m_recentFilesPlaceholder)+1;
// Remove active items
while (auto appItem = dynamic_cast<AppMenuItem*>(menu->at(insertIndex))) {
menu->removeChild(appItem);
appItem->deferDelete();
}
for (auto item : m_recentMenuItems)
removeMenuItemFromGroup(item);
m_recentMenuItems.clear();
Command* openFile = Commands::instance()->byId(CommandId::OpenFile());
@ -492,18 +540,24 @@ bool AppMenus::rebuildRecentList()
for (const auto& fn : files) {
params.set("filename", fn.c_str());
auto menuitem = new AppMenuItem(base::get_file_name(fn).c_str(),
openFile, params);
std::unique_ptr<AppMenuItem> menuitem(
new AppMenuItem(base::get_file_name(fn).c_str(),
openFile, params));
menuitem->setIsRecentFileItem(true);
menu->insertChild(insertIndex++, menuitem);
m_recentMenuItems.push_back(menuitem.get());
addMenuItemIntoGroup(kFileRecentListGroup, std::move(menuitem));
}
}
else {
auto menuitem = new AppMenuItem(
Strings::main_menu_file_no_recent_file(), nullptr);
std::unique_ptr<AppMenuItem> menuitem(
new AppMenuItem(
Strings::main_menu_file_no_recent_file(), nullptr));
menuitem->setIsRecentFileItem(true);
menuitem->setEnabled(false);
menu->insertChild(insertIndex++, menuitem);
m_recentMenuItems.push_back(menuitem.get());
addMenuItemIntoGroup(kFileRecentListGroup, std::move(menuitem));
}
// Sync native menus
@ -520,6 +574,65 @@ bool AppMenus::rebuildRecentList()
return true;
}
void AppMenus::addMenuItemIntoGroup(const std::string& groupId,
std::unique_ptr<MenuItem>&& menuItem)
{
GroupInfo& group = m_groups[groupId];
Widget* menu = group.end->parent();
ASSERT(menu);
int insertIndex = menu->getChildIndex(group.end);
menu->insertChild(insertIndex+1, menuItem.get());
group.end = menuItem.get();
group.items.push_back(menuItem.get());
menuItem.release();
}
template<typename Pred>
void AppMenus::removeMenuItemFromGroup(Pred pred)
{
for (auto& it : m_groups) {
GroupInfo& group = it.second;
for (auto it=group.items.begin(); it != group.items.end(); ) {
auto& item = *it;
if (pred(item)) {
if (item == group.end)
group.end = group.end->previousSibling();
item->parent()->removeChild(item);
if (auto appItem = dynamic_cast<AppMenuItem*>(item)) {
if (appItem)
appItem->disposeNative();
}
item->deferDelete();
it = group.items.erase(it);
}
else {
++it;
}
}
}
}
void AppMenus::removeMenuItemFromGroup(Command* cmd)
{
removeMenuItemFromGroup(
[cmd](Widget* item){
auto appMenuItem = dynamic_cast<AppMenuItem*>(item);
return (appMenuItem && appMenuItem->getCommand() == cmd);
});
}
void AppMenus::removeMenuItemFromGroup(Widget* menuItem)
{
removeMenuItemFromGroup(
[menuItem](Widget* item){
return (item == menuItem);
});
}
Menu* AppMenus::loadMenuById(TiXmlHandle& handle, const char* id)
{
ASSERT(id != NULL);
@ -565,6 +678,7 @@ Menu* AppMenus::convertXmlelemToMenu(TiXmlElement* elem)
Widget* AppMenus::convertXmlelemToMenuitem(TiXmlElement* elem)
{
const char* id = elem->Attribute("id");
const char* group = elem->Attribute("group");
// is it a <separator>?
if (strcmp(elem->Value(), "separator") == 0) {
@ -577,6 +691,8 @@ Widget* AppMenus::convertXmlelemToMenuitem(TiXmlElement* elem)
m_recentFilesPlaceholder = item;
}
}
if (group)
m_groups[group].end = item;
return item;
}
@ -608,6 +724,8 @@ Widget* AppMenus::convertXmlelemToMenuitem(TiXmlElement* elem)
if (id) menuitem->setId(id);
menuitem->processMnemonicFromText();
if (group)
m_groups[group].end = menuitem;
// Has it a ID?
if (id) {
@ -708,8 +826,7 @@ void AppMenus::createNativeMenus()
if (!menus) // This platform doesn't support native menu items
return;
if (m_osMenu)
m_osMenu->dispose();
os::Menu* oldOSMenu = m_osMenu;
m_osMenu = menus->createMenu();
#ifdef __APPLE__ // Create default macOS app menus (App ... Window)
@ -795,6 +912,8 @@ void AppMenus::createNativeMenus()
#endif
menus->setAppMenu(m_osMenu);
if (oldOSMenu)
oldOSMenu->dispose();
}
void AppMenus::createNativeSubmenus(os::Menu* osMenu, const ui::Menu* uiMenu)

View File

@ -65,7 +65,16 @@ namespace app {
const KeyPtr& key);
void syncNativeMenuItemKeyShortcuts();
// Menu item handling in groups
void addMenuItemIntoGroup(const std::string& groupId,
std::unique_ptr<MenuItem>&& menuItem);
void removeMenuItemFromGroup(Command* cmd);
void removeMenuItemFromGroup(Widget* menuItem);
private:
template<typename Pred>
void removeMenuItemFromGroup(Pred pred);
Menu* loadMenuById(TiXmlHandle& handle, const char *id);
Menu* convertXmlelemToMenu(TiXmlElement* elem);
Widget* convertXmlelemToMenuitem(TiXmlElement* elem);
@ -82,6 +91,11 @@ namespace app {
const bool rootLevel);
#endif
struct GroupInfo {
Widget* end = nullptr;
WidgetsList items;
};
std::unique_ptr<Menu> m_rootMenu;
Widget* m_recentFilesPlaceholder;
MenuItem* m_helpMenuitem;
@ -97,6 +111,13 @@ namespace app {
std::unique_ptr<Menu> m_inkPopupMenu;
obs::scoped_connection m_recentFilesConn;
std::vector<Menu*> m_menus;
// List of recent menu items pointing to recent files.
WidgetsList m_recentMenuItems;
// Extension points for plugins (each group is a place where new
// menu items can be added).
std::map<std::string, GroupInfo> m_groups;
// Native main menu bar (== nullptr if the platform doesn't
// support native menus)
os::Menu* m_osMenu;
XmlTranslator m_xmlTranslator;
};

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -23,6 +23,7 @@
#include "app/filename_formatter.h"
#include "app/restore_visible_layers.h"
#include "app/ui_context.h"
#include "base/clamp.h"
#include "base/convert_to.h"
#include "base/fs.h"
#include "base/split_string.h"
@ -34,6 +35,7 @@
#include "doc/tags.h"
#include "render/dithering_algorithm.h"
#include <algorithm>
#include <queue>
#include <vector>
@ -517,7 +519,7 @@ int CliProcessor::process(Context* ctx)
scaleWidth = (doc->width() > maxWidth ? maxWidth / doc->width() : 1.0);
scaleHeight = (doc->height() > maxHeight ? maxHeight / doc->height() : 1.0);
if (scaleWidth < 1.0 || scaleHeight < 1.0) {
scale = MIN(scaleWidth, scaleHeight);
scale = std::min(scaleWidth, scaleHeight);
Params params;
params.set("scale", base::convert_to<std::string>(scale).c_str());
ctx->executeCommand(Commands::instance()->byId(CommandId::SpriteSize()),
@ -642,8 +644,8 @@ bool CliProcessor::openFile(Context* ctx, CliOpenFile& cof)
// --frame-range with --frame-tag
if (tag) {
selFrames.insert(
tag->fromFrame()+MID(0, cof.fromFrame, tag->frames()-1),
tag->fromFrame()+MID(0, cof.toFrame, tag->frames()-1));
tag->fromFrame()+base::clamp(cof.fromFrame, 0, tag->frames()-1),
tag->fromFrame()+base::clamp(cof.toFrame, 0, tag->frames()-1));
}
// --frame-range without --frame-tag
else {

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello
//
// This program is distributed under the terms of
@ -18,6 +18,7 @@
#include "app/cmd/set_cel_opacity.h"
#include "app/cmd/set_cel_position.h"
#include "app/doc.h"
#include "base/clamp.h"
#include "doc/cel.h"
#include "doc/image.h"
#include "doc/layer.h"
@ -64,7 +65,7 @@ void BackgroundFromLayer::onExecute()
bg_image.get(), cel_image,
sprite->palette(cel->frame()),
cel->x(), cel->y(),
MID(0, cel->opacity(), 255),
base::clamp(cel->opacity(), 0, 255),
static_cast<LayerImage*>(layer)->blendMode());
// now we have to copy the new image (bg_image) to the cel...

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2015 David Capello
//
// This program is distributed under the terms of
@ -37,11 +37,11 @@ SetPalette::SetPalette(Sprite* sprite, frame_t frame, const Palette* newPalette)
ASSERT(diffs > 0);
if (m_from >= 0 && m_to >= m_from) {
int oldColors = MIN(m_to+1, m_oldNColors)-m_from;
int oldColors = std::min(m_to+1, m_oldNColors)-m_from;
if (oldColors > 0)
m_oldColors.resize(oldColors);
int newColors = MIN(m_to+1, m_newNColors)-m_from;
int newColors = std::min(m_to+1, m_newNColors)-m_from;
if (newColors > 0)
m_newColors.resize(newColors);

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -70,6 +70,7 @@ private:
SetPixelFormat::SetPixelFormat(Sprite* sprite,
const PixelFormat newFormat,
const render::Dithering& dithering,
doc::rgba_to_graya_func toGray,
render::TaskDelegate* delegate)
: WithSprite(sprite)
, m_oldFormat(sprite->pixelFormat())
@ -101,6 +102,7 @@ SetPixelFormat::SetPixelFormat(Sprite* sprite,
oldImage,
cel->frame(),
cel->layer()->isBackground(),
toGray,
&superDel);
superDel.nextImage();
@ -116,6 +118,7 @@ SetPixelFormat::SetPixelFormat(Sprite* sprite,
oldImage,
0, // TODO select a frame or generate other tilesets?
false, // TODO is background? it depends of the layer where this tileset is used
toGray,
&superDel);
}
superDel.nextImage();
@ -188,6 +191,7 @@ void SetPixelFormat::convertImage(doc::Sprite* sprite,
const doc::ImageRef& oldImage,
const doc::frame_t frame,
const bool isBackground,
doc::rgba_to_graya_func toGray,
render::TaskDelegate* delegate)
{
ASSERT(oldImage);
@ -201,6 +205,7 @@ void SetPixelFormat::convertImage(doc::Sprite* sprite,
sprite->palette(frame),
isBackground,
oldImage->maskColor(),
toGray,
delegate));
m_seq.add(new cmd::ReplaceImage(sprite, oldImage, newImage));

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
@ -11,6 +11,7 @@
#include "app/cmd/with_sprite.h"
#include "app/cmd_sequence.h"
#include "doc/color.h"
#include "doc/frame.h"
#include "doc/image_ref.h"
#include "doc/pixel_format.h"
@ -33,6 +34,7 @@ namespace cmd {
SetPixelFormat(doc::Sprite* sprite,
const doc::PixelFormat newFormat,
const render::Dithering& dithering,
doc::rgba_to_graya_func toGray,
render::TaskDelegate* delegate);
protected:
@ -50,6 +52,7 @@ namespace cmd {
const doc::ImageRef& oldImage,
const doc::frame_t frame,
const bool isBackground,
doc::rgba_to_graya_func toGray,
render::TaskDelegate* delegate);
doc::PixelFormat m_oldFormat;

View File

@ -24,12 +24,16 @@ SetUserData::SetUserData(doc::WithUserData* obj, const doc::UserData& userData)
void SetUserData::onExecute()
{
doc::get<doc::WithUserData>(m_objId)->setUserData(m_newUserData);
auto obj = doc::get<doc::WithUserData>(m_objId);
obj->setUserData(m_newUserData);
obj->incrementVersion();
}
void SetUserData::onUndo()
{
doc::get<doc::WithUserData>(m_objId)->setUserData(m_oldUserData);
auto obj = doc::get<doc::WithUserData>(m_objId);
obj->setUserData(m_oldUserData);
obj->incrementVersion();
}
} // namespace cmd

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -88,10 +88,7 @@ std::istream* CmdTransaction::documentRangeAfterExecute() const
void CmdTransaction::onExecute()
{
CmdSequence::onExecute();
// The execution of CmdTransaction is called by Transaction at the
// very beginning, just to save the current sprite position.
// Save the current site and doc range
m_spritePositionBefore = calcSpritePosition();
#ifdef ENABLE_UI
if (isDocRangeEnabled()) {
@ -100,6 +97,9 @@ void CmdTransaction::onExecute()
}
#endif
// Execute the sequence of "cmds"
CmdSequence::onExecute();
if (m_changeSavedState)
++(*m_savedCounter);
}

View File

@ -13,6 +13,7 @@
#include "app/color_utils.h"
#include "app/modules/palettes.h"
#include "base/clamp.h"
#include "base/debug.h"
#include "doc/image.h"
#include "doc/palette.h"
@ -203,8 +204,8 @@ std::string Color::toString() const
<< std::setprecision(2)
<< std::fixed
<< m_value.hsv.h << ","
<< MID(0.0, m_value.hsv.s*100.0, 100.0) << ","
<< MID(0.0, m_value.hsv.v*100.0, 100.0) << ","
<< base::clamp(m_value.hsv.s*100.0, 0.0, 100.0) << ","
<< base::clamp(m_value.hsv.v*100.0, 0.0, 100.0) << ","
<< m_value.hsv.a << "}";
break;
@ -213,8 +214,8 @@ std::string Color::toString() const
<< std::setprecision(2)
<< std::fixed
<< m_value.hsl.h << ","
<< MID(0.0, m_value.hsl.s*100.0, 100.0) << ","
<< MID(0.0, m_value.hsl.l*100.0, 100.0) << ","
<< base::clamp(m_value.hsl.s*100.0, 0.0, 100.0) << ","
<< base::clamp(m_value.hsl.l*100.0, 0.0, 100.0) << ","
<< m_value.hsl.a << "}";
break;
@ -267,8 +268,8 @@ std::string Color::toHumanReadableString(PixelFormat pixelFormat, HumanReadableS
else {
result << "HSV "
<< int(m_value.hsv.h) << "\xc2\xb0 "
<< MID(0, int(m_value.hsv.s*100.0), 100) << "% "
<< MID(0, int(m_value.hsv.v*100.0), 100) << "%";
<< base::clamp(int(m_value.hsv.s*100.0), 0, 100) << "% "
<< base::clamp(int(m_value.hsv.v*100.0), 0, 100) << "%";
if (pixelFormat == IMAGE_INDEXED)
result << " Index " << color_utils::color_for_image(*this, IMAGE_INDEXED);
@ -287,8 +288,8 @@ std::string Color::toHumanReadableString(PixelFormat pixelFormat, HumanReadableS
else {
result << "HSL "
<< int(m_value.hsl.h) << "\xc2\xb0 "
<< MID(0, int(m_value.hsl.s*100.0), 100) << "% "
<< MID(0, int(m_value.hsl.l*100.0), 100) << "%";
<< base::clamp(int(m_value.hsl.s*100.0), 0, 100) << "% "
<< base::clamp(int(m_value.hsl.l*100.0), 0, 100) << "%";
if (pixelFormat == IMAGE_INDEXED)
result << " Index " << color_utils::color_for_image(*this, IMAGE_INDEXED);
@ -357,8 +358,8 @@ std::string Color::toHumanReadableString(PixelFormat pixelFormat, HumanReadableS
}
else {
result << int(m_value.hsv.h) << "\xc2\xb0"
<< MID(0, int(m_value.hsv.s*100.0), 100) << ","
<< MID(0, int(m_value.hsv.v*100.0), 100);
<< base::clamp(int(m_value.hsv.s*100.0), 0, 100) << ","
<< base::clamp(int(m_value.hsv.v*100.0), 0, 100);
}
break;
@ -368,8 +369,8 @@ std::string Color::toHumanReadableString(PixelFormat pixelFormat, HumanReadableS
}
else {
result << int(m_value.hsl.h) << "\xc2\xb0"
<< MID(0, int(m_value.hsl.s*100.0), 100) << ","
<< MID(0, int(m_value.hsl.l*100.0), 100);
<< base::clamp(int(m_value.hsl.s*100.0), 0, 100) << ","
<< base::clamp(int(m_value.hsl.l*100.0), 0, 100);
}
break;
@ -907,7 +908,7 @@ int Color::getAlpha() const
void Color::setAlpha(int alpha)
{
alpha = MID(0, alpha, 255);
alpha = base::clamp(alpha, 0, 255);
switch (getType()) {

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -24,6 +24,7 @@
#include "app/ui/skin/skin_theme.h"
#include "app/ui_context.h"
#include "base/bind.h"
#include "base/clamp.h"
#include "doc/image.h"
#include "doc/mask.h"
#include "doc/sprite.h"
@ -368,8 +369,8 @@ void CanvasSizeCommand::onExecute(Context* context)
api.cropSprite(sprite,
gfx::Rect(x1, y1,
MID(1, x2-x1, DOC_SPRITE_MAX_WIDTH),
MID(1, y2-y1, DOC_SPRITE_MAX_HEIGHT)),
base::clamp(x2-x1, 1, DOC_SPRITE_MAX_WIDTH),
base::clamp(y2-y1, 1, DOC_SPRITE_MAX_HEIGHT)),
params.trimOutside());
tx.commit();

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2018 David Capello
//
// This program is distributed under the terms of
@ -18,6 +19,7 @@
#include "app/modules/gui.h"
#include "app/tx.h"
#include "app/ui/timeline/timeline.h"
#include "base/clamp.h"
#include "doc/cel.h"
#include "doc/cels_range.h"
#include "doc/sprite.h"
@ -51,7 +53,7 @@ CelOpacityCommand::CelOpacityCommand()
void CelOpacityCommand::onLoadParams(const Params& params)
{
m_opacity = params.get_as<int>("opacity");
m_opacity = MID(0, m_opacity, 255);
m_opacity = base::clamp(m_opacity, 0, 255);
}
bool CelOpacityCommand::onEnabled(Context* context)

View File

@ -44,6 +44,7 @@
#include "ui/size_hint_event.h"
#include "color_mode.xml.h"
#include <string>
namespace app {
@ -51,6 +52,15 @@ using namespace ui;
namespace {
rgba_to_graya_func get_gray_func(gen::ToGrayAlgorithm toGray) {
switch (toGray) {
case gen::ToGrayAlgorithm::LUMA: return &rgba_to_graya_using_luma;
case gen::ToGrayAlgorithm::HSV: return &rgba_to_graya_using_hsv;
case gen::ToGrayAlgorithm::HSL: return &rgba_to_graya_using_hsl;
}
return nullptr;
}
class ConversionItem : public ListItem {
public:
ConversionItem(const doc::PixelFormat pixelFormat)
@ -79,6 +89,7 @@ public:
const doc::frame_t frame,
const doc::PixelFormat pixelFormat,
const render::Dithering& dithering,
const gen::ToGrayAlgorithm toGray,
const gfx::Point& pos,
const bool newBlend)
: m_image(dstImage)
@ -91,10 +102,12 @@ public:
sprite, frame,
pixelFormat,
dithering,
toGray,
newBlend]() { // Copy the matrix
run(sprite, frame,
pixelFormat,
dithering,
toGray,
newBlend);
})
{
@ -118,6 +131,7 @@ private:
const doc::frame_t frame,
const doc::PixelFormat pixelFormat,
const render::Dithering& dithering,
const gen::ToGrayAlgorithm toGray,
const bool newBlend) {
doc::ImageRef tmp(
Image::create(sprite->pixelFormat(),
@ -142,6 +156,7 @@ private:
sprite->palette(frame),
(sprite->backgroundLayer() != nullptr),
0,
get_gray_func(toGray),
this);
m_running = false;
@ -211,9 +226,12 @@ public:
else {
amount()->setVisible(false);
}
if (from != IMAGE_GRAYSCALE)
if (from != IMAGE_GRAYSCALE) {
colorMode()->addChild(new ConversionItem(IMAGE_GRAYSCALE));
toGrayCombobox()->Change.connect(base::Bind<void>(&ColorModeWindow::onToGrayChange, this));
}
colorModeView()->setMinSize(
colorModeView()->sizeHint() +
colorMode()->sizeHint());
@ -249,6 +267,15 @@ public:
return d;
}
gen::ToGrayAlgorithm toGray() const {
static_assert(
int(gen::ToGrayAlgorithm::LUMA) == 0 &&
int(gen::ToGrayAlgorithm::HSV) == 1 &&
int(gen::ToGrayAlgorithm::HSL) == 2,
"Check that 'to_gray_combobox' combobox items matches these indexes in color_mode.xml");
return (gen::ToGrayAlgorithm)toGrayCombobox()->getSelectedItemIndex();
}
bool flattenEnabled() const {
return flatten()->isSelected();
}
@ -307,6 +334,11 @@ private:
amount()->setVisible(toIndexed && errorDiff);
}
{
const bool toGray = (dstPixelFormat == doc::IMAGE_GRAYSCALE);
toGrayCombobox()->setVisible(toGray);
}
m_image.reset(
Image::create(dstPixelFormat,
visibleBounds.w,
@ -336,6 +368,7 @@ private:
m_editor->frame(),
dstPixelFormat,
dithering(),
toGray(),
visibleBounds.origin(),
Preferences::instance().experimental.newBlend()));
@ -348,6 +381,12 @@ private:
onChangeColorMode();
}
void onToGrayChange() {
stop();
m_selectedItem = nullptr;
onChangeColorMode();
}
void onMonitorProgress() {
ASSERT(m_bgThread);
if (!m_bgThread)
@ -402,6 +441,7 @@ private:
bool m_useUI;
doc::PixelFormat m_format;
render::Dithering m_dithering;
gen::ToGrayAlgorithm m_toGray;
};
ChangePixelFormatCommand::ChangePixelFormatCommand()
@ -410,6 +450,7 @@ ChangePixelFormatCommand::ChangePixelFormatCommand()
m_useUI = true;
m_format = IMAGE_RGB;
m_dithering = render::Dithering();
m_toGray = gen::ToGrayAlgorithm::DEFAULT;
}
void ChangePixelFormatCommand::onLoadParams(const Params& params)
@ -418,7 +459,8 @@ void ChangePixelFormatCommand::onLoadParams(const Params& params)
std::string format = params.get("format");
if (format == "rgb") m_format = IMAGE_RGB;
else if (format == "grayscale") m_format = IMAGE_GRAYSCALE;
else if (format == "grayscale" ||
format == "gray") m_format = IMAGE_GRAYSCALE;
else if (format == "indexed") m_format = IMAGE_INDEXED;
else
m_useUI = true;
@ -454,6 +496,16 @@ void ChangePixelFormatCommand::onLoadParams(const Params& params)
// TODO object slicing here (from BayerMatrix -> DitheringMatrix)
m_dithering.matrix(render::BayerMatrix(8));
}
std::string toGray = params.get("toGray");
if (toGray == "luma")
m_toGray = gen::ToGrayAlgorithm::LUMA;
else if (dithering == "hsv")
m_toGray = gen::ToGrayAlgorithm::HSV;
else if (dithering == "hsl")
m_toGray = gen::ToGrayAlgorithm::HSL;
else
m_toGray = gen::ToGrayAlgorithm::DEFAULT;
}
bool ChangePixelFormatCommand::onEnabled(Context* context)
@ -518,6 +570,7 @@ void ChangePixelFormatCommand::onExecute(Context* context)
m_format = window.pixelFormat();
m_dithering = window.dithering();
m_toGray = window.toGray();
flatten = window.flattenEnabled();
window.saveDitheringOptions();
@ -554,6 +607,7 @@ void ChangePixelFormatCommand::onExecute(Context* context)
new cmd::SetPixelFormat(
sprite, m_format,
m_dithering,
get_gray_func(m_toGray),
&job)); // SpriteJob is a render::TaskDelegate
});
job.waitJob();

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -63,23 +63,23 @@ void FlipCommand::onLoadParams(const Params& params)
doc::algorithm::FlipHorizontal);
}
bool FlipCommand::onEnabled(Context* context)
bool FlipCommand::onEnabled(Context* ctx)
{
return context->checkFlags(ContextFlags::ActiveDocumentIsWritable);
return ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable);
}
void FlipCommand::onExecute(Context* context)
void FlipCommand::onExecute(Context* ctx)
{
Site site = context->activeSite();
Timeline* timeline = App::instance()->timeline();
LockTimelineRange lockRange(timeline);
Site site = ctx->activeSite();
LockTimelineRange lockRange(App::instance()->timeline());
CelList cels;
if (m_flipMask) {
// If we want to flip the visible mask we can go to
// MovingPixelsState (even when the range is enabled, because now
// PixelsMovement support ranges).
if (site.document()->isMaskVisible()) {
if (site.document()->isMaskVisible() &&
ctx->isUIAvailable()) {
// Select marquee tool
if (tools::Tool* tool = App::instance()->toolBox()
->getToolById(tools::WellKnownTools::RectangularMarquee)) {
@ -89,7 +89,7 @@ void FlipCommand::onExecute(Context* context)
}
}
auto range = timeline->range();
auto range = site.range();
if (range.enabled()) {
cels = get_unlocked_unique_cels(site.sprite(), range);
}
@ -101,7 +101,7 @@ void FlipCommand::onExecute(Context* context)
if (cels.empty()) {
StatusBar::instance()->showTip(
1000, Strings::statusbar_tips_all_layers_are_locked().c_str());
1000, Strings::statusbar_tips_all_layers_are_locked());
return;
}
}
@ -111,10 +111,10 @@ void FlipCommand::onExecute(Context* context)
cels.push_back(cel);
}
ContextWriter writer(context);
ContextWriter writer(ctx);
Doc* document = writer.document();
Sprite* sprite = writer.sprite();
Tx tx(writer.context(), friendlyName());
Tx tx(ctx, friendlyName());
DocApi api = document->getApi(tx);
Mask* mask = document->mask();
@ -228,7 +228,11 @@ void FlipCommand::onExecute(Context* context)
}
tx.commit();
update_screen_for_document(document);
#ifdef ENABLE_UI
if (ctx->isUIAvailable())
update_screen_for_document(document);
#endif
}
std::string FlipCommand::onGetFriendlyName() const

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -18,6 +18,7 @@
#include "app/ui/editor/editor.h"
#include "app/ui/editor/editor_customization_delegate.h"
#include "app/ui/search_entry.h"
#include "base/clamp.h"
#include "doc/sprite.h"
#include "doc/tag.h"
#include "ui/combobox.h"
@ -256,7 +257,9 @@ private:
}
}
return MID(0, m_frame-docPref.timeline.firstFrame(), editor->sprite()->lastFrame());
return base::clamp(
m_frame-docPref.timeline.firstFrame(),
0, editor->sprite()->lastFrame());
}
private:

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
@ -18,6 +19,7 @@
#include "app/ui/timeline/timeline.h"
#include "doc/layer.h"
#include "doc/sprite.h"
#include "fmt/format.h"
namespace app {
@ -73,9 +75,9 @@ protected:
void updateStatusBar(Site& site) {
if (site.layer() != NULL)
StatusBar::instance()->setStatusText(
1000, "%s '%s' selected",
(site.layer()->isGroup() ? "Group": "Layer"),
site.layer()->name().c_str());
1000, fmt::format("{} '{}' selected",
(site.layer()->isGroup() ? "Group": "Layer"),
site.layer()->name()));
}
private:

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -45,6 +45,7 @@
#include "keyboard_shortcuts.xml.h"
#include <algorithm>
#include <map>
#include <memory>
@ -260,7 +261,7 @@ private:
m_headerItem->contextXPos() +
Graphics::measureUITextLength(
convertKeyContextToUserFriendlyString(m_key->keycontext()), font());
size.w = MAX(size.w, w);
size.w = std::max(size.w, w);
}
if (m_key && !m_key->accels().empty()) {

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2016-2018 David Capello
//
// This program is distributed under the terms of
@ -18,6 +19,7 @@
#include "app/modules/gui.h"
#include "app/tx.h"
#include "app/ui/timeline/timeline.h"
#include "base/clamp.h"
#include "doc/layer.h"
#include "fmt/format.h"
@ -49,7 +51,7 @@ LayerOpacityCommand::LayerOpacityCommand()
void LayerOpacityCommand::onLoadParams(const Params& params)
{
m_opacity = params.get_as<int>("opacity");
m_opacity = MID(0, m_opacity, 255);
m_opacity = base::clamp(m_opacity, 0, 255);
}
bool LayerOpacityCommand::onEnabled(Context* context)

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2015-2018 David Capello
//
// This program is distributed under the terms of
@ -18,6 +18,7 @@
#include "app/modules/gui.h"
#include "app/pref/preferences.h"
#include "app/tx.h"
#include "base/clamp.h"
#include "base/convert_to.h"
#include "doc/algorithm/modify_selection.h"
#include "doc/brush_type.h"
@ -112,7 +113,7 @@ void ModifySelectionCommand::onExecute(Context* context)
return;
quantity = window.quantity()->textInt();
quantity = MID(1, quantity, 100);
quantity = base::clamp(quantity, 1, 100);
brush = (window.circle()->isSelected() ? doc::kCircleBrushType:
doc::kSquareBrushType);

View File

@ -42,7 +42,7 @@ protected:
void onExecute(Context* context) override;
// SelectBoxDelegate impl
void onQuickboxEnd(Editor* editor, const gfx::Rect& rect, ui::MouseButtons buttons) override;
void onQuickboxEnd(Editor* editor, const gfx::Rect& rect, ui::MouseButton button) override;
void onQuickboxCancel(Editor* editor) override;
std::string onGetContextBarHelp() override {
@ -101,7 +101,7 @@ void NewBrushCommand::onExecute(Context* context)
}
}
void NewBrushCommand::onQuickboxEnd(Editor* editor, const gfx::Rect& rect, ui::MouseButtons buttons)
void NewBrushCommand::onQuickboxEnd(Editor* editor, const gfx::Rect& rect, ui::MouseButton button)
{
Mask mask;
mask.replace(rect);
@ -109,7 +109,7 @@ void NewBrushCommand::onQuickboxEnd(Editor* editor, const gfx::Rect& rect, ui::M
selectPencilTool();
// If the right-button was used, we clear the selected area.
if (buttons & ui::kButtonRight) {
if (button == ui::kButtonRight) {
try {
ContextWriter writer(UIContext::instance());
if (writer.cel()) {
@ -175,7 +175,7 @@ void NewBrushCommand::createBrush(const Site& site, const Mask* mask)
std::string tooltip;
tooltip += "Shortcut: ";
tooltip += key->accels().front().toString();
StatusBar::instance()->showTip(2000, tooltip.c_str());
StatusBar::instance()->showTip(2000, tooltip);
}
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -129,7 +129,7 @@ void NewFileCommand::onExecute(Context* ctx)
int w = pref.newFile.width();
int h = pref.newFile.height();
int bg = pref.newFile.backgroundColor();
bg = MID(0, bg, 2);
bg = base::clamp(bg, 0, 2);
// If the clipboard contains an image, we can show the size of the
// clipboard as default image size.

View File

@ -172,10 +172,10 @@ void NewFrameCommand::onExecute(Context* context)
if (context->isUIAvailable()) {
update_screen_for_document(document);
StatusBar::instance()
->showTip(1000, "New frame %d/%d",
(int)context->activeSite().frame()+1,
(int)sprite->totalFrames());
StatusBar::instance()->showTip(
1000, fmt::format("New frame {}/{}",
(int)context->activeSite().frame()+1,
(int)sprite->totalFrames()));
App::instance()->mainWindow()->popTimeline();
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -24,6 +24,7 @@
#include "app/i18n/strings.h"
#include "app/load_widget.h"
#include "app/modules/gui.h"
#include "app/restore_visible_layers.h"
#include "app/tx.h"
#include "app/ui/main_window.h"
#include "app/ui/status_bar.h"
@ -31,6 +32,7 @@
#include "app/ui_context.h"
#include "app/util/clipboard.h"
#include "app/util/new_image_from_mask.h"
#include "app/util/range_utils.h"
#include "doc/layer.h"
#include "doc/layer_tilemap.h"
#include "doc/primitives.h"
@ -44,8 +46,10 @@
#include "new_layer.xml.h"
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#include <cstdlib>
namespace app {
@ -400,28 +404,54 @@ void NewLayerCommand::onExecute(Context* context)
#endif // ENABLE_UI
// Paste new layer from selection
else if ((params().viaCut() || params().viaCopy())
&& layer->isImage()
&& document->isMaskVisible()) {
const doc::Mask* mask = document->mask();
ASSERT(mask);
ImageRef image(new_image_from_mask(site, mask, true));
if (image) {
Cel* cel = api.addCel(static_cast<LayerImage*>(layer),
site.frame(), image);
if (cel) {
RestoreVisibleLayers restore;
SelectedLayers layers;
SelectedFrames frames;
bool merged;
if (site.range().enabled()) {
merged = true;
layers = site.range().selectedLayers();
frames = site.range().selectedFrames();
restore.showSelectedLayers(site.sprite(), layers);
}
else {
merged = false;
layers.insert(site.layer());
frames.insert(site.frame());
}
for (frame_t frame : frames) {
ImageRef newImage(new_image_from_mask(site, mask, true, merged));
if (!newImage)
continue;
Cel* newCel = api.addCel(static_cast<LayerImage*>(layer),
frame, newImage);
if (newCel) {
gfx::Point pos = mask->bounds().origin();
cel->setPosition(pos.x, pos.y);
newCel->setPosition(pos.x, pos.y);
}
if (params().viaCut() &&
site.cel() && site.layer()) {
tx(new cmd::ClearMask(site.cel()));
for (Layer* layer : layers) {
if (!layer->isImage() ||
!layer->isEditable()) // Locked layers will not be modified
continue;
if (site.layer()->isTransparent()) {
// If the cel wasn't deleted by cmd::ClearMask, we trim it.
cel = site.layer()->cel(site.frame());
if (cel)
tx(new cmd::TrimCel(cel));
Cel* origCel = layer->cel(site.frame());
if (origCel &&
params().viaCut()) {
tx(new cmd::ClearMask(origCel));
if (layer->isTransparent()) {
// If the cel wasn't deleted by cmd::ClearMask, we trim it.
origCel = layer->cel(frame);
if (origCel)
tx(new cmd::TrimCel(origCel));
}
}
}
}
@ -436,9 +466,9 @@ void NewLayerCommand::onExecute(Context* context)
StatusBar::instance()->invalidate();
StatusBar::instance()->showTip(
1000, "%s '%s' created",
layerPrefix().c_str(),
name.c_str());
1000, fmt::format("{} '{}' created",
layerPrefix(),
name));
App::instance()->mainWindow()->popTimeline();
}
@ -466,8 +496,8 @@ std::string NewLayerCommand::onGetFriendlyName() const
void NewLayerCommand::adjustRefCelBounds(Cel* cel, gfx::RectF bounds)
{
Sprite* sprite = cel->sprite();
double scale = MIN(double(sprite->width()) / bounds.w,
double(sprite->height()) / bounds.h);
double scale = std::min(double(sprite->width()) / bounds.w,
double(sprite->height()) / bounds.h);
bounds.w *= scale;
bounds.h *= scale;
bounds.x = sprite->width()/2 - bounds.w/2;
@ -494,7 +524,7 @@ int NewLayerCommand::getMaxLayerNum(const Layer* layer) const
if (layer->isGroup()) {
for (const Layer* child : static_cast<const LayerGroup*>(layer)->layers()) {
int tmp = getMaxLayerNum(child);
max = MAX(tmp, max);
max = std::max(tmp, max);
}
}

View File

@ -31,6 +31,7 @@
#include "app/ui/separator_in_view.h"
#include "app/ui/skin/skin_theme.h"
#include "base/bind.h"
#include "base/clamp.h"
#include "base/convert_to.h"
#include "base/fs.h"
#include "base/string.h"
@ -70,6 +71,25 @@ app::gen::ColorProfileBehavior missingCsMap[] = {
app::gen::ColorProfileBehavior::ASK,
};
class ExtensionCategorySeparator : public SeparatorInView {
public:
ExtensionCategorySeparator(const Extension::Category category,
const std::string& text)
: SeparatorInView(text, ui::HORIZONTAL)
, m_category(category) {
InitTheme.connect(
[this]{
auto b = this->border();
b.top(2*b.top());
b.bottom(2*b.bottom());
this->setBorder(b);
});
}
Extension::Category category() const { return m_category; }
private:
Extension::Category m_category;
};
} // anonymous namespace
using namespace ui;
@ -122,6 +142,11 @@ class OptionsWindow : public app::gen::Options {
Extension* extension() { return m_extension; }
Extension::Category category() const {
ASSERT(m_extension);
return m_extension->category();
}
bool isEnabled() const {
ASSERT(m_extension);
return m_extension->isEnabled();
@ -620,7 +645,7 @@ public:
int undo_size_limit_value;
undo_size_limit_value = undoSizeLimit()->textInt();
undo_size_limit_value = MID(0, undo_size_limit_value, 999999);
undo_size_limit_value = base::clamp(undo_size_limit_value, 0, 999999);
m_pref.undo.sizeLimit(undo_size_limit_value);
m_pref.undo.gotoModified(undoGotoModified()->isSelected());
@ -894,12 +919,13 @@ private:
exportAnimationInSequenceAlert()->resetWithDefaultValue();
overwriteFilesOnExportAlert()->resetWithDefaultValue();
overwriteFilesOnExportSpriteSheetAlert()->resetWithDefaultValue();
gifOptionsAlert()->resetWithDefaultValue();
jpegOptionsAlert()->resetWithDefaultValue();
svgOptionsAlert()->resetWithDefaultValue();
advancedModeAlert()->resetWithDefaultValue();
invalidFgBgColorAlert()->resetWithDefaultValue();
runScriptAlert()->resetWithDefaultValue();
gifOptionsAlert()->resetWithDefaultValue();
jpegOptionsAlert()->resetWithDefaultValue();
svgOptionsAlert()->resetWithDefaultValue();
tgaOptionsAlert()->resetWithDefaultValue();
}
void onChangeBgScope() {
@ -1131,16 +1157,51 @@ private:
themeList()->layout();
}
void loadExtensionsByCategory(const Extension::Category category,
const std::string& label) {
bool hasItems = false;
auto sep = new ExtensionCategorySeparator(category, label);
extensionsList()->addChild(sep);
for (auto e : App::instance()->extensions()) {
if (e->category() == category) {
ExtensionItem* item = new ExtensionItem(e);
extensionsList()->addChild(item);
hasItems = true;
}
}
sep->setVisible(hasItems);
}
void loadExtensions() {
// Extensions already loaded
if (extensionsList()->getItemsCount() > 0)
return;
for (auto extension : App::instance()->extensions()) {
ExtensionItem* item = new ExtensionItem(extension);
extensionsList()->addChild(item);
}
extensionsList()->sortItems();
loadExtensionsByCategory(
Extension::Category::Languages,
Strings::options_language_extensions());
loadExtensionsByCategory(
Extension::Category::Themes,
Strings::options_theme_extensions());
#ifdef ENABLE_SCRIPTING
loadExtensionsByCategory(
Extension::Category::Scripts,
Strings::options_script_extensions());
#endif
loadExtensionsByCategory(
Extension::Category::Palettes,
Strings::options_palette_extensions());
loadExtensionsByCategory(
Extension::Category::DitheringMatrices,
Strings::options_dithering_matrix_extensions());
loadExtensionsByCategory(
Extension::Category::Multiple,
Strings::options_multiple_extensions());
onExtensionChange();
extensionsList()->layout();
@ -1297,7 +1358,33 @@ private:
// Add the new extension in the listbox
ExtensionItem* item = new ExtensionItem(ext);
extensionsList()->addChild(item);
extensionsList()->sortItems();
updateCategoryVisibility();
extensionsList()->sortItems(
[](Widget* a, Widget* b){
auto aSep = dynamic_cast<ExtensionCategorySeparator*>(a);
auto bSep = dynamic_cast<ExtensionCategorySeparator*>(b);
auto aItem = dynamic_cast<ExtensionItem*>(a);
auto bItem = dynamic_cast<ExtensionItem*>(b);
auto aCat = (aSep ? aSep->category():
aItem ? aItem->category(): Extension::Category::None);
auto bCat = (bSep ? bSep->category():
bItem ? bItem->category(): Extension::Category::None);
if (aCat < bCat)
return true;
else if (aCat == bCat) {
// There are no two separators with same category.
ASSERT(!(aSep && bSep));
if (aSep && !bSep)
return true;
else if (!aSep && bSep)
return false;
else
return (base::compare_filenames(a->text(), b->text()) < 0);
}
else
return false;
});
extensionsList()->layout();
extensionsList()->selectChild(item);
}
@ -1337,6 +1424,7 @@ private:
void deleteExtensionItem(ExtensionItem* item) {
// Remove the item from the list
extensionsList()->removeChild(item);
updateCategoryVisibility();
extensionsList()->layout();
item->deferDelete();
}
@ -1402,6 +1490,21 @@ private:
return paths;
}
void updateCategoryVisibility() {
bool visibleCategories[int(Extension::Category::Max)];
for (auto& v : visibleCategories)
v = false;
for (auto w : extensionsList()->children()) {
if (auto item = dynamic_cast<ExtensionItem*>(w)) {
visibleCategories[int(item->category())] = true;
}
}
for (auto w : extensionsList()->children()) {
if (auto sep = dynamic_cast<ExtensionCategorySeparator*>(w))
sep->setVisible(visibleCategories[int(sep->category())]);
}
}
Context* m_context;
Preferences& m_pref;
DocumentPreferences& m_globPref;

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -14,6 +14,7 @@
#include "app/commands/params.h"
#include "app/context_access.h"
#include "app/tx.h"
#include "base/clamp.h"
#include "doc/palette.h"
#include "doc/sprite.h"
@ -69,7 +70,7 @@ void PaletteSizeCommand::onExecute(Context* context)
if (ncolors == palette.size())
return;
palette.resize(MID(1, ncolors, std::numeric_limits<int>::max()));
palette.resize(base::clamp(ncolors, 1, std::numeric_limits<int>::max()));
ContextWriter writer(reader);
Tx tx(context, "Palette Size", ModifyDocument);

View File

@ -22,6 +22,7 @@
#include "app/ui/timeline/timeline.h"
#include "app/util/freetype_utils.h"
#include "base/bind.h"
#include "base/clamp.h"
#include "base/fs.h"
#include "base/string.h"
#include "doc/image.h"
@ -80,7 +81,7 @@ public:
int sizeValue() const {
int size = fontSize()->textInt();
size = MID(1, size, 5000);
size = base::clamp(size, 1, 5000);
return size;
}
@ -165,7 +166,7 @@ void PasteTextCommand::onExecute(Context* ctx)
bool antialias = window.antialias()->isSelected();
std::string faceName = window.faceValue();
int size = window.sizeValue();
size = MID(1, size, 999);
size = base::clamp(size, 1, 999);
pref.textTool.fontFace(faceName);
pref.textTool.fontSize(size);
pref.textTool.antialias(antialias);

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
@ -11,6 +12,7 @@
#include "app/app.h"
#include "app/commands/command.h"
#include "app/ui/status_bar.h"
#include "fmt/format.h"
#include "ui/scale.h"
#include "ui/system.h"
#include "ui/theme.h"
@ -47,12 +49,12 @@ void RefreshCommand::onExecute(Context* context)
{
PROCESS_MEMORY_COUNTERS pmc;
if (::GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) {
StatusBar::instance()
->showTip(1000,
"Current memory: %.16g KB (%lu)\n"
"Peak of memory: %.16g KB (%lu)",
pmc.WorkingSetSize / 1024.0, pmc.WorkingSetSize,
pmc.PeakWorkingSetSize / 1024.0, pmc.PeakWorkingSetSize);
StatusBar::instance()->showTip(
1000,
fmt::format("Current memory: {:.2f} MB ({})\n"
"Peak of memory: {:.2f} MB ({})",
pmc.WorkingSetSize / 1024.0 / 1024.0, pmc.WorkingSetSize,
pmc.PeakWorkingSetSize / 1024.0 / 1024.0, pmc.PeakWorkingSetSize));
}
}
#endif

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -18,6 +19,7 @@
#include "app/ui/status_bar.h"
#include "doc/layer.h"
#include "doc/sprite.h"
#include "fmt/format.h"
#include "ui/alert.h"
#include "ui/widget.h"
@ -95,7 +97,7 @@ void RemoveLayerCommand::onExecute(Context* context)
StatusBar::instance()->invalidate();
if (!layerName.empty())
StatusBar::instance()->showTip(1000, "Layer '%s' removed", layerName.c_str());
StatusBar::instance()->showTip(1000, fmt::format("Layer '{}' removed", layerName));
else
StatusBar::instance()->showTip(1000, "Layers removed");
}

View File

@ -21,6 +21,7 @@
#include "doc/selected_objects.h"
#include "doc/slice.h"
#include "doc/sprite.h"
#include "fmt/format.h"
#include "ui/alert.h"
#include "ui/widget.h"
@ -121,10 +122,10 @@ void RemoveSliceCommand::onExecute(Context* context)
StatusBar::instance()->invalidate();
if (!sliceName.empty())
StatusBar::instance()->showTip(
1000, "Slice '%s' removed", sliceName.c_str());
1000, fmt::format("Slice '{}' removed", sliceName));
else
StatusBar::instance()->showTip(
1000, "%d slice(s) removed", slicesToDelete.size());
1000, fmt::format("{} slice(s) removed", slicesToDelete.size()));
}
Command* CommandFactory::createRemoveSliceCommand()

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -223,7 +223,7 @@ void RotateCommand::onExecute(Context* context)
if (cels.empty()) {
StatusBar::instance()->showTip(
1000, Strings::statusbar_tips_all_layers_are_locked().c_str());
1000, Strings::statusbar_tips_all_layers_are_locked());
return;
}
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -248,9 +248,9 @@ void SaveFileBaseCommand::saveDocumentInBackground(
document->incrementVersion();
}
#ifdef ENABLE_UI
StatusBar::instance()
->setStatusText(2000, "File <%s> saved.",
base::get_file_name(filename).c_str());
StatusBar::instance()->setStatusText(
2000, fmt::format("File <{}> saved.",
base::get_file_name(filename)));
#endif
}
}
@ -363,8 +363,12 @@ void SaveFileCopyAsCommand::onExecute(Context* context)
return result;
});
win.remapWindow();
load_window_pos(&win, "ExportFile");
again:;
if (!win.show())
const bool result = win.show();
save_window_pos(&win, "ExportFile");
if (!result)
return;
outputFilename = win.outputFilenameValue();

View File

@ -37,7 +37,7 @@ bool ScrollCenterCommand::onEnabled(Context* context)
void ScrollCenterCommand::onExecute(Context* context)
{
current_editor->setDefaultScroll();
current_editor->setScrollToCenter();
}
Command* CommandFactory::createScrollCenterCommand()

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -21,6 +21,7 @@
#include "app/sprite_job.h"
#include "app/util/resize_image.h"
#include "base/bind.h"
#include "base/clamp.h"
#include "base/convert_to.h"
#include "doc/algorithm/resize_image.h"
#include "doc/cel.h"
@ -35,6 +36,8 @@
#include "sprite_size.xml.h"
#include <algorithm>
#define PERC_FORMAT "%.4g"
namespace app {
@ -129,7 +132,9 @@ protected:
new_mask->replace(
gfx::Rect(
scale_x(document()->mask()->bounds().x-1),
scale_y(document()->mask()->bounds().y-1), MAX(1, w), MAX(1, h)));
scale_y(document()->mask()->bounds().y-1),
std::max(1, w),
std::max(1, h)));
// Always use the nearest-neighbor method to resize the bitmap
// mask.
@ -382,8 +387,8 @@ void SpriteSizeCommand::onExecute(Context* context)
}
#endif // ENABLE_UI
new_width = MID(1, new_width, DOC_SPRITE_MAX_WIDTH);
new_height = MID(1, new_height, DOC_SPRITE_MAX_HEIGHT);
new_width = base::clamp(new_width, 1, DOC_SPRITE_MAX_WIDTH);
new_height = base::clamp(new_height, 1, DOC_SPRITE_MAX_HEIGHT);
{
SpriteSizeJob job(reader, new_width, new_height, resize_method);

View File

@ -116,9 +116,9 @@ void UndoCommand::onExecute(Context* context)
else
msg = "Redid " + undo->nextRedoLabel();
if (Preferences::instance().undo.showTooltip())
statusbar->showTip(1000, msg.c_str());
statusbar->showTip(1000, msg);
else
statusbar->setStatusText(0, msg.c_str());
statusbar->setStatusText(0, msg);
}
#endif // ENABLE_UI

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
@ -71,6 +72,15 @@ Commands* Commands::add(Command* command)
return this;
}
void Commands::remove(Command* command)
{
auto lid = base::string_to_lower(command->id());
auto it = m_commands.find(lid);
ASSERT(it != m_commands.end());
if (it != m_commands.end())
m_commands.erase(it);
}
void Commands::getAllIds(std::vector<std::string>& ids)
{
for (auto& it : m_commands)

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
@ -30,6 +31,9 @@ namespace app {
Command* byId(const char* id);
Commands* add(Command* command);
// Remove the command but doesn't delete it
void remove(Command* command);
void getAllIds(std::vector<std::string>& ids);
private:

View File

@ -21,6 +21,7 @@ FOR_EACH_COMMAND(CropSprite)
FOR_EACH_COMMAND(Despeckle)
FOR_EACH_COMMAND(ExportSpriteSheet)
FOR_EACH_COMMAND(FlattenLayers)
FOR_EACH_COMMAND(Flip)
FOR_EACH_COMMAND(HueSaturation)
FOR_EACH_COMMAND(InvertColor)
FOR_EACH_COMMAND(LayerFromBackground)
@ -69,7 +70,6 @@ FOR_EACH_COMMAND(Exit)
FOR_EACH_COMMAND(Eyedropper)
FOR_EACH_COMMAND(Fill)
FOR_EACH_COMMAND(FitScreen)
FOR_EACH_COMMAND(Flip)
FOR_EACH_COMMAND(FrameProperties)
FOR_EACH_COMMAND(FrameTagProperties)
FOR_EACH_COMMAND(FullscreenPreview)

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2017-2018 David Capello
//
// This program is distributed under the terms of
@ -35,10 +35,12 @@
namespace app {
using Mode = filters::HueSaturationFilter::Mode;
struct HueSaturationParams : public NewParams {
Param<bool> ui { this, true, "ui" };
Param<filters::Target> channels { this, 0, "channels" };
Param<filters::HueSaturationFilter::Mode> mode { this, filters::HueSaturationFilter::Mode::HSL, "mode" };
Param<Mode> mode { this, Mode::HSL_MUL, "mode" };
Param<double> hue { this, 0.0, "hue" };
Param<double> saturation { this, 0.0, "saturation" };
Param<double> lightness { this, 0.0, { "lightness", "value" } };
@ -62,13 +64,17 @@ public:
getContainer()->addChild(&m_colorType);
getContainer()->addChild(&m_sliders);
static_assert(int(Mode::HSV_MUL) == 0 &&
int(Mode::HSL_MUL) == 1 &&
int(Mode::HSV_ADD) == 2 &&
int(Mode::HSL_ADD) == 3,
"Adjust widget showing filters::HueSaturationFilter::Mode enum");
auto mode = Preferences::instance().hueSaturation.mode();
m_colorType.addItem("HSV")->setFocusStop(false);
m_colorType.addItem("HSL")->setFocusStop(false);
if (mode == gen::HueSaturationMode::HSV)
m_colorType.setSelectedItem(0);
else
m_colorType.setSelectedItem(1);
m_colorType.addItem("HSV+")->setFocusStop(false);
m_colorType.addItem("HSL+")->setFocusStop(false);
m_colorType.setSelectedItem(int(mode));
m_colorType.ItemChange.connect(base::Bind<void>(&HueSaturationWindow::onChangeMode, this));
m_sliders.setColorType(app::Color::HslType);
@ -78,24 +84,21 @@ public:
onChangeMode();
}
Mode mode() const {
return Mode(m_colorType.selectedItem());
}
private:
bool isHsl() const {
return (m_colorType.selectedItem() == 1);
return (mode() == Mode::HSL_MUL ||
mode() == Mode::HSL_ADD);
}
void onChangeMode() {
const int isHsl = this->isHsl();
Preferences::instance().hueSaturation.mode
(isHsl ? gen::HueSaturationMode::HSL:
gen::HueSaturationMode::HSV);
m_filter.setMode(isHsl ?
HueSaturationFilter::Mode::HSL:
HueSaturationFilter::Mode::HSV);
m_sliders.setColorType(isHsl ?
Preferences::instance().hueSaturation.mode(mode());
m_filter.setMode(mode());
m_sliders.setColorType(isHsl() ?
app::Color::HslType:
app::Color::HsvType);
@ -139,8 +142,8 @@ public:
HueSaturationCommand();
protected:
bool onEnabled(Context* context) override;
void onExecute(Context* context) override;
bool onEnabled(Context* ctx) override;
void onExecute(Context* ctx) override;
};
HueSaturationCommand::HueSaturationCommand()
@ -148,20 +151,20 @@ HueSaturationCommand::HueSaturationCommand()
{
}
bool HueSaturationCommand::onEnabled(Context* context)
bool HueSaturationCommand::onEnabled(Context* ctx)
{
return context->checkFlags(ContextFlags::ActiveDocumentIsWritable |
ContextFlags::HasActiveSprite);
return ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable |
ContextFlags::HasActiveSprite);
}
void HueSaturationCommand::onExecute(Context* context)
void HueSaturationCommand::onExecute(Context* ctx)
{
#ifdef ENABLE_UI
const bool ui = (params().ui() && context->isUIAvailable());
const bool ui = (params().ui() && ctx->isUIAvailable());
#endif
HueSaturationFilter filter;
FilterManagerImpl filterMgr(context, &filter);
FilterManagerImpl filterMgr(ctx, &filter);
if (params().mode.isSet()) filter.setMode(params().mode());
if (params().hue.isSet()) filter.setHue(params().hue());
if (params().saturation.isSet()) filter.setSaturation(params().saturation() / 100.0);

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -11,6 +11,7 @@
#include "app/commands/filters/color_curve_editor.h"
#include "base/clamp.h"
#include "filters/color_curve.h"
#include "ui/alert.h"
#include "ui/entry.h"
@ -139,8 +140,8 @@ bool ColorCurveEditor::onProcessMessage(Message* msg)
if (m_editPoint) {
gfx::Point mousePos = static_cast<MouseMessage*>(msg)->position();
*m_editPoint = screenToView(mousePos);
m_editPoint->x = MID(m_viewBounds.x, m_editPoint->x, m_viewBounds.x+m_viewBounds.w-1);
m_editPoint->y = MID(m_viewBounds.y, m_editPoint->y, m_viewBounds.y+m_viewBounds.h-1);
m_editPoint->x = base::clamp(m_editPoint->x, m_viewBounds.x, m_viewBounds.x2()-1);
m_editPoint->y = base::clamp(m_editPoint->y, m_viewBounds.y, m_viewBounds.y2()-1);
// TODO this should be optional
CurveEditorChange();
@ -208,9 +209,9 @@ void ColorCurveEditor::onPaint(ui::PaintEvent& ev)
// Draw curve
for (c = client.x; c < client.x+client.w; ++c) {
pt = clientToView(gfx::Point(c, 0));
pt.x = MID(m_viewBounds.x, pt.x, m_viewBounds.x+m_viewBounds.w-1);
pt.x = base::clamp(pt.x, m_viewBounds.x, m_viewBounds.x2()-1);
pt.y = values[pt.x - m_viewBounds.x];
pt.y = MID(m_viewBounds.y, pt.y, m_viewBounds.y+m_viewBounds.h-1);
pt.y = base::clamp(pt.y, m_viewBounds.y, m_viewBounds.y2()-1);
pt = viewToClient(pt);
g->putPixel(gfx::rgba(255, 255, 255), c, pt.y);
@ -269,8 +270,8 @@ bool ColorCurveEditor::editNodeManually(gfx::Point& viewPt)
if (window.closer() == window.ok()) {
viewPt.x = window.x()->textInt();
viewPt.y = window.y()->textInt();
viewPt.x = MID(0, viewPt.x, 255);
viewPt.y = MID(0, viewPt.y, 255);
viewPt.x = base::clamp(viewPt.x, 0, 255);
viewPt.y = base::clamp(viewPt.y, 0, 255);
return true;
}
else if (window.closer() == window.deleteButton()) {

View File

@ -158,10 +158,15 @@ void Param<filters::OutlineFilter::Matrix>::fromString(const std::string& value)
template<>
void Param<filters::HueSaturationFilter::Mode>::fromString(const std::string& value)
{
if (base::utf8_icmp(value, "hsv") == 0)
setValue(filters::HueSaturationFilter::Mode::HSV);
if (base::utf8_icmp(value, "hsv") == 0 ||
base::utf8_icmp(value, "hsv_mul") == 0)
setValue(filters::HueSaturationFilter::Mode::HSV_MUL);
else if (base::utf8_icmp(value, "hsv_add") == 0)
setValue(filters::HueSaturationFilter::Mode::HSV_ADD);
else if (base::utf8_icmp(value, "hsl_add") == 0)
setValue(filters::HueSaturationFilter::Mode::HSL_ADD);
else
setValue(filters::HueSaturationFilter::Mode::HSL);
setValue(filters::HueSaturationFilter::Mode::HSL_MUL);
}
template<>

View File

@ -100,6 +100,11 @@ void Context::setActiveFrame(const doc::frame_t frame)
onSetActiveFrame(frame);
}
void Context::setRange(const DocRange& range)
{
onSetRange(range);
}
void Context::setSelectedColors(const doc::PalettePicks& picks)
{
onSetSelectedColors(picks);
@ -260,6 +265,12 @@ void Context::onSetActiveFrame(const doc::frame_t frame)
activeSiteHandler()->setActiveFrameInDoc(m_lastSelectedDoc, frame);
}
void Context::onSetRange(const DocRange& range)
{
if (m_lastSelectedDoc)
activeSiteHandler()->setRangeInDoc(m_lastSelectedDoc, range);
}
void Context::onSetSelectedColors(const doc::PalettePicks& picks)
{
if (m_lastSelectedDoc)

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -32,6 +32,7 @@ namespace app {
class ActiveSiteHandler;
class Command;
class Doc;
class DocRange;
class DocView;
class Preferences;
@ -88,6 +89,7 @@ namespace app {
void setActiveDocument(Doc* document);
void setActiveLayer(doc::Layer* layer);
void setActiveFrame(doc::frame_t frame);
void setRange(const DocRange& range);
void setSelectedColors(const doc::PalettePicks& picks);
void setSelectedTiles(const doc::PalettePicks& picks);
bool hasModifiedDocuments() const;
@ -112,6 +114,7 @@ namespace app {
virtual void onSetActiveDocument(Doc* doc);
virtual void onSetActiveLayer(doc::Layer* layer);
virtual void onSetActiveFrame(const doc::frame_t frame);
virtual void onSetRange(const DocRange& range);
virtual void onSetSelectedColors(const doc::PalettePicks& picks);
virtual void onSetSelectedTiles(const doc::PalettePicks& picks);
virtual void onCloseDocument(Doc* doc);

View File

@ -15,6 +15,7 @@
#include "app/crash/doc_format.h"
#include "app/crash/internals.h"
#include "app/doc.h"
#include "base/clamp.h"
#include "base/convert_to.h"
#include "base/exception.h"
#include "base/fs.h"
@ -41,6 +42,7 @@
#include "doc/tileset.h"
#include "doc/tileset_io.h"
#include "doc/tilesets.h"
#include "doc/user_data_io.h"
#include "fixmath/fixmath.h"
#include <fstream>
@ -411,52 +413,64 @@ private:
type == ObjectType::LayerTilemap);
std::string name = read_string(s);
std::unique_ptr<Layer> lay;
if (type == ObjectType::LayerImage ||
type == ObjectType::LayerTilemap) {
std::unique_ptr<LayerImage> lay;
switch (type) {
switch (type) {
case ObjectType::LayerImage:
lay.reset(new LayerImage(m_sprite));
break;
case ObjectType::LayerTilemap: {
tileset_index tilesetIndex = read32(s);
lay.reset(new LayerTilemap(m_sprite, tilesetIndex));
break;
case ObjectType::LayerImage:
case ObjectType::LayerTilemap: {
std::unique_ptr<LayerImage> lay;
switch (type) {
case ObjectType::LayerImage:
lay.reset(new LayerImage(m_sprite));
break;
case ObjectType::LayerTilemap: {
tileset_index tilesetIndex = read32(s);
lay.reset(new LayerTilemap(m_sprite, tilesetIndex));
break;
}
}
lay->setName(name);
lay->setFlags(flags);
// Blend mode & opacity
lay->setBlendMode((BlendMode)read16(s));
lay->setOpacity(read8(s));
// Cels
int ncels = read32(s);
for (int i=0; i<ncels; ++i) {
if (canceled())
return nullptr;
// Add a new cel to load in the future after we load all layers
ObjectId celId = read32(s);
m_celsToLoad.push_back(std::make_pair(lay->id(), celId));
}
break;
}
lay->setName(name);
lay->setFlags(flags);
case ObjectType::LayerGroup:
lay.reset(new LayerGroup(m_sprite));
lay->setName(name);
lay->setFlags(flags);
break;
// Blend mode & opacity
lay->setBlendMode((BlendMode)read16(s));
lay->setOpacity(read8(s));
default:
Console().printf("Unable to load layer named '%s', type #%d\n",
name.c_str(), (int)type);
break;
}
// Cels
int ncels = read32(s);
for (int i=0; i<ncels; ++i) {
if (canceled())
return nullptr;
// Add a new cel to load in the future after we load all layers
ObjectId celId = read32(s);
m_celsToLoad.push_back(std::make_pair(lay->id(), celId));
}
if (lay) {
UserData userData = read_user_data(s);
lay->setUserData(userData);
return lay.release();
}
else if (type == ObjectType::LayerGroup) {
std::unique_ptr<LayerGroup> lay(new LayerGroup(m_sprite));
lay->setName(name);
lay->setFlags(flags);
return lay.release();
}
else {
Console().printf("Unable to load layer named '%s', type #%d\n",
name.c_str(), (int)type);
else
return nullptr;
}
}
Cel* readCel(std::ifstream& s) {
@ -559,8 +573,8 @@ Doc* read_document_with_raw_images(const std::string& dir,
info.height = 256;
info.filename = "Unknown";
}
info.width = MID(1, info.width, 99999);
info.height = MID(1, info.height, 99999);
info.width = base::clamp(info.width, 1, 99999);
info.height = base::clamp(info.height, 1, 99999);
Sprite* spr = new Sprite(ImageSpec(info.mode, info.width, info.height), 256);
// Load each image as a new frame

View File

@ -39,6 +39,7 @@
#include "doc/tileset.h"
#include "doc/tileset_io.h"
#include "doc/tilesets.h"
#include "doc/user_data_io.h"
#include "fixmath/fixmath.h"
#include <fstream>
@ -262,6 +263,9 @@ private:
// writeSprite/writeAllLayersID() functions)
break;
}
// Save user data
write_user_data(s, lay->userData());
return true;
}

View File

@ -311,9 +311,9 @@ void Doc::generateMaskBoundaries(const Mask* mask)
ASSERT(mask);
if (!mask->isEmpty()) {
m_maskBoundaries.reset(new MaskBoundaries(mask->bitmap()));
m_maskBoundaries->offset(mask->bounds().x,
mask->bounds().y);
m_maskBoundaries.regen(mask->bitmap());
m_maskBoundaries.offset(mask->bounds().x,
mask->bounds().y);
}
notifySelectionBoundariesChanged();

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -20,6 +20,7 @@
#include "doc/color.h"
#include "doc/document.h"
#include "doc/frame.h"
#include "doc/mask_boundaries.h"
#include "doc/pixel_format.h"
#include "gfx/rect.h"
#include "obs/observable.h"
@ -32,7 +33,6 @@ namespace doc {
class Cel;
class Layer;
class Mask;
class MaskBoundaries;
class Sprite;
class Tileset;
}
@ -145,8 +145,16 @@ namespace app {
void destroyMaskBoundaries();
void generateMaskBoundaries(const Mask* mask = nullptr);
const MaskBoundaries* getMaskBoundaries() const {
return m_maskBoundaries.get();
const MaskBoundaries& maskBoundaries() const {
return m_maskBoundaries;
}
MaskBoundaries& maskBoundaries() {
return m_maskBoundaries;
}
bool hasMaskBoundaries() const {
return !m_maskBoundaries.isEmpty();
}
//////////////////////////////////////////////////////////////////////
@ -219,7 +227,7 @@ namespace app {
Transaction* m_transaction;
// Selected mask region boundaries
std::unique_ptr<doc::MaskBoundaries> m_maskBoundaries;
doc::MaskBoundaries m_maskBoundaries;
// Data to save the file in the same format that it was loaded
FormatOptionsPtr m_format_options;

View File

@ -20,6 +20,7 @@
#include "app/restore_visible_layers.h"
#include "app/snap_to_grid.h"
#include "app/util/autocrop.h"
#include "base/clamp.h"
#include "base/convert_to.h"
#include "base/fs.h"
#include "base/fstream_path.h"
@ -125,7 +126,7 @@ int DocExporter::Item::frames() const
return selFrames->size();
else if (tag) {
int result = tag->toFrame() - tag->fromFrame() + 1;
return MID(1, result, doc->sprite()->totalFrames());
return base::clamp(result, 1, doc->sprite()->totalFrames());
}
else
return doc->sprite()->totalFrames();
@ -138,8 +139,8 @@ doc::SelectedFrames DocExporter::Item::getSelectedFrames() const
doc::SelectedFrames frames;
if (tag) {
frames.insert(MID(0, tag->fromFrame(), doc->sprite()->lastFrame()),
MID(0, tag->toFrame(), doc->sprite()->lastFrame()));
frames.insert(base::clamp(tag->fromFrame(), 0, doc->sprite()->lastFrame()),
base::clamp(tag->toFrame(), 0, doc->sprite()->lastFrame()));
}
else {
frames.insert(0, doc->sprite()->lastFrame());
@ -1182,7 +1183,8 @@ void DocExporter::renderTexture(Context* ctx,
sample.sprite(),
textureImage->pixelFormat(),
render::Dithering(),
nullptr) // TODO add a delegate to show progress
nullptr, // toGray is not needed because the texture is Indexed or RGB
nullptr) // TODO add a delegate to show progress
.execute(ctx);
}

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -10,6 +11,7 @@
#include "app/doc_range.h"
#include "app/util/layer_utils.h"
#include "base/serialization.h"
#include "doc/cel.h"
#include "doc/image.h"
@ -93,6 +95,23 @@ void DocRange::selectLayers(const SelectedLayers& selLayers)
m_selectedLayers.insert(layer);
}
void DocRange::eraseAndAdjust(const Layer* layer)
{
if (!enabled())
return;
if (m_selectingFromLayer)
m_selectingFromLayer = candidate_if_layer_is_deleted(m_selectingFromLayer, layer);
SelectedLayers copy = m_selectedLayers;
m_selectedLayers.clear();
for (Layer* selectedLayer : copy) {
Layer* layerToSelect = candidate_if_layer_is_deleted(selectedLayer, layer);
if (layerToSelect)
m_selectedLayers.insert(layerToSelect);
}
}
bool DocRange::contains(const Layer* layer) const
{
if (enabled())
@ -239,4 +258,42 @@ void DocRange::selectFrameRange(frame_t fromFrame, frame_t toFrame)
m_selectedFrames.insert(fromFrame, toFrame);
}
void DocRange::setType(const Type type)
{
if (type != kNone) {
m_type = type;
m_flags |= type;
}
else {
m_type = kNone;
m_flags = kNone;
}
}
void DocRange::setSelectedLayers(const SelectedLayers& layers)
{
if (layers.empty()) {
m_type = kNone;
m_selectedLayers.clear();
return;
}
m_type = kLayers;
m_flags |= kLayers;
m_selectedLayers = layers;
}
void DocRange::setSelectedFrames(const SelectedFrames& frames)
{
if (frames.empty()) {
m_type = kNone;
m_selectedFrames.clear();
return;
}
m_type = kFrames;
m_flags |= kFrames;
m_selectedFrames = frames;
}
} // namespace app

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -31,6 +32,8 @@ namespace app {
DocRange();
DocRange(Cel* cel);
DocRange(const DocRange&) = default;
DocRange& operator=(const DocRange&) = default;
Type type() const { return m_type; }
bool enabled() const { return m_type != kNone; }
@ -39,6 +42,10 @@ namespace app {
const SelectedLayers& selectedLayers() const { return m_selectedLayers; }
const SelectedFrames& selectedFrames() const { return m_selectedFrames; }
void setType(const Type type);
void setSelectedLayers(const SelectedLayers& layers);
void setSelectedFrames(const SelectedFrames& frames);
void displace(layer_t layerDelta, frame_t frameDelta);
bool contains(const Layer* layer) const;
@ -48,6 +55,12 @@ namespace app {
bool contains(const Layer* layer,
const frame_t frame) const;
// If the range includes the given layer, it will be erased from
// the selection and other candidates might be selected (e.g. a
// sibling, parent, etc.) using the
// candidate_if_layer_is_deleted() function.
void eraseAndAdjust(const Layer* layer);
void clearRange();
void startRange(Layer* fromLayer, frame_t fromFrame, Type type);
void endRange(Layer* toLayer, frame_t toFrame);

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2017-2018 David Capello
//
// This program is distributed under the terms of
@ -10,16 +11,27 @@
#include "app/extensions.h"
#include "app/app.h"
#include "app/app_menus.h"
#include "app/commands/command.h"
#include "app/commands/commands.h"
#include "app/console.h"
#include "app/ini_file.h"
#include "app/load_matrix.h"
#include "app/pref/preferences.h"
#include "app/resource_finder.h"
#include "base/exception.h"
#include "base/file_content.h"
#include "base/file_handle.h"
#include "base/fs.h"
#include "base/fstream_path.h"
#include "render/dithering_matrix.h"
#ifdef ENABLE_SCRIPTING
#include "app/script/engine.h"
#include "app/script/luacpp.h"
#endif
#include "archive.h"
#include "archive_entry.h"
#include "json11.hpp"
@ -35,6 +47,7 @@ namespace {
const char* kPackageJson = "package.json";
const char* kInfoJson = "__info.json";
const char* kPrefLua = "__pref.lua";
const char* kAsepriteDefaultThemeExtensionName = "aseprite-theme";
class ReadArchive {
@ -181,19 +194,27 @@ void write_json_file(const std::string& path, const json11::Json& json)
} // anonymous namespace
const render::DitheringMatrix& Extension::DitheringMatrixInfo::matrix() const
//////////////////////////////////////////////////////////////////////
// Extension
Extension::DitheringMatrixInfo::DitheringMatrixInfo()
{
if (!m_matrix) {
m_matrix = new render::DitheringMatrix;
load_dithering_matrix_from_sprite(m_path, *m_matrix);
}
return *m_matrix;
}
void Extension::DitheringMatrixInfo::destroyMatrix()
Extension::DitheringMatrixInfo::DitheringMatrixInfo(const std::string& path,
const std::string& name)
: m_path(path)
, m_name(name)
{
if (m_matrix)
delete m_matrix;
}
const render::DitheringMatrix& Extension::DitheringMatrixInfo::matrix() const
{
if (!m_loaded) {
m_loaded = true;
load_dithering_matrix_from_sprite(m_path, m_matrix);
}
return m_matrix;
}
Extension::Extension(const std::string& path,
@ -206,32 +227,52 @@ Extension::Extension(const std::string& path,
, m_name(name)
, m_version(version)
, m_displayName(displayName)
, m_category(Category::None)
, m_isEnabled(isEnabled)
, m_isInstalled(true)
, m_isBuiltinExtension(isBuiltinExtension)
{
#ifdef ENABLE_SCRIPTING
m_plugin.pluginRef = LUA_REFNIL;
#endif
}
Extension::~Extension()
{
// Delete all matrices
for (auto& it : m_ditheringMatrices)
it.second.destroyMatrix();
}
void Extension::executeInitActions()
{
#ifdef ENABLE_SCRIPTING
if (isEnabled() && hasScripts())
initScripts();
#endif
}
void Extension::executeExitActions()
{
#ifdef ENABLE_SCRIPTING
if (isEnabled() && hasScripts())
exitScripts();
#endif // ENABLE_SCRIPTING
}
void Extension::addLanguage(const std::string& id, const std::string& path)
{
m_languages[id] = path;
updateCategory(Category::Languages);
}
void Extension::addTheme(const std::string& id, const std::string& path)
{
m_themes[id] = path;
updateCategory(Category::Themes);
}
void Extension::addPalette(const std::string& id, const std::string& path)
{
m_palettes[id] = path;
updateCategory(Category::Palettes);
}
void Extension::addDitheringMatrix(const std::string& id,
@ -239,9 +280,35 @@ void Extension::addDitheringMatrix(const std::string& id,
const std::string& name)
{
DitheringMatrixInfo info(path, name);
m_ditheringMatrices[id] = info;
m_ditheringMatrices[id] = std::move(info);
updateCategory(Category::DitheringMatrices);
}
#ifdef ENABLE_SCRIPTING
void Extension::addCommand(const std::string& id)
{
PluginItem item;
item.type = PluginItem::Command;
item.id = id;
m_plugin.items.push_back(item);
}
void Extension::removeCommand(const std::string& id)
{
for (auto it=m_plugin.items.begin(); it != m_plugin.items.end(); ) {
if (it->type == PluginItem::Command &&
it->id == id) {
it = m_plugin.items.erase(it);
}
else {
++it;
}
}
}
#endif
bool Extension::canBeDisabled() const
{
return (m_isEnabled &&
@ -267,6 +334,17 @@ void Extension::enable(const bool state)
flush_config_file();
m_isEnabled = state;
#ifdef ENABLE_SCRIPTING
if (hasScripts()) {
if (m_isEnabled) {
initScripts();
}
else {
exitScripts();
}
}
#endif // ENABLE_SCRIPTING
}
void Extension::uninstall()
@ -314,6 +392,13 @@ void Extension::uninstallFiles(const std::string& path)
}
}
// Delete __pref.lua file
{
std::string fn = base::join_path(path, kPrefLua);
if (base::is_file(fn))
base::delete_file(fn);
}
std::sort(installedDirs.begin(),
installedDirs.end(),
[](const std::string& a,
@ -369,6 +454,244 @@ bool Extension::isDefaultTheme() const
return (name() == kAsepriteDefaultThemeExtensionName);
}
void Extension::updateCategory(const Category newCategory)
{
if (m_category == Category::None)
m_category = newCategory;
else if (m_category != newCategory)
m_category = Category::Multiple;
}
#ifdef ENABLE_SCRIPTING
// TODO move this to app/script/tableutils.h
static void serialize_table(lua_State* L, int idx, std::string& result)
{
bool first = true;
result.push_back('{');
idx = lua_absindex(L, idx);
lua_pushnil(L);
while (lua_next(L, idx) != 0) {
if (first) {
first = false;
}
else {
result.push_back(',');
}
// Save key
if (lua_type(L, -2) == LUA_TSTRING) {
if (const char* k = lua_tostring(L, -2)) {
result += k;
result.push_back('=');
}
}
// Save value
switch (lua_type(L, -1)) {
case LUA_TNIL:
default:
result += "nil";
break;
case LUA_TBOOLEAN:
if (lua_toboolean(L, -1))
result += "true";
else
result += "false";
break;
case LUA_TNUMBER:
result += lua_tostring(L, -1);
break;
case LUA_TSTRING:
result.push_back('\"');
if (const char* p = lua_tostring(L, -1)) {
for (; *p; ++p) {
switch (*p) {
case '\"':
result.push_back('\\');
result.push_back('\"');
break;
case '\\':
result.push_back('\\');
result.push_back('\\');
break;
case '\t':
result.push_back('\\');
result.push_back('t');
break;
case '\r':
result.push_back('\\');
result.push_back('n');
break;
case '\n':
result.push_back('\\');
result.push_back('n');
break;
default:
result.push_back(*p);
break;
}
}
}
result.push_back('\"');
break;
case LUA_TTABLE:
serialize_table(L, -1, result);
break;
}
lua_pop(L, 1);
}
result.push_back('}');
}
Extension::ScriptItem::ScriptItem(const std::string& fn)
: fn(fn)
, exitFunctionRef(LUA_REFNIL)
{
}
void Extension::initScripts()
{
script::Engine* engine = App::instance()->scriptEngine();
lua_State* L = engine->luaState();
// Put a new "plugin" object for init()/exit() functions
script::push_plugin(L, this);
m_plugin.pluginRef = luaL_ref(L, LUA_REGISTRYINDEX);
// Read plugin.preferences value
{
std::string fn = base::join_path(m_path, kPrefLua);
if (base::is_file(fn)) {
lua_rawgeti(L, LUA_REGISTRYINDEX, m_plugin.pluginRef);
if (luaL_loadfile(L, fn.c_str()) == LUA_OK) {
if (lua_pcall(L, 0, 1, 0) == LUA_OK) {
lua_setfield(L, -2, "preferences");
}
else {
const char* s = lua_tostring(L, -1);
if (s) {
Console().printf("%s\n", s);
}
}
lua_pop(L, 1);
}
else {
lua_pop(L, 1);
}
}
}
for (auto& script : m_plugin.scripts) {
// Reset global init()/exit() functions
engine->evalCode("init=nil exit=nil");
// Eval the code of the script (it should define an init() and an exit() function)
engine->evalFile(script.fn);
if (lua_getglobal(L, "exit") == LUA_TFUNCTION) {
// Save a reference to the exit() function of this script
script.exitFunctionRef = luaL_ref(L, LUA_REGISTRYINDEX);
}
else {
lua_pop(L, 1);
}
// Call the init() function of thi sscript with a Plugin object as first parameter
if (lua_getglobal(L, "init") == LUA_TFUNCTION) {
// Call init(plugin)
lua_rawgeti(L, LUA_REGISTRYINDEX, m_plugin.pluginRef);
lua_pcall(L, 1, 1, 0);
lua_pop(L, 1);
}
else {
lua_pop(L, 1);
}
}
}
void Extension::exitScripts()
{
script::Engine* engine = App::instance()->scriptEngine();
lua_State* L = engine->luaState();
// Call the exit() function of each script
for (auto& script : m_plugin.scripts) {
if (script.exitFunctionRef != LUA_REFNIL) {
// Get the exit() function, the "plugin" object, and call exit(plugin)
lua_rawgeti(L, LUA_REGISTRYINDEX, script.exitFunctionRef);
lua_rawgeti(L, LUA_REGISTRYINDEX, m_plugin.pluginRef);
lua_pcall(L, 1, 0, 0);
luaL_unref(L, LUA_REGISTRYINDEX, script.exitFunctionRef);
script.exitFunctionRef = LUA_REFNIL;
}
}
// Save the plugin preferences object
if (m_plugin.pluginRef != LUA_REFNIL) {
lua_rawgeti(L, LUA_REGISTRYINDEX, m_plugin.pluginRef);
lua_getfield(L, -1, "preferences");
lua_pushnil(L); // Push a nil key, to ask for the first element of the table
bool hasPreferences = (lua_next(L, -2) != 0);
if (hasPreferences)
lua_pop(L, 2); // Remove the value and the key
if (hasPreferences) {
std::string result = "return ";
serialize_table(L, -1, result);
base::write_file_content(
base::join_path(m_path, kPrefLua),
(const uint8_t*)result.c_str(), result.size());
}
lua_pop(L, 2); // Pop preferences table and plugin
luaL_unref(L, LUA_REGISTRYINDEX, m_plugin.pluginRef);
m_plugin.pluginRef = LUA_REFNIL;
}
// Remove plugin items automatically
for (const auto& item : m_plugin.items) {
switch (item.type) {
case PluginItem::Command: {
auto cmds = Commands::instance();
auto cmd = cmds->byId(item.id.c_str());
ASSERT(cmd);
if (cmd) {
#ifdef ENABLE_UI
// TODO use a signal
AppMenus::instance()->removeMenuItemFromGroup(cmd);
#endif // ENABLE_UI
cmds->remove(cmd);
// This will call ~PluginCommand() and unref the command
// onclick callback.
delete cmd;
}
break;
}
}
}
m_plugin.items.clear();
}
void Extension::addScript(const std::string& fn)
{
m_plugin.scripts.push_back(ScriptItem(fn));
updateCategory(Category::Scripts);
}
#endif // ENABLE_SCRIPTING
//////////////////////////////////////////////////////////////////////
// Extensions
Extensions::Extensions()
{
// Create and get the user extensions directory
@ -429,6 +752,22 @@ Extensions::~Extensions()
delete ext;
}
void Extensions::executeInitActions()
{
for (auto& ext : m_extensions)
ext->executeInitActions();
ScriptsChange(nullptr);
}
void Extensions::executeExitActions()
{
for (auto& ext : m_extensions)
ext->executeExitActions();
ScriptsChange(nullptr);
}
std::string Extensions::languagePath(const std::string& langId)
{
for (auto ext : m_extensions) {
@ -540,7 +879,15 @@ ExtensionInfo Extensions::getCompressedExtensionInfo(const std::string& zipFn)
ReadArchive in(zipFn);
archive_entry* entry;
while ((entry = in.readEntry()) != nullptr) {
const std::string entryFn = archive_entry_pathname(entry);
const char* entryFnPtr = archive_entry_pathname(entry);
// This can happen, e.g. if the file contains "!" + unicode chars
// (maybe a bug in archive library?)
// TODO try to find the real cause of this on libarchive
if (!entryFnPtr)
continue;
const std::string entryFn = entryFnPtr;
if (base::get_file_name(entryFn) != kPackageJson)
continue;
@ -577,8 +924,12 @@ Extension* Extensions::installCompressedExtension(const std::string& zipFn,
archive_entry* entry;
while ((entry = in.readEntry()) != nullptr) {
const char* entryFnPtr = archive_entry_pathname(entry);
if (!entryFnPtr)
continue;
// Fix the entry filename to write the file in the disk
std::string fn = archive_entry_pathname(entry);
std::string fn = entryFnPtr;
LOG("EXT: Original filename in zip <%s>...\n", fn.c_str());
@ -739,6 +1090,37 @@ Extension* Extensions::loadExtension(const std::string& path,
extension->addDitheringMatrix(matId, matPath, matName);
}
}
#ifdef ENABLE_SCRIPTING
// Scripts
auto scripts = contributes["scripts"];
if (scripts.is_array()) {
for (const auto& script : scripts.array_items()) {
std::string scriptPath = script["path"].string_value();
if (scriptPath.empty())
continue;
// The path must be always relative to the extension
scriptPath = base::join_path(path, scriptPath);
LOG("EXT: New script '%s'\n", scriptPath.c_str());
extension->addScript(scriptPath);
}
}
// Simple version of packages.json with {... "scripts": "file.lua" ...}
else if (scripts.is_string() &&
!scripts.string_value().empty()) {
std::string scriptPath = scripts.string_value();
// The path must be always relative to the extension
scriptPath = base::join_path(path, scriptPath);
LOG("EXT: New script '%s'\n", scriptPath.c_str());
extension->addScript(scriptPath);
}
#endif // ENABLE_SCRIPTING
}
if (extension)
@ -752,6 +1134,9 @@ void Extensions::generateExtensionSignals(Extension* extension)
if (extension->hasThemes()) ThemesChange(extension);
if (extension->hasPalettes()) PalettesChange(extension);
if (extension->hasDitheringMatrices()) DitheringMatricesChange(extension);
#ifdef ENABLE_SCRIPTING
if (extension->hasScripts()) ScriptsChange(extension);
#endif
}
} // namespace app

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2017-2018 David Capello
//
// This program is distributed under the terms of
@ -9,15 +10,12 @@
#pragma once
#include "obs/signal.h"
#include "render/dithering_matrix.h"
#include <map>
#include <string>
#include <vector>
namespace render {
class DitheringMatrix;
}
namespace app {
// Key=theme/palette/etc. id
@ -36,21 +34,31 @@ namespace app {
class Extension {
friend class Extensions;
public:
enum class Category {
None,
Languages,
Themes,
Scripts,
Palettes,
DitheringMatrices,
Multiple,
Max
};
class DitheringMatrixInfo {
public:
DitheringMatrixInfo() : m_matrix(nullptr) { }
DitheringMatrixInfo();
DitheringMatrixInfo(const std::string& path,
const std::string& name)
: m_path(path), m_name(name), m_matrix(nullptr) { }
const std::string& name);
const std::string& name() const { return m_name; }
const render::DitheringMatrix& matrix() const;
void destroyMatrix();
private:
std::string m_path;
std::string m_name;
mutable render::DitheringMatrix* m_matrix;
mutable render::DitheringMatrix m_matrix;
mutable bool m_loaded = false;
};
Extension(const std::string& path,
@ -61,10 +69,14 @@ namespace app {
const bool isBuiltinExtension);
~Extension();
void executeInitActions();
void executeExitActions();
const std::string& path() const { return m_path; }
const std::string& name() const { return m_name; }
const std::string& version() const { return m_version; }
const std::string& displayName() const { return m_displayName; }
const Category category() const { return m_category; }
const ExtensionItems& languages() const { return m_languages; }
const ExtensionItems& themes() const { return m_themes; }
@ -76,6 +88,10 @@ namespace app {
void addDitheringMatrix(const std::string& id,
const std::string& path,
const std::string& name);
#ifdef ENABLE_SCRIPTING
void addCommand(const std::string& id);
void removeCommand(const std::string& id);
#endif
bool isEnabled() const { return m_isEnabled; }
bool isInstalled() const { return m_isInstalled; }
@ -86,6 +102,10 @@ namespace app {
bool hasThemes() const { return !m_themes.empty(); }
bool hasPalettes() const { return !m_palettes.empty(); }
bool hasDitheringMatrices() const { return !m_ditheringMatrices.empty(); }
#ifdef ENABLE_SCRIPTING
bool hasScripts() const { return !m_plugin.scripts.empty(); }
void addScript(const std::string& fn);
#endif
private:
void enable(const bool state);
@ -93,15 +113,40 @@ namespace app {
void uninstallFiles(const std::string& path);
bool isCurrentTheme() const;
bool isDefaultTheme() const;
void updateCategory(const Category newCategory);
#ifdef ENABLE_SCRIPTING
void initScripts();
void exitScripts();
#endif
ExtensionItems m_languages;
ExtensionItems m_themes;
ExtensionItems m_palettes;
std::map<std::string, DitheringMatrixInfo> m_ditheringMatrices;
#ifdef ENABLE_SCRIPTING
struct ScriptItem {
std::string fn;
int exitFunctionRef;
ScriptItem(const std::string& fn);
};
struct PluginItem {
enum Type { Command };
Type type;
std::string id;
};
struct Plugin {
int pluginRef;
std::vector<ScriptItem> scripts;
std::vector<PluginItem> items;
} m_plugin;
#endif
std::string m_path;
std::string m_name;
std::string m_version;
std::string m_displayName;
Category m_category;
bool m_isEnabled;
bool m_isInstalled;
bool m_isBuiltinExtension;
@ -115,6 +160,9 @@ namespace app {
Extensions();
~Extensions();
void executeInitActions();
void executeExitActions();
iterator begin() { return m_extensions.begin(); }
iterator end() { return m_extensions.end(); }
@ -136,6 +184,7 @@ namespace app {
obs::signal<void(Extension*)> ThemesChange;
obs::signal<void(Extension*)> PalettesChange;
obs::signal<void(Extension*)> DitheringMatricesChange;
obs::signal<void(Extension*)> ScriptsChange;
private:
Extension* loadExtension(const std::string& path,

View File

@ -16,6 +16,7 @@
#include "app/file/format_options.h"
#include "app/pref/preferences.h"
#include "base/cfile.h"
#include "base/clamp.h"
#include "base/exception.h"
#include "base/file_handle.h"
#include "base/fs.h"
@ -1089,8 +1090,8 @@ static void ase_file_write_tags_chunk(FILE* f,
tag->toFrame() < fromFrame)
continue;
frame_t from = MID(0, tag->fromFrame()-fromFrame, toFrame-fromFrame);
frame_t to = MID(from, tag->toFrame()-fromFrame, toFrame-fromFrame);
frame_t from = base::clamp(tag->fromFrame()-fromFrame, 0, toFrame-fromFrame);
frame_t to = base::clamp(tag->toFrame()-fromFrame, from, toFrame-fromFrame);
fputw(from, f);
fputw(to, f);

View File

@ -95,8 +95,6 @@ Doc* load_document(Context* context, const std::string& filename)
}
Doc* document = fop->releaseDocument();
fop.release();
if (document && context)
document->setContext(context);
@ -168,8 +166,8 @@ FileOpROI::FileOpROI(const Doc* doc,
m_selFrames.displace(m_tag->fromFrame());
m_selFrames =
m_selFrames.filter(MAX(0, m_tag->fromFrame()),
MIN(m_tag->toFrame(), doc->sprite()->lastFrame()));
m_selFrames.filter(std::max(0, m_tag->fromFrame()),
std::min(m_tag->toFrame(), doc->sprite()->lastFrame()));
}
// All frames if selected frames is empty
else if (m_selFrames.empty())
@ -838,7 +836,7 @@ void FileOp::operate(IFileOpProgress* progress)
setError(
fmt::format("Save operation is not supported in trial version.\n"
"Go to {} and get the full-version.",
get_app_download_url()));
get_app_download_url()).c_str());
#endif
}
@ -1007,7 +1005,9 @@ void FileOp::postLoad()
void FileOp::setLoadedFormatOptions(const FormatOptionsPtr& opts)
{
ASSERT(!m_formatOptions);
// This assert can fail when we load a sequence of files.
// TODO what we should do, keep the first or the latest format options?
//ASSERT(!m_formatOptions);
m_formatOptions = opts;
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -158,6 +158,10 @@ namespace app {
return opts;
}
bool hasFormatOptionsOfDocument() const {
return (m_document->formatOptions() != nullptr);
}
// Options from the document when it was loaded. This function
// doesn't return nullptr.
template<typename T>
@ -187,6 +191,7 @@ namespace app {
void sequenceGetAlpha(int index, int* a) const;
Image* sequenceImage(PixelFormat pixelFormat, int w, int h);
const Image* sequenceImage() const { return m_seq.image.get(); }
const Palette* sequenceGetPalette() const { return m_seq.palette; }
bool sequenceGetHasAlpha() const {
return m_seq.has_alpha;
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -20,6 +20,7 @@
#include "flic/flic.h"
#include "render/render.h"
#include <algorithm>
#include <cstdio>
namespace app {
@ -76,8 +77,13 @@ bool FliFormat::onLoad(FileOp* fop)
}
// Size by frame
int w = header.width;
int h = header.height;
const int w = header.width;
const int h = header.height;
ASSERT(w > 0 && h > 0); // The decoder cannot return invalid widht/height values
if (w > 10000 || h > 10000) {
fop->setError("Image size too big: %dx%d not suported\n", w, h);
return false;
}
// Create a temporal bitmap
ImageRef bmp(Image::create(IMAGE_INDEXED, w, h));
@ -234,7 +240,7 @@ bool FliFormat::onSave(FileOp* fop)
frame_t frame = *frame_it;
const Palette* pal = sprite->palette(frame);
int size = MIN(256, pal->size());
int size = std::min(256, pal->size());
for (int c=0; c<size; c++) {
color_t color = pal->getEntry(c);
@ -250,7 +256,7 @@ bool FliFormat::onSave(FileOp* fop)
// time that it has in the sprite
if (f < nframes) {
int times = sprite->frameDuration(frame) / header.speed;
times = MAX(1, times);
times = std::max(1, times);
for (int c=0; c<times; c++)
encoder.writeFrame(fliFrame);
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -33,6 +33,8 @@
#include "gif_options.xml.h"
#include <algorithm>
#include <gif_lib.h>
#ifdef _WIN32
@ -52,7 +54,7 @@
#define GIF_TRACE(...)
// GifBitSize can return 9 (it's a bug in giflib)
#define GifBitSizeLimited(v) (MIN(GifBitSize(v), 8))
#define GifBitSizeLimited(v) (std::min(GifBitSize(v), 8))
namespace app {
@ -539,7 +541,7 @@ private:
palette.reset(new Palette(*m_sprite->palette(m_frameNum-1)));
palette->setFrame(m_frameNum);
}
resetRemap(MAX(ncolors, palette->size()));
resetRemap(std::max(ncolors, palette->size()));
// Number of colors in the colormap that are part of the current
// sprite palette.
@ -588,7 +590,7 @@ private:
Palette oldPalette(*palette);
palette->resize(base + missing + (needsExtraBgColor ? 1: 0));
resetRemap(MAX(ncolors, palette->size()));
resetRemap(std::max(ncolors, palette->size()));
for (int i=0; i<ncolors; ++i) {
if (!usedEntries[i])
@ -768,12 +770,13 @@ private:
Image* oldImage = cel->image();
ImageRef newImage(
render::convert_pixel_format
(oldImage, NULL, IMAGE_RGB,
(oldImage, nullptr, IMAGE_RGB,
render::Dithering(),
nullptr,
m_sprite->palette(cel->frame()),
m_opaque,
m_bgIndex));
m_bgIndex,
nullptr));
m_sprite->replaceImage(oldImage->id(), newImage);
}
@ -792,7 +795,7 @@ private:
(m_previousImage.get(), NULL, IMAGE_RGB,
render::Dithering(),
nullptr,
m_sprite->palette(MAX(0, m_frameNum-1)),
m_sprite->palette(std::max(0, m_frameNum-1)),
m_opaque,
m_bgIndex));
@ -906,7 +909,7 @@ public:
if (m_sprite->pixelFormat() == IMAGE_INDEXED) {
for (Palette* palette : m_sprite->getPalettes()) {
int bpp = GifBitSizeLimited(palette->size());
m_bitsPerPixel = MAX(m_bitsPerPixel, bpp);
m_bitsPerPixel = std::max(m_bitsPerPixel, bpp);
}
}
else {
@ -1092,9 +1095,9 @@ private:
// 1/4 of its duration for some strange reason in the Twitter
// conversion from GIF to video.
if (fixDuration)
frameDelay = MAX(2, frameDelay/4);
frameDelay = std::max(2, frameDelay/4);
if (fix_last_frame_duration)
frameDelay = MAX(2, frameDelay);
frameDelay = std::max(2, frameDelay);
extension_bytes[0] = (((int(disposalMethod) & 7) << 2) |
(transparentIndex >= 0 ? 1: 0));

View File

@ -19,6 +19,7 @@
#include "app/find_widget.h"
#include "app/load_widget.h"
#include "app/pref/preferences.h"
#include "base/clamp.h"
#include "base/file_handle.h"
#include "base/memory.h"
#include "doc/doc.h"
@ -356,7 +357,7 @@ bool JpegFormat::onSave(FileOp* fop)
JSAMPARRAY buffer;
JDIMENSION buffer_height;
const auto jpeg_options = std::static_pointer_cast<JpegOptions>(fop->formatOptions());
const int qualityValue = (int)MID(0, 100.0f * jpeg_options->quality, 100);
const int qualityValue = (int)base::clamp(100.0f * jpeg_options->quality, 0.f, 100.f);
int c;

View File

@ -14,6 +14,7 @@
#include "app/file/file.h"
#include "app/file/file_format.h"
#include "app/file/file_formats_manager.h"
#include "base/clamp.h"
#include "base/fs.h"
#include "base/string.h"
#include "dio/detect_format.h"
@ -149,7 +150,7 @@ bool save_palette(const char* filename, const Palette* pal, int columns)
if (!ff || !ff->support(FILE_SUPPORT_SAVE))
break;
int w = (columns > 0 ? MID(0, columns, pal->size()): pal->size());
int w = (columns > 0 ? base::clamp(columns, 0, pal->size()): pal->size());
int h = (pal->size() / w) + (pal->size() % w > 0 ? 1: 0);
Context tmpContext;

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -16,6 +16,7 @@
#include "app/file/format_options.h"
#include "app/file/png_format.h"
#include "app/file/png_options.h"
#include "base/clamp.h"
#include "base/file_handle.h"
#include "doc/doc.h"
#include "gfx/color_space.h"
@ -613,7 +614,7 @@ bool PngFormat::onSave(FileOp* fop)
if (color_type == PNG_COLOR_TYPE_PALETTE) {
int c, r, g, b;
int pal_size = fop->sequenceGetNColors();
pal_size = MID(1, pal_size, PNG_MAX_PALETTE_LENGTH);
pal_size = base::clamp(pal_size, 1, PNG_MAX_PALETTE_LENGTH);
#if PNG_MAX_PALETTE_LENGTH != 256
#error PNG_MAX_PALETTE_LENGTH should be 256

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (c) 2018-2019 Igara Studio S.A.
// Copyright (c) 2018-2020 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -17,6 +17,7 @@
#include "app/file/format_options.h"
#include "app/pref/preferences.h"
#include "base/cfile.h"
#include "base/clamp.h"
#include "base/file_handle.h"
#include "doc/doc.h"
#include "ui/window.h"
@ -81,7 +82,7 @@ bool SvgFormat::onSave(FileOp* fop)
const Image* image = fop->sequenceImage();
int x, y, c, r, g, b, a, alpha;
const auto svg_options = std::static_pointer_cast<SvgOptions>(fop->formatOptions());
const int pixelScaleValue = MID(0, svg_options->pixelScale, 10000);
const int pixelScaleValue = base::clamp(svg_options->pixelScale, 0, 10000);
FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb"));
FILE* f = handle.get();
auto printcol = [f](int x, int y,int r, int g, int b, int a, int pxScale) {

View File

@ -1,24 +1,30 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
//
// tga.c - Based on the code of Tim Gunn, Michal Mertl, Salvador
// Eduardo Tropea and Peter Wang.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/console.h"
#include "app/context.h"
#include "app/doc.h"
#include "app/file/file.h"
#include "app/file/file_format.h"
#include "app/file/format_options.h"
#include "app/file/tga_options.h"
#include "base/cfile.h"
#include "base/convert_to.h"
#include "base/file_handle.h"
#include "doc/doc.h"
#include "doc/image_bits.h"
#include "tga/tga.h"
#include "ui/combobox.h"
#include "ui/listitem.h"
#include "tga_options.xml.h"
namespace app {
@ -45,13 +51,16 @@ class TgaFormat : public FileFormat {
FILE_SUPPORT_RGBA |
FILE_SUPPORT_GRAY |
FILE_SUPPORT_INDEXED |
FILE_SUPPORT_SEQUENCES;
FILE_SUPPORT_SEQUENCES |
FILE_SUPPORT_GET_FORMAT_OPTIONS;
}
bool onLoad(FileOp* fop) override;
#ifdef ENABLE_SAVE
bool onSave(FileOp* fop) override;
#endif
FormatOptionsPtr onAskUserForFormatOptions(FileOp* fop) override;
};
FileFormat* CreateTgaFormat()
@ -59,344 +68,139 @@ FileFormat* CreateTgaFormat()
return new TgaFormat;
}
static void rle_tga_read8(FILE* f, uint8_t* address, int w, int type)
{
uint8_t* end = address + (w * (type == 1 ? 1: 2));
uint8_t value;
int count, g;
int c = 0;
namespace {
do {
count = fgetc(f);
if (count & 0x80) {
count = (count & 0x7F) + 1;
c += count;
value = fgetc(f);
while (count--) {
if (type == 1) {
if (address+1 < end)
*(address++) = value;
}
else {
if (address+2 < end)
*((uint16_t*)address) = graya(value, 255);
address += 2;
}
}
}
else {
count++;
c += count;
if (type == 1) {
if (address+count < end)
fread(address, 1, count, f);
address += count;
}
else {
for (g=0; g<count; g++) {
int c = fgetc(f);
if (address+2 < end)
*((uint16_t*)address) = graya(c, 255);
address += 2;
}
}
}
} while (c < w);
class TgaDelegate : public tga::Delegate {
public:
TgaDelegate(FileOp* fop) : m_fop(fop) { }
bool notifyProgress(double progress) override {
m_fop->setProgress(progress);
return !m_fop->isStop();
}
private:
FileOp* m_fop;
};
bool get_image_spec(const tga::Header& header, ImageSpec& spec)
{
switch (header.imageType) {
case tga::UncompressedIndexed:
case tga::RleIndexed:
if (header.bitsPerPixel != 8)
return false;
spec = ImageSpec(ColorMode::INDEXED,
header.width,
header.height);
return true;
case tga::UncompressedRgb:
case tga::RleRgb:
if (header.bitsPerPixel != 15 &&
header.bitsPerPixel != 16 &&
header.bitsPerPixel != 24 &&
header.bitsPerPixel != 32)
return false;
spec = ImageSpec(ColorMode::RGB,
header.width,
header.height);
return true;
case tga::UncompressedGray:
case tga::RleGray:
if (header.bitsPerPixel != 8)
return false;
spec = ImageSpec(ColorMode::GRAYSCALE,
header.width,
header.height);
return true;
}
return false;
}
static void rle_tga_read32(FILE* f, uint32_t* address, int w, bool withAlpha)
{
uint32_t* end = address + w;
unsigned char value[4];
int count;
int c = 0;
} // anonymous namespace
do {
count = fgetc(f);
if (count & 0x80) {
count = (count & 0x7F) + 1;
c += count;
fread(value, 1, 4, f);
while (count--) {
if (address+1 < end)
*(address++) = rgba(value[2], value[1], value[0],
withAlpha ? value[3]: 255);
}
}
else {
count++;
c += count;
while (count--) {
fread(value, 1, 4, f);
if (address+1 < end)
*(address++) = rgba(value[2], value[1], value[0],
withAlpha ? value[3]: 255);
}
}
} while (c < w);
}
static void rle_tga_read24(FILE* f, uint32_t* address, int w)
{
uint32_t* end = address + w;
unsigned char value[4];
int count;
int c = 0;
do {
count = fgetc(f);
if (count & 0x80) {
count = (count & 0x7F) + 1;
c += count;
fread(value, 1, 3, f);
while (count--) {
if (address+1 < end)
*(address++) = rgba(value[2], value[1], value[0], 255);
}
}
else {
count++;
c += count;
while (count--) {
fread(value, 1, 3, f);
if (address+1 < end)
*(address++) = rgba(value[2], value[1], value[0], 255);
}
}
} while (c < w);
}
static void rle_tga_read16(uint32_t* address, int w, FILE *f)
{
uint32_t* end = address + w;
unsigned int value;
uint32_t color;
int count;
int c = 0;
do {
count = fgetc(f);
if (count & 0x80) {
count = (count & 0x7F) + 1;
c += count;
value = fgetw(f);
color = rgba(scale_5bits_to_8bits((value >> 10) & 0x1F),
scale_5bits_to_8bits((value >> 5) & 0x1F),
scale_5bits_to_8bits(value & 0x1F), 255);
while (count--) {
if (address+1 < end)
*(address++) = color;
}
}
else {
count++;
c += count;
while (count--) {
value = fgetw(f);
color = rgba(scale_5bits_to_8bits((value >> 10) & 0x1F),
scale_5bits_to_8bits((value >> 5) & 0x1F),
scale_5bits_to_8bits(value & 0x1F), 255);
if (address+1 < end)
*(address++) = color;
}
}
} while (c < w);
}
// Loads a 256 color or 24 bit uncompressed TGA file, returning a bitmap
// structure and storing the palette data in the specified palette (this
// should be an array of at least 256 RGB structures).
bool TgaFormat::onLoad(FileOp* fop)
{
unsigned char image_id[256], image_palette[256][3], rgb[4];
unsigned char id_length, palette_type, image_type, palette_entry_size;
unsigned char bpp, descriptor_bits;
short unsigned int palette_colors;
short unsigned int image_width, image_height;
unsigned int c, i, x, y, yc;
int compressed;
FileHandle handle(open_file_with_exception(fop->filename(), "rb"));
FILE* f = handle.get();
id_length = fgetc(f);
palette_type = fgetc(f);
image_type = fgetc(f);
fgetw(f); // first_color
palette_colors = fgetw(f);
palette_entry_size = fgetc(f);
fgetw(f); // "left" field
fgetw(f); // "top" field
image_width = fgetw(f);
image_height = fgetw(f);
bpp = fgetc(f);
descriptor_bits = fgetc(f);
fread(image_id, 1, id_length, f);
if (palette_type == 1) {
for (i=0; i<palette_colors; i++) {
switch (palette_entry_size) {
case 16:
c = fgetw(f);
image_palette[i][0] = scale_5bits_to_8bits(c & 0x1F);
image_palette[i][1] = scale_5bits_to_8bits((c >> 5) & 0x1F);
image_palette[i][2] = scale_5bits_to_8bits((c >> 10) & 0x1F);
break;
case 24:
case 32:
image_palette[i][0] = fgetc(f);
image_palette[i][1] = fgetc(f);
image_palette[i][2] = fgetc(f);
if (palette_entry_size == 32)
fgetc(f);
break;
}
}
}
else if (palette_type != 0) {
tga::StdioFileInterface finterface(handle.get());
tga::Decoder decoder(&finterface);
tga::Header header;
if (!decoder.readHeader(header)) {
fop->setError("Invalid TGA header\n");
return false;
}
/* Image type:
* 0 = no image data
* 1 = uncompressed color mapped
* 2 = uncompressed true color
* 3 = grayscale
* 9 = RLE color mapped
* 10 = RLE true color
* 11 = RLE grayscale
*/
compressed = (image_type & 8);
image_type &= 7;
PixelFormat pixelFormat;
bool withAlpha = false;
switch (image_type) {
/* paletted image */
case 1:
if ((palette_type != 1) || (bpp != 8)) {
return false;
}
for (i=0; i<palette_colors; i++) {
fop->sequenceSetColor(i,
image_palette[i][2],
image_palette[i][1],
image_palette[i][0]);
}
pixelFormat = IMAGE_INDEXED;
break;
/* truecolor image */
case 2:
if ((palette_type != 0) ||
((bpp != 15) && (bpp != 16) &&
(bpp != 24) && (bpp != 32))) {
return false;
}
withAlpha = ((descriptor_bits & 0xf) == 8);
if (withAlpha)
fop->sequenceSetHasAlpha(true);
pixelFormat = IMAGE_RGB;
break;
/* grayscale image */
case 3:
if ((palette_type != 0) || (bpp != 8)) {
return false;
}
for (i=0; i<256; i++)
fop->sequenceSetColor(i, i, i, i);
pixelFormat = IMAGE_GRAYSCALE;
break;
default:
/* TODO add support for more TGA types? */
return false;
ImageSpec spec(ColorMode::RGB, 1, 1);
if (!get_image_spec(header, spec)) {
fop->setError("Unsupported color depth in TGA file: %d bpp, image type=%d.\n",
header.bitsPerPixel,
header.imageType);
return false;
}
Image* image = fop->sequenceImage(pixelFormat, image_width, image_height);
// Palette from TGA file
if (header.hasColormap()) {
const tga::Colormap& pal = header.colormap;
for (int i=0; i<pal.size(); ++i) {
tga::color_t c = pal[i];
fop->sequenceSetColor(i,
tga::getr(c),
tga::getg(c),
tga::getb(c));
if (tga::geta(c) < 255)
fop->sequenceSetAlpha(i, tga::geta(c));
}
}
// Generate grayscale palette
else if (header.isGray()) {
for (int i=0; i<256; ++i)
fop->sequenceSetColor(i, i, i, i);
}
if (decoder.hasAlpha())
fop->sequenceSetHasAlpha(true);
Image* image = fop->sequenceImage((doc::PixelFormat)spec.colorMode(),
spec.width(),
spec.height());
if (!image)
return false;
for (y=image_height; y; y--) {
yc = (descriptor_bits & 0x20) ? image_height-y : y-1;
tga::Image tgaImage;
tgaImage.pixels = image->getPixelAddress(0, 0);
tgaImage.rowstride = image->getRowStrideSize();
tgaImage.bytesPerPixel = image->getRowStrideSize(1);
switch (image_type) {
// Read image
TgaDelegate delegate(fop);
if (!decoder.readImage(header, tgaImage, &delegate)) {
fop->setError("Error loading image data from TGA file.\n");
return false;
}
case 1:
case 3:
if (compressed)
rle_tga_read8(f, image->getPixelAddress(0, yc), image_width, image_type);
else if (image_type == 1)
fread(image->getPixelAddress(0, yc), 1, image_width, f);
else {
for (x=0; x<image_width; x++)
put_pixel_fast<GrayscaleTraits>(image, x, yc, graya(fgetc(f), 255));
}
break;
// Fix alpha values for RGB images
decoder.postProcessImage(header, tgaImage);
case 2:
if (bpp == 32) {
if (compressed) {
rle_tga_read32(f, (uint32_t*)image->getPixelAddress(0, yc), image_width,
withAlpha);
}
else {
for (x=0; x<image_width; x++) {
fread(rgb, 1, 4, f);
put_pixel_fast<RgbTraits>(image, x, yc,
rgba(rgb[2], rgb[1], rgb[0],
withAlpha ? rgb[3]: 255));
}
}
}
else if (bpp == 24) {
if (compressed) {
rle_tga_read24(f, (uint32_t*)image->getPixelAddress(0, yc), image_width);
}
else {
for (x=0; x<image_width; x++) {
fread(rgb, 1, 3, f);
put_pixel_fast<RgbTraits>(image, x, yc, rgba(rgb[2], rgb[1], rgb[0], 255));
}
}
}
else {
if (compressed) {
rle_tga_read16((uint32_t*)image->getPixelAddress(0, yc), image_width, f);
}
else {
for (x=0; x<image_width; x++) {
c = fgetw(f);
put_pixel_fast<RgbTraits>(
image, x, yc, rgba(scale_5bits_to_8bits((c >> 10) & 0x1F),
scale_5bits_to_8bits((c >> 5) & 0x1F),
scale_5bits_to_8bits(c & 0x1F), 255));
}
}
}
break;
}
if (image_height > 1) {
fop->setProgress((float)(image_height-y) / (float)(image_height));
if (fop->isStop())
break;
// Post process gray image pixels (because we use grayscale images
// with alpha).
if (header.isGray()) {
doc::LockImageBits<GrayscaleTraits> bits(image);
for (auto it=bits.begin(), end=bits.end(); it != end; ++it) {
*it = doc::graya(*it, 255);
}
}
if (ferror(f)) {
if (decoder.hasAlpha())
fop->sequenceSetHasAlpha(true);
// Set default options for this TGA
auto opts = std::make_shared<TgaOptions>();
opts->bitsPerPixel(header.bitsPerPixel);
opts->compress(header.isRle());
fop->setLoadedFormatOptions(opts);
if (ferror(handle.get())) {
fop->setError("Error reading file.\n");
return false;
}
@ -406,87 +210,110 @@ bool TgaFormat::onLoad(FileOp* fop)
}
#ifdef ENABLE_SAVE
// Writes a bitmap into a TGA file, using the specified palette (this
// should be an array of at least 256 RGB structures).
namespace {
void prepare_header(tga::Header& header,
const doc::Image* image,
const doc::Palette* palette,
const bool isOpaque,
const bool compressed,
int bitsPerPixel)
{
header.idLength = 0;
header.colormapType = 0;
header.imageType = tga::NoImage;
header.colormapOrigin = 0;
header.colormapLength = 0;
header.colormapDepth = 0;
header.xOrigin = 0;
header.yOrigin = 0;
header.width = image->width();
header.height = image->height();
header.bitsPerPixel = 0;
// TODO make this option configurable
header.imageDescriptor = 0x20; // Top-to-bottom
switch (image->colorMode()) {
case ColorMode::RGB:
header.imageType = (compressed ? tga::RleRgb: tga::UncompressedRgb);
header.bitsPerPixel = (bitsPerPixel > 8 ?
bitsPerPixel:
(isOpaque ? 24: 32));
if (!isOpaque) {
switch (header.bitsPerPixel) {
case 16: header.imageDescriptor |= 1; break;
case 32: header.imageDescriptor |= 8; break;
}
}
break;
case ColorMode::GRAYSCALE:
// TODO if the grayscale is not opaque, we should use RGB,
// this could be done automatically in FileOp in a
// generic way for all formats when FILE_SUPPORT_RGBA is
// available and FILE_SUPPORT_GRAYA isn't.
header.imageType = (compressed ? tga::RleGray: tga::UncompressedGray);
header.bitsPerPixel = 8;
break;
case ColorMode::INDEXED:
ASSERT(palette);
header.imageType = (compressed ? tga::RleIndexed: tga::UncompressedIndexed);
header.bitsPerPixel = 8;
header.colormapType = 1;
header.colormapLength = palette->size();
if (palette->hasAlpha())
header.colormapDepth = 32;
else
header.colormapDepth = 24;
header.colormap = tga::Colormap(palette->size());
for (int i=0; i<palette->size(); ++i) {
doc::color_t c = palette->getEntry(i);
header.colormap[i] =
tga::rgba(doc::rgba_getr(c),
doc::rgba_getg(c),
doc::rgba_getb(c),
doc::rgba_geta(c));
}
break;
}
}
} // anonymous namespace
bool TgaFormat::onSave(FileOp* fop)
{
const Image* image = fop->sequenceImage();
unsigned char image_palette[256][3];
int x, y, c, r, g, b;
int depth = (image->pixelFormat() == IMAGE_RGB) ? 32 : 8;
bool need_pal = (image->pixelFormat() == IMAGE_INDEXED)? true: false;
const Palette* palette = fop->sequenceGetPalette();
FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb"));
FILE* f = handle.get();
tga::StdioFileInterface finterface(handle.get());
tga::Encoder encoder(&finterface);
tga::Header header;
fputc(0, f); /* id length (no id saved) */
fputc((need_pal) ? 1 : 0, f); /* palette type */
/* image type */
fputc((image->pixelFormat() == IMAGE_RGB ) ? 2 :
(image->pixelFormat() == IMAGE_GRAYSCALE) ? 3 :
(image->pixelFormat() == IMAGE_INDEXED ) ? 1 : 0, f);
fputw(0, f); /* first colour */
fputw((need_pal) ? 256 : 0, f); /* number of colours */
fputc((need_pal) ? 24 : 0, f); /* palette entry size */
fputw(0, f); /* left */
fputw(0, f); /* top */
fputw(image->width(), f); /* width */
fputw(image->height(), f); /* height */
fputc(depth, f); /* bits per pixel */
const auto tgaOptions = std::static_pointer_cast<TgaOptions>(fop->formatOptions());
prepare_header(
header, image, palette,
// Is alpha channel required?
fop->document()->sprite()->isOpaque(),
// Compressed by default
(tgaOptions ? tgaOptions->compress(): true),
// Bits per pixel (0 means "calculate what is best")
(tgaOptions ? tgaOptions->bitsPerPixel(): 0));
/* descriptor (bottom to top, 8-bit alpha) */
fputc(image->pixelFormat() == IMAGE_RGB &&
!fop->document()->sprite()->isOpaque() ? 8: 0, f);
encoder.writeHeader(header);
if (need_pal) {
for (y=0; y<256; y++) {
fop->sequenceGetColor(y, &r, &g, &b);
image_palette[y][2] = r;
image_palette[y][1] = g;
image_palette[y][0] = b;
}
fwrite(image_palette, 1, 768, f);
}
tga::Image tgaImage;
tgaImage.pixels = image->getPixelAddress(0, 0);
tgaImage.rowstride = image->getRowStrideSize();
tgaImage.bytesPerPixel = image->getRowStrideSize(1);
switch (image->pixelFormat()) {
TgaDelegate delegate(fop);
encoder.writeImage(header, tgaImage);
encoder.writeFooter();
case IMAGE_RGB:
for (y=image->height()-1; y>=0; y--) {
for (x=0; x<image->width(); x++) {
c = get_pixel(image, x, y);
fputc(rgba_getb(c), f);
fputc(rgba_getg(c), f);
fputc(rgba_getr(c), f);
fputc(rgba_geta(c), f);
}
fop->setProgress((float)(image->height()-y) / (float)(image->height()));
}
break;
case IMAGE_GRAYSCALE:
for (y=image->height()-1; y>=0; y--) {
for (x=0; x<image->width(); x++)
fputc(graya_getv(get_pixel(image, x, y)), f);
fop->setProgress((float)(image->height()-y) / (float)(image->height()));
}
break;
case IMAGE_INDEXED:
for (y=image->height()-1; y>=0; y--) {
for (x=0; x<image->width(); x++)
fputc(get_pixel(image, x, y), f);
fop->setProgress((float)(image->height()-y) / (float)(image->height()));
}
break;
}
const char* tga2_footer = "\0\0\0\0\0\0\0\0TRUEVISION-XFILE.\0";
fwrite(tga2_footer, 1, 26, f);
if (ferror(f)) {
if (ferror(handle.get())) {
fop->setError("Error writing file.\n");
return false;
}
@ -494,6 +321,79 @@ bool TgaFormat::onSave(FileOp* fop)
return true;
}
}
#endif
#endif // ENABLE_SAVE
FormatOptionsPtr TgaFormat::onAskUserForFormatOptions(FileOp* fop)
{
const bool origOpts = fop->hasFormatOptionsOfDocument();
auto opts = fop->formatOptionsOfDocument<TgaOptions>();
#ifdef ENABLE_UI
if (fop->context() && fop->context()->isUIAvailable()) {
try {
auto& pref = Preferences::instance();
// If the TGA options are not original from a TGA file, we can
// use the default options from the preferences.
if (!origOpts) {
if (pref.isSet(pref.tga.bitsPerPixel))
opts->bitsPerPixel(pref.tga.bitsPerPixel());
if (pref.isSet(pref.tga.compress))
opts->compress(pref.tga.compress());
}
if (pref.tga.showAlert()) {
const bool isOpaque = fop->document()->sprite()->isOpaque();
const std::string defBitsPerPixel = (isOpaque ? "24": "32");
app::gen::TgaOptions win;
if (fop->document()->colorMode() == doc::ColorMode::RGB) {
// TODO implement a better way to create ListItems with values
auto newItem = [](const char* s) -> ui::ListItem* {
auto item = new ui::ListItem(s);
item->setValue(s);
return item;
};
win.bitsPerPixel()->addItem(newItem("16"));
win.bitsPerPixel()->addItem(newItem("24"));
win.bitsPerPixel()->addItem(newItem("32"));
std::string v = defBitsPerPixel;
if (opts->bitsPerPixel() > 0)
v = base::convert_to<std::string>(opts->bitsPerPixel());
win.bitsPerPixel()->setValue(v);
}
else {
win.bitsPerPixelLabel()->setVisible(false);
win.bitsPerPixel()->setVisible(false);
}
win.compress()->setSelected(opts->compress());
win.openWindowInForeground();
if (win.closer() == win.ok()) {
int bpp = base::convert_to<int>(win.bitsPerPixel()->getValue());
pref.tga.bitsPerPixel(bpp);
pref.tga.compress(win.compress()->isSelected());
pref.tga.showAlert(!win.dontShow()->isSelected());
opts->bitsPerPixel(pref.tga.bitsPerPixel());
opts->compress(pref.tga.compress());
}
else {
opts.reset();
}
}
}
catch (std::exception& e) {
Console::showException(e);
return std::shared_ptr<TgaOptions>(nullptr);
}
}
#endif // ENABLE_UI
return opts;
}
} // namespace app

View File

@ -0,0 +1,30 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_FILE_TGA_OPTIONS_H_INCLUDED
#define APP_FILE_TGA_OPTIONS_H_INCLUDED
#pragma once
#include "app/file/format_options.h"
namespace app {
class TgaOptions : public FormatOptions {
public:
int bitsPerPixel() const { return m_bitsPerPixel; }
bool compress() const { return m_compress; }
void bitsPerPixel(int bpp) { m_bitsPerPixel = bpp; }
void compress(bool state) { m_compress = state; }
private:
int m_bitsPerPixel = 0;
bool m_compress = true;
};
} // namespace app
#endif

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2015-2018 David Capello
// Copyright (C) 2015 Gabriel Rauter
//
@ -21,6 +21,7 @@
#include "app/ini_file.h"
#include "app/pref/preferences.h"
#include "base/bind.h"
#include "base/clamp.h"
#include "base/convert_to.h"
#include "base/file_handle.h"
#include "doc/doc.h"
@ -242,8 +243,8 @@ static int progress_report(int percent, const WebPPicture* pic)
FileOp* fop = wd->fop;
double newProgress = (double(wd->f) + double(percent)/100.0) / double(wd->n);
wd->progress = MAX(wd->progress, newProgress);
wd->progress = MID(0.0, wd->progress, 1.0);
wd->progress = std::max(wd->progress, newProgress);
wd->progress = base::clamp(wd->progress, 0.0, 1.0);
fop->setProgress(wd->progress);
if (fop->isStop())

View File

@ -21,12 +21,14 @@ std::string XmlTranslator::operator()(const TiXmlElement* elem,
const char* value = elem->Attribute(attrName);
if (!value)
return std::string();
else if (value[0] == '@') {
else if (value[0] == '@') { // Translate string
if (value[1] == '.')
return Strings::instance()->translate((m_stringIdPrefix + (value+1)).c_str());
else
return Strings::instance()->translate(value+1);
}
else if (value[0] == '!') // Raw string
return std::string(value+1);
else
return std::string(value);
}

View File

@ -30,6 +30,8 @@
#include "ui/system.h"
#include "ui/theme.h"
#include <algorithm>
namespace app {
using namespace app::skin;
@ -183,7 +185,7 @@ void draw_alpha_slider(ui::Graphics* g,
const gfx::Rect& rc,
const app::Color& color)
{
const int xmax = MAX(1, rc.w-1);
const int xmax = std::max(1, rc.w-1);
const doc::color_t c =
(color.getType() != app::Color::MaskType ?
doc::rgba(color.getRed(),
@ -210,7 +212,7 @@ void draw_alpha_slider(os::Surface* s,
const gfx::Rect& rc,
const app::Color& color)
{
const int xmax = MAX(1, rc.w-1);
const int xmax = std::max(1, rc.w-1);
const doc::color_t c =
(color.getType() != app::Color::MaskType ?
doc::rgba(color.getRed(),

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -121,7 +121,7 @@ static bool create_main_display(bool gpuAccel,
try {
if (w > 0 && h > 0) {
main_display = os::instance()->createDisplay(
w, h, (scale == 0 ? 2: MID(1, scale, 4)));
w, h, (scale == 0 ? 2: base::clamp(scale, 1, 4)));
}
}
catch (const os::DisplayCreationException& e) {

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -64,6 +64,13 @@ Preferences::Preferences()
});
doc::Sprite::SetDefaultGridBounds(defPref.grid.bounds());
// Reset confusing defaults for a new instance of the program.
defPref.grid.snap(false);
if (selection.mode() != gen::SelectionMode::DEFAULT &&
selection.mode() != gen::SelectionMode::ADD) {
selection.mode(gen::SelectionMode::DEFAULT);
}
// Hide the menu bar depending on:
// 1. this is the first run of the program
// 2. the native menu bar is available
@ -166,6 +173,10 @@ DocumentPreferences& Preferences::document(const Doc* doc)
// Load specific settings of this document
serializeDocPref(doc, docPref, false);
// Turn off snap to grid setting (it's confusing for most of the
// users to load this setting).
docPref->grid.snap.setValueAndDefault(false);
return *docPref;
}
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -26,6 +26,7 @@
#include "doc/frame.h"
#include "doc/layer_list.h"
#include "doc/sprite.h"
#include "filters/hue_saturation_filter.h"
#include "filters/tiled_mode.h"
#include "gfx/rect.h"
#include "render/onionskin_position.h"
@ -55,7 +56,6 @@ namespace app {
Preferences();
~Preferences();
void load();
void save();
// Returns true if the given option was set by the user or false
@ -80,6 +80,7 @@ namespace app {
void onRemoveDocument(Doc* doc) override;
private:
void load();
std::string docConfigFileName(const Doc* doc);
void serializeDocPref(const Doc* doc, app::DocumentPreferences* docPref, bool save);

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2018-2020 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -10,6 +10,6 @@
// Increment this value if the scripting API is modified between two
// released Aseprite versions.
#define API_VERSION 9
#define API_VERSION 11
#endif

View File

@ -335,7 +335,13 @@ int App_useTool(lua_State* L)
while (lua_next(L, -2) != 0) {
gfx::Point pt = convert_args_into_point(L, -1);
tools::Pointer pointer(pt, tools::Pointer::Button::Left);
tools::Pointer pointer(
pt,
// TODO configurable params
tools::Vec2(0.0f, 0.0f),
tools::Pointer::Button::Left,
tools::Pointer::Type::Unknown,
0.0f);
if (first) {
first = false;
manager.prepareLoop(pointer);

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2018 David Capello
//
// This program is distributed under the terms of
@ -27,6 +27,7 @@
#include "ui/entry.h"
#include "ui/grid.h"
#include "ui/label.h"
#include "ui/manager.h"
#include "ui/separator.h"
#include "ui/slider.h"
#include "ui/window.h"
@ -50,7 +51,9 @@ struct Dialog {
ui::VBox vbox;
ui::Grid grid;
ui::HBox* hbox = nullptr;
bool autoNewRow = false;
std::map<std::string, ui::Widget*> dataWidgets;
std::map<std::string, ui::Widget*> labelWidgets;
int currentRadioGroup = 0;
// Used to create a new row when a different kind of widget is added
@ -101,13 +104,33 @@ struct Dialog {
}
}
Widget* findDataWidgetById(const char* id) {
auto it = dataWidgets.find(id);
if (it != dataWidgets.end())
return it->second;
else
return nullptr;
}
void setLabelVisibility(const char* id, bool visible) {
auto it = labelWidgets.find(id);
if (it != labelWidgets.end())
it->second->setVisible(visible);
}
void setLabelText(const char* id, const char* text) {
auto it = labelWidgets.find(id);
if (it != labelWidgets.end())
it->second->setText(text);
}
};
template<typename Signal,
template<typename...Args,
typename Callback>
void Dialog_connect_signal(lua_State* L,
int dlgIdx,
Signal& signal,
obs::signal<void(Args...)>& signal,
Callback callback)
{
auto dlg = get_obj<Dialog>(L, dlgIdx);
@ -124,7 +147,7 @@ void Dialog_connect_signal(lua_State* L,
lua_pop(L, 1); // Pop the uservalue
signal.connect(
base::Bind<void>([=]() {
[=](Args...args) {
// In case that the dialog is hidden, we cannot access to the
// global LUA_REGISTRYINDEX to get its reference.
if (dlg->showRef == LUA_REFNIL)
@ -141,7 +164,7 @@ void Dialog_connect_signal(lua_State* L,
// lua_pcall() (that table is like an "event data" parameter
// for the function).
lua_newtable(L);
callback(L);
callback(L, std::forward<Args>(args)...);
if (lua_isfunction(L, -2)) {
if (lua_pcall(L, 1, 0, 0)) {
@ -164,7 +187,7 @@ void Dialog_connect_signal(lua_State* L,
->scriptEngine()
->consolePrint(ex.what());
}
}));
});
}
int Dialog_new(lua_State* L)
@ -193,7 +216,7 @@ int Dialog_new(lua_State* L)
if (type == LUA_TFUNCTION) {
Dialog_connect_signal(
L, -2, dlg->window.Close,
[](lua_State* L){
[](lua_State*, CloseEvent&){
// Do nothing
});
}
@ -261,10 +284,13 @@ int Dialog_add_widget(lua_State* L, Widget* widget)
{
auto dlg = get_obj<Dialog>(L, 1);
const char* label = nullptr;
std::string id;
bool visible = true;
// This is to separate different kind of widgets without label in
// different rows.
if (dlg->lastWidgetType != widget->type()) {
if (dlg->lastWidgetType != widget->type() ||
dlg->autoNewRow) {
dlg->lastWidgetType = widget->type();
dlg->hbox = nullptr;
}
@ -273,8 +299,9 @@ int Dialog_add_widget(lua_State* L, Widget* widget)
// Widget ID (used to fill the Dialog_get_data table then)
int type = lua_getfield(L, 2, "id");
if (type == LUA_TSTRING) {
if (auto id = lua_tostring(L, -1)) {
widget->setId(id);
if (auto s = lua_tostring(L, -1)) {
id = s;
widget->setId(s);
dlg->dataWidgets[id] = widget;
}
}
@ -282,20 +309,41 @@ int Dialog_add_widget(lua_State* L, Widget* widget)
// Label
type = lua_getfield(L, 2, "label");
if (type == LUA_TSTRING)
if (type != LUA_TNIL)
label = lua_tostring(L, -1);
lua_pop(L, 1);
// Focus magnet
type = lua_getfield(L, 2, "focus");
if (type != LUA_TNONE && lua_toboolean(L, -1))
if (type != LUA_TNIL && lua_toboolean(L, -1))
widget->setFocusMagnet(true);
lua_pop(L, 1);
// Enabled
type = lua_getfield(L, 2, "enabled");
if (type != LUA_TNIL)
widget->setEnabled(lua_toboolean(L, -1));
lua_pop(L, 1);
// Visible
type = lua_getfield(L, 2, "visible");
if (type != LUA_TNIL) {
visible = lua_toboolean(L, -1);
widget->setVisible(visible);
}
lua_pop(L, 1);
}
if (label || !dlg->hbox) {
if (label)
dlg->grid.addChildInCell(new ui::Label(label), 1, 1, ui::LEFT | ui::TOP);
if (label) {
auto labelWidget = new ui::Label(label);
if (!visible)
labelWidget->setVisible(false);
dlg->grid.addChildInCell(labelWidget, 1, 1, ui::LEFT | ui::TOP);
if (!id.empty())
dlg->labelWidgets[id] = labelWidget;
}
else
dlg->grid.addChildInCell(new ui::HBox, 1, 1, ui::LEFT | ui::TOP);
@ -317,6 +365,20 @@ int Dialog_newrow(lua_State* L)
{
auto dlg = get_obj<Dialog>(L, 1);
dlg->hbox = nullptr;
dlg->autoNewRow = false;
if (lua_istable(L, 2)) {
// Dialog:newrow{ always }
const int type = lua_getfield(L, 2, "always");
if (type != LUA_TNONE) {
if (type == LUA_TNIL ||
lua_toboolean(L, -1)) {
dlg->autoNewRow = true;
}
lua_pop(L, 1);
}
}
lua_pushvalue(L, 1);
return 1;
}
@ -325,7 +387,8 @@ int Dialog_separator(lua_State* L)
{
auto dlg = get_obj<Dialog>(L, 1);
std::string text;
std::string id, text;
if (lua_isstring(L, 2)) {
if (auto p = lua_tostring(L, 2))
text = p;
@ -337,9 +400,21 @@ int Dialog_separator(lua_State* L)
text = p;
}
lua_pop(L, 1);
type = lua_getfield(L, 2, "id");
if (type == LUA_TSTRING) {
if (auto s = lua_tostring(L, -1))
id = s;
}
lua_pop(L, 1);
}
auto widget = new ui::Separator(text, ui::HORIZONTAL);
if (!id.empty()) {
widget->setId(id.c_str());
dlg->dataWidgets[id] = widget;
}
dlg->grid.addChildInCell(widget, 2, 1, ui::HORIZONTAL | ui::TOP);
dlg->hbox = nullptr;
@ -352,7 +427,7 @@ int Dialog_label(lua_State* L)
std::string text;
if (lua_istable(L, 2)) {
int type = lua_getfield(L, 2, "text");
if (type == LUA_TSTRING) {
if (type != LUA_TNIL) {
if (auto p = lua_tostring(L, -1))
text = p;
}
@ -369,7 +444,7 @@ int Dialog_button_base(lua_State* L, T** outputWidget = nullptr)
std::string text;
if (lua_istable(L, 2)) {
int type = lua_getfield(L, 2, "text");
if (type == LUA_TSTRING) {
if (type != LUA_TNIL) {
if (auto p = lua_tostring(L, -1))
text = p;
}
@ -386,7 +461,7 @@ int Dialog_button_base(lua_State* L, T** outputWidget = nullptr)
if (lua_istable(L, 2)) {
int type = lua_getfield(L, 2, "selected");
if (type != LUA_TNONE)
if (type != LUA_TNIL)
widget->setSelected(lua_toboolean(L, -1));
lua_pop(L, 1);
@ -395,8 +470,9 @@ int Dialog_button_base(lua_State* L, T** outputWidget = nullptr)
auto dlg = get_obj<Dialog>(L, 1);
Dialog_connect_signal(
L, 1, widget->Click,
[dlg, widget](lua_State* L){
dlg->lastButton = widget;
[dlg, widget](lua_State* L, Event&){
if (widget->type() == ui::kButtonWidget)
dlg->lastButton = widget;
});
closeWindowByDefault = false;
}
@ -457,6 +533,19 @@ int Dialog_entry(lua_State* L)
}
auto widget = new ui::Entry(4096, text.c_str());
if (lua_istable(L, 2)) {
int type = lua_getfield(L, 2, "onchange");
if (type == LUA_TFUNCTION) {
Dialog_connect_signal(
L, 1, widget->Change,
[](lua_State* L){
// Do nothing
});
}
lua_pop(L, 1);
}
return Dialog_add_widget(L, widget);
}
@ -473,11 +562,20 @@ int Dialog_number(lua_State* L)
lua_pop(L, 1);
type = lua_getfield(L, 2, "decimals");
if (type != LUA_TNONE &&
type != LUA_TNIL) {
if (type != LUA_TNIL) {
widget->setDecimals(lua_tointegerx(L, -1, nullptr));
}
lua_pop(L, 1);
type = lua_getfield(L, 2, "onchange");
if (type == LUA_TFUNCTION) {
Dialog_connect_signal(
L, 1, widget->Change,
[](lua_State* L){
// Do nothing
});
}
lua_pop(L, 1);
}
return Dialog_add_widget(L, widget);
@ -491,25 +589,48 @@ int Dialog_slider(lua_State* L)
if (lua_istable(L, 2)) {
int type = lua_getfield(L, 2, "min");
if (type != LUA_TNONE) {
if (type != LUA_TNIL) {
min = lua_tointegerx(L, -1, nullptr);
}
lua_pop(L, 1);
type = lua_getfield(L, 2, "max");
if (type != LUA_TNONE) {
if (type != LUA_TNIL) {
max = lua_tointegerx(L, -1, nullptr);
}
lua_pop(L, 1);
type = lua_getfield(L, 2, "value");
if (type != LUA_TNONE) {
if (type != LUA_TNIL) {
value = lua_tointegerx(L, -1, nullptr);
}
lua_pop(L, 1);
}
auto widget = new ui::Slider(min, max, value);
if (lua_istable(L, 2)) {
int type = lua_getfield(L, 2, "onchange");
if (type == LUA_TFUNCTION) {
Dialog_connect_signal(
L, 1, widget->Change,
[](lua_State* L){
// Do nothing
});
}
lua_pop(L, 1);
type = lua_getfield(L, 2, "onrelease");
if (type == LUA_TFUNCTION) {
Dialog_connect_signal(
L, 1, widget->SliderReleased,
[](lua_State* L){
// Do nothing
});
}
lua_pop(L, 1);
}
return Dialog_add_widget(L, widget);
}
@ -538,6 +659,16 @@ int Dialog_combobox(lua_State* L)
}
}
lua_pop(L, 1);
type = lua_getfield(L, 2, "onchange");
if (type == LUA_TFUNCTION) {
Dialog_connect_signal(
L, 1, widget->Change,
[](lua_State* L){
// Do nothing
});
}
lua_pop(L, 1);
}
return Dialog_add_widget(L, widget);
@ -555,6 +686,19 @@ int Dialog_color(lua_State* L)
auto widget = new ColorButton(color,
app_get_current_pixel_format(),
ColorButtonOptions());
if (lua_istable(L, 2)) {
int type = lua_getfield(L, 2, "onchange");
if (type == LUA_TFUNCTION) {
Dialog_connect_signal(
L, 1, widget->Change,
[](lua_State* L, const app::Color& color){
push_obj<app::Color>(L, color);
lua_setfield(L, -2, "color");
});
}
}
return Dialog_add_widget(L, widget);
}
@ -595,7 +739,10 @@ int Dialog_shades(lua_State* L)
if (type == LUA_TFUNCTION) {
Dialog_connect_signal(
L, 1, widget->Click,
[widget](lua_State* L){
[widget](lua_State* L, ColorShades::ClickEvent& ev){
lua_pushinteger(L, (int)ev.button());
lua_setfield(L, -2, "button");
const int i = widget->getHotEntry();
const Shade shade = widget->getShade();
if (i >= 0 && i < int(shade.size())) {
@ -682,6 +829,173 @@ int Dialog_file(lua_State* L)
return Dialog_add_widget(L, widget);
}
int Dialog_modify(lua_State* L)
{
auto dlg = get_obj<Dialog>(L, 1);
if (lua_istable(L, 2)) {
const char* id = nullptr;
bool relayout = false;
int type = lua_getfield(L, 2, "id");
if (type != LUA_TNIL)
id = lua_tostring(L, -1);
lua_pop(L, 1);
// Modify window itself when no ID is specified
if (id == nullptr) {
// "title" or "text" is the same for dialogs
type = lua_getfield(L, 2, "title");
if (type == LUA_TNIL) {
lua_pop(L, 1);
type = lua_getfield(L, 2, "text");
}
if (const char* s = lua_tostring(L, -1)) {
dlg->window.setText(s);
relayout = true;
}
lua_pop(L, 1);
return 0;
}
// Here we could use dlg->window.findChild(id) but why not use the
// map directly (it should be faster than iterating over all
// children).
Widget* widget = dlg->findDataWidgetById(id);
if (!widget)
return luaL_error(L, "Given id=\"%s\" in Dialog:modify{} not found in dialog", id);
type = lua_getfield(L, 2, "enabled");
if (type != LUA_TNIL)
widget->setEnabled(lua_toboolean(L, -1));
lua_pop(L, 1);
type = lua_getfield(L, 2, "selected");
if (type != LUA_TNIL)
widget->setSelected(lua_toboolean(L, -1));
lua_pop(L, 1);
type = lua_getfield(L, 2, "visible");
if (type != LUA_TNIL) {
bool state = lua_toboolean(L, -1);
widget->setVisible(state);
dlg->setLabelVisibility(id, state);
relayout = true;
}
lua_pop(L, 1);
type = lua_getfield(L, 2, "text");
if (const char* s = lua_tostring(L, -1)) {
widget->setText(s);
relayout = true;
}
lua_pop(L, 1);
type = lua_getfield(L, 2, "label");
if (const char* s = lua_tostring(L, -1)) {
dlg->setLabelText(id, s);
relayout = true;
}
lua_pop(L, 1);
type = lua_getfield(L, 2, "focus");
if (type != LUA_TNIL && lua_toboolean(L, -1)) {
widget->requestFocus();
relayout = true;
}
lua_pop(L, 1);
type = lua_getfield(L, 2, "decimals");
if (type != LUA_TNIL) {
if (auto expr = dynamic_cast<ExprEntry*>(widget)) {
expr->setDecimals(lua_tointegerx(L, -1, nullptr));
}
}
lua_pop(L, 1);
type = lua_getfield(L, 2, "min");
if (type != LUA_TNIL) {
if (auto slider = dynamic_cast<ui::Slider*>(widget)) {
slider->setRange(lua_tointegerx(L, -1, nullptr), slider->getMaxValue());
}
}
lua_pop(L, 1);
type = lua_getfield(L, 2, "max");
if (type != LUA_TNIL) {
if (auto slider = dynamic_cast<ui::Slider*>(widget)) {
slider->setRange(slider->getMinValue(), lua_tointegerx(L, -1, nullptr));
}
}
lua_pop(L, 1);
type = lua_getfield(L, 2, "value");
if (type != LUA_TNIL) {
if (auto slider = dynamic_cast<ui::Slider*>(widget)) {
slider->setValue(lua_tointegerx(L, -1, nullptr));
}
}
lua_pop(L, 1);
type = lua_getfield(L, 2, "option");
if (auto p = lua_tostring(L, -1)) {
if (auto combobox = dynamic_cast<ui::ComboBox*>(widget)) {
int index = combobox->findItemIndex(p);
if (index >= 0)
combobox->setSelectedItemIndex(index);
}
}
lua_pop(L, 1);
type = lua_getfield(L, 2, "color");
if (type != LUA_TNIL) {
if (auto colorButton = dynamic_cast<ColorButton*>(widget)) {
colorButton->setColor(convert_args_into_color(L, -1));
}
}
lua_pop(L, 1);
type = lua_getfield(L, 2, "colors");
if (type != LUA_TNIL) {
if (auto colorShade = dynamic_cast<ColorShades*>(widget)) {
Shade shade;
if (lua_istable(L, -1)) {
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
app::Color color = convert_args_into_color(L, -1);
shade.push_back(color);
lua_pop(L, 1);
}
}
colorShade->setShade(shade);
}
}
lua_pop(L, 1);
type = lua_getfield(L, 2, "filename");
if (auto p = lua_tostring(L, -1)) {
if (auto filenameField = dynamic_cast<FilenameField*>(widget)) {
filenameField->setFilename(p);
}
}
lua_pop(L, 1);
// TODO combobox options? shades mode? file title / open / save / filetypes? on* events?
if (relayout) {
dlg->window.layout();
gfx::Rect origBounds = dlg->window.bounds();
gfx::Rect bounds = origBounds;
bounds.h = dlg->window.sizeHint().h;
dlg->window.setBounds(bounds);
dlg->window.manager()->invalidateRect(origBounds);
}
}
lua_pushvalue(L, 1);
return 1;
}
int Dialog_get_data(lua_State* L)
{
auto dlg = get_obj<Dialog>(L, 1);
@ -689,6 +1003,9 @@ int Dialog_get_data(lua_State* L)
for (const auto& kv : dlg->dataWidgets) {
const ui::Widget* widget = kv.second;
switch (widget->type()) {
case ui::kSeparatorWidget:
// Do nothing
continue;
case ui::kButtonWidget:
case ui::kCheckWidget:
case ui::kRadioWidget:
@ -779,6 +1096,9 @@ int Dialog_set_data(lua_State* L)
ui::Widget* widget = kv.second;
switch (widget->type()) {
case ui::kSeparatorWidget:
// Do nothing
break;
case ui::kButtonWidget:
case ui::kCheckWidget:
case ui::kRadioWidget:
@ -891,6 +1211,7 @@ const luaL_Reg Dialog_methods[] = {
{ "color", Dialog_color },
{ "shades", Dialog_shades },
{ "file", Dialog_file },
{ "modify", Dialog_modify },
{ nullptr, nullptr }
};

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -27,6 +27,7 @@
#include "doc/blend_mode.h"
#include "doc/color_mode.h"
#include "filters/target.h"
#include "ui/mouse_button.h"
#include <fstream>
#include <sstream>
@ -162,6 +163,7 @@ void register_layer_class(lua_State* L);
void register_layers_class(lua_State* L);
void register_palette_class(lua_State* L);
void register_palettes_class(lua_State* L);
void register_plugin_class(lua_State* L);
void register_point_class(lua_State* L);
void register_range_class(lua_State* L);
void register_rect_class(lua_State* L);
@ -346,6 +348,17 @@ Engine::Engine()
setfield_integer(L, "GRAYA", TARGET_GRAY_CHANNEL | TARGET_ALPHA_CHANNEL);
lua_pop(L, 1);
lua_newtable(L);
lua_pushvalue(L, -1);
lua_setglobal(L, "MouseButton");
setfield_integer(L, "NONE", (int)ui::kButtonNone);
setfield_integer(L, "LEFT", (int)ui::kButtonLeft);
setfield_integer(L, "RIGHT", (int)ui::kButtonRight);
setfield_integer(L, "MIDDLE", (int)ui::kButtonMiddle);
setfield_integer(L, "X1", (int)ui::kButtonX1);
setfield_integer(L, "X2", (int)ui::kButtonX2);
lua_pop(L, 1);
lua_newtable(L);
lua_pushvalue(L, -1);
lua_setglobal(L, "TilesetMode");
@ -372,6 +385,7 @@ Engine::Engine()
register_layers_class(L);
register_palette_class(L);
register_palettes_class(L);
register_plugin_class(L);
register_point_class(L);
register_range_class(L);
register_rect_class(L);

View File

@ -15,6 +15,7 @@
#include "app/color.h"
#include "app/commands/params.h"
#include "app/extensions.h"
#include "doc/brush.h"
#include "doc/frame.h"
#include "doc/object_ids.h"
@ -96,6 +97,8 @@ namespace app {
return m_returnCode;
}
lua_State* luaState() { return L; }
private:
void onConsolePrint(const char* text);
@ -133,6 +136,7 @@ namespace app {
void push_images(lua_State* L, const doc::ObjectIds& images);
void push_layers(lua_State* L, const doc::ObjectIds& layers);
void push_palette(lua_State* L, doc::Palette* palette);
void push_plugin(lua_State* L, Extension* ext);
void push_sprite_cel(lua_State* L, doc::Cel* cel);
void push_sprite_frame(lua_State* L, doc::Sprite* sprite, doc::frame_t frame);
void push_sprite_frames(lua_State* L, doc::Sprite* sprite);

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2018 David Capello
//
// This program is distributed under the terms of
@ -146,6 +146,8 @@ doc::frame_t get_frame_number_from_arg(lua_State* L, int index)
auto obj = may_get_obj<FrameObj>(L, index);
if (obj)
return obj->frame;
else if (lua_isnil(L, index) || lua_isnone(L, index))
return 0;
else
return lua_tointeger(L, index)-1;
}

View File

@ -80,10 +80,26 @@ struct ImageObj {
}
};
void render_sprite(Image* dst,
const Sprite* sprite,
const frame_t frame,
const int x, const int y)
{
render::Render render;
render.setNewBlend(true);
render.renderSprite(
dst, sprite, frame,
gfx::Clip(x, y,
0, 0,
sprite->width(),
sprite->height()));
}
int Image_clone(lua_State* L);
int Image_new(lua_State* L)
{
doc::Image* image = nullptr;
doc::ImageSpec spec(doc::ColorMode::RGB, 1, 1, 0);
if (auto spec2 = may_get_obj<doc::ImageSpec>(L, 1)) {
spec = *spec2;
@ -91,6 +107,13 @@ int Image_new(lua_State* L)
else if (may_get_obj<ImageObj>(L, 1)) {
return Image_clone(L);
}
else if (auto spr = may_get_docobj<doc::Sprite>(L, 1)) {
image = doc::Image::create(spr->spec());
if (!image)
return 0;
render_sprite(image, spr, 0, 0, 0);
}
else if (lua_istable(L, 1)) {
// Image{ fromFile }
int type = lua_getfield(L, 1, "fromFile");
@ -129,8 +152,16 @@ int Image_new(lua_State* L)
spec.setHeight(h);
spec.setColorMode((doc::ColorMode)colorMode);
}
doc::Image* image = doc::Image::create(spec);
doc::clear_image(image, spec.maskColor());
if (!image) {
if (spec.width() < 1) spec.setWidth(1);
if (spec.height() < 1) spec.setHeight(1);
image = doc::Image::create(spec);
if (!image) {
// Invalid spec (e.g. width=0, height=0, etc.)
return 0;
}
doc::clear_image(image, spec.maskColor());
}
push_new<ImageObj>(L, image);
return 1;
}
@ -230,27 +261,13 @@ int Image_drawSprite(lua_State* L)
// If the destination image is not related to a sprite, we just draw
// the source image without undo information.
if (obj->cel(L) == nullptr) {
render::Render render;
render.setNewBlend(true);
render.renderSprite(
dst, sprite, frame,
gfx::Clip(pos.x, pos.y,
0, 0,
sprite->width(),
sprite->height()));
render_sprite(dst, sprite, frame, pos.x, pos.y);
}
else {
Tx tx;
ImageRef tmp(Image::createCopy(dst));
render::Render render;
render.setNewBlend(true);
render.renderSprite(
tmp.get(), sprite, frame,
gfx::Clip(pos.x, pos.y,
0, 0,
sprite->width(),
sprite->height()));
render_sprite(tmp.get(), sprite, frame, pos.x, pos.y);
int x1, y1, x2, y2;
if (get_shrink_rect2(&x1, &y1, &x2, &y2, dst, tmp.get())) {

View File

@ -0,0 +1,218 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/app.h"
#include "app/app_menus.h"
#include "app/commands/command.h"
#include "app/commands/commands.h"
#include "app/console.h"
#include "app/script/engine.h"
#include "app/script/luacpp.h"
#include "app/ui/app_menuitem.h"
namespace app {
namespace script {
namespace {
struct Plugin {
Extension* ext;
Plugin(Extension* ext) : ext(ext) { }
};
class PluginCommand : public Command {
public:
PluginCommand(const std::string& id,
const std::string& title,
int onclickRef)
: Command(id.c_str(), CmdUIOnlyFlag)
, m_title(title)
, m_onclickRef(onclickRef) {
}
~PluginCommand() {
auto app = App::instance();
ASSERT(app);
if (!app)
return;
if (script::Engine* engine = app->scriptEngine()) {
lua_State* L = engine->luaState();
luaL_unref(L, LUA_REGISTRYINDEX, m_onclickRef);
}
}
protected:
std::string onGetFriendlyName() const override {
return m_title;
}
void onExecute(Context* context) override {
script::Engine* engine = App::instance()->scriptEngine();
lua_State* L = engine->luaState();
lua_rawgeti(L, LUA_REGISTRYINDEX, m_onclickRef);
if (lua_pcall(L, 0, 1, 0)) {
if (const char* s = lua_tostring(L, -1)) {
Console().printf("Error: %s", s);
}
}
else {
lua_pop(L, 1);
}
}
std::string m_title;
int m_onclickRef;
};
void deleteCommandIfExistent(Extension* ext, const std::string& id)
{
auto cmd = Commands::instance()->byId(id.c_str());
if (cmd) {
Commands::instance()->remove(cmd);
ext->removeCommand(id);
delete cmd;
}
}
int Plugin_gc(lua_State* L)
{
get_obj<Plugin>(L, 1)->~Plugin();
return 0;
}
int Plugin_newCommand(lua_State* L)
{
auto plugin = get_obj<Plugin>(L, 1);
if (lua_istable(L, 2)) {
std::string id, title, group;
lua_getfield(L, 2, "id");
if (const char* s = lua_tostring(L, -1)) {
id = s;
}
lua_pop(L, 1);
if (id.empty())
return luaL_error(L, "Empty id field in plugin:newCommand{ id=... }");
lua_getfield(L, 2, "title");
if (const char* s = lua_tostring(L, -1)) {
title = s;
}
lua_pop(L, 1);
lua_getfield(L, 2, "group");
if (const char* s = lua_tostring(L, -1)) {
group = s;
}
lua_pop(L, 1);
int type = lua_getfield(L, 2, "onclick");
if (type == LUA_TFUNCTION) {
int onclickRef = luaL_ref(L, LUA_REGISTRYINDEX);
// Delete the command if it already exist (e.g. we are
// overwriting a previous registered command)
deleteCommandIfExistent(plugin->ext, id);
auto cmd = new PluginCommand(id, title, onclickRef);
Commands::instance()->add(cmd);
plugin->ext->addCommand(id);
#ifdef ENABLE_UI
// Add a new menu option if the "group" is defined
if (!group.empty() &&
App::instance()->isGui()) { // On CLI menus do not make sense
if (auto appMenus = AppMenus::instance()) {
std::unique_ptr<MenuItem> menuItem(new AppMenuItem(title, cmd));
appMenus->addMenuItemIntoGroup(group, std::move(menuItem));
}
}
#endif // ENABLE_UI
}
else {
lua_pop(L, 1);
}
}
return 0;
}
int Plugin_deleteCommand(lua_State* L)
{
std::string id;
auto plugin = get_obj<Plugin>(L, 1);
if (lua_istable(L, 2)) {
lua_getfield(L, 2, "id");
if (const char* s = lua_tostring(L, -1)) {
id = s;
}
lua_pop(L, 1);
}
else if (const char* s = lua_tostring(L, 2)) {
id = s;
}
if (id.empty())
return luaL_error(L, "No command id specified in plugin:deleteCommand()");
// TODO this can crash if we delete the command from the same command
deleteCommandIfExistent(plugin->ext, id);
return 0;
}
int Plugin_get_preferences(lua_State* L)
{
if (!lua_getuservalue(L, 1)) {
lua_newtable(L);
lua_pushvalue(L, -1);
lua_setuservalue(L, 1);
}
return 1;
}
int Plugin_set_preferences(lua_State* L)
{
lua_pushvalue(L, 2);
lua_setuservalue(L, 1);
return 0;
}
const luaL_Reg Plugin_methods[] = {
{ "__gc", Plugin_gc },
{ "newCommand", Plugin_newCommand },
{ "deleteCommand", Plugin_deleteCommand },
{ nullptr, nullptr }
};
const Property Plugin_properties[] = {
{ "preferences", Plugin_get_preferences, Plugin_set_preferences },
{ nullptr, nullptr, nullptr }
};
} // anonymous namespace
DEF_MTNAME(Plugin);
void register_plugin_class(lua_State* L)
{
REG_CLASS(L, Plugin);
REG_CLASS_PROPERTIES(L, Plugin);
}
void push_plugin(lua_State* L, Extension* ext)
{
push_new<Plugin>(L, ext);
}
} // namespace script
} // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2018-2020 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -22,7 +22,6 @@
#include "doc/sprite.h"
#include "doc/tile.h"
#include <algorithm>
#include <set>
#include <vector>
@ -41,15 +40,32 @@ struct RangeObj { // This is like DocRange but referencing objects with IDs
std::vector<tile_index> tiles;
RangeObj(Site& site) {
const DocRange& docRange = site.range();
updateFromSite(site);
}
RangeObj(const RangeObj&) = delete;
RangeObj& operator=(const RangeObj&) = delete;
void updateFromSite(const Site& site) {
if (!site.sprite()) {
type = DocRange::kNone;
spriteId = NullId;
return;
}
const DocRange& range = site.range();
spriteId = site.sprite()->id();
type = docRange.type();
type = range.type();
if (docRange.enabled()) {
for (const Layer* layer : docRange.selectedLayers())
layers.clear();
frames.clear();
cels.clear();
colors.clear();
if (range.enabled()) {
for (const Layer* layer : range.selectedLayers())
layers.insert(layer->id());
for (const frame_t frame : docRange.selectedFrames())
for (const frame_t frame : range.selectedFrames())
frames.push_back(frame);
// TODO improve this, in the best case we should defer layers,
@ -57,7 +73,7 @@ struct RangeObj { // This is like DocRange but referencing objects with IDs
// it might not be possible because we have to save the IDs of the
// objects (and we cannot store the DocRange because it contains
// pointers instead of IDs).
for (Cel* cel : get_cels(site.sprite(), docRange))
for (const Cel* cel : get_cels(site.sprite(), range))
cels.insert(cel->id());
}
else {
@ -73,8 +89,6 @@ struct RangeObj { // This is like DocRange but referencing objects with IDs
if (site.selectedTiles().picks() > 0)
tiles = site.selectedTiles().toVectorOfIndexes();
}
RangeObj(const RangeObj&) = delete;
RangeObj& operator=(const RangeObj&) = delete;
Sprite* sprite(lua_State* L) { return check_docobj(L, doc::get<Sprite>(spriteId)); }
@ -149,6 +163,23 @@ int Range_containsTile(lua_State* L)
return 1;
}
int Range_clear(lua_State* L)
{
auto obj = get_obj<RangeObj>(L, 1);
auto ctx = App::instance()->context();
// Set an empty range
DocRange range;
ctx->setRange(range);
// Set empty palette picks
doc::PalettePicks picks;
ctx->setSelectedColors(picks);
obj->updateFromSite(ctx->activeSite());
return 0;
}
int Range_get_isEmpty(lua_State* L)
{
auto obj = get_obj<RangeObj>(L, 1);
@ -240,8 +271,53 @@ int Range_get_tiles(lua_State* L)
return 1;
}
int Range_set_layers(lua_State* L)
{
auto obj = get_obj<RangeObj>(L, 1);
app::Context* ctx = App::instance()->context();
DocRange range = ctx->activeSite().range();
doc::SelectedLayers layers;
if (lua_istable(L, 2)) {
lua_pushnil(L);
while (lua_next(L, 2) != 0) {
if (Layer* layer = may_get_docobj<Layer>(L, -1))
layers.insert(layer);
lua_pop(L, 1);
}
}
range.setSelectedLayers(layers);
ctx->setRange(range);
obj->updateFromSite(ctx->activeSite());
return 0;
}
int Range_set_frames(lua_State* L)
{
auto obj = get_obj<RangeObj>(L, 1);
app::Context* ctx = App::instance()->context();
DocRange range = ctx->activeSite().range();
doc::SelectedFrames frames;
if (lua_istable(L, 2)) {
lua_pushnil(L);
while (lua_next(L, 2) != 0) {
doc::frame_t f = get_frame_number_from_arg(L, -1);
frames.insert(f);
lua_pop(L, 1);
}
}
range.setSelectedFrames(frames);
ctx->setRange(range);
obj->updateFromSite(ctx->activeSite());
return 0;
}
int Range_set_colors(lua_State* L)
{
auto obj = get_obj<RangeObj>(L, 1);
app::Context* ctx = App::instance()->context();
doc::PalettePicks picks;
if (lua_istable(L, 2)) {
@ -254,7 +330,9 @@ int Range_set_colors(lua_State* L)
lua_pop(L, 1);
}
}
ctx->setSelectedColors(picks);
obj->updateFromSite(ctx->activeSite());
return 0;
}
@ -281,6 +359,7 @@ const luaL_Reg Range_methods[] = {
{ "contains", Range_contains },
{ "containsColor", Range_containsColor },
{ "containsTile", Range_containsTile },
{ "clear", Range_clear },
{ nullptr, nullptr }
};
@ -288,8 +367,8 @@ const Property Range_properties[] = {
{ "sprite", Range_get_sprite, nullptr },
{ "type", Range_get_type, nullptr },
{ "isEmpty", Range_get_isEmpty, nullptr },
{ "layers", Range_get_layers, nullptr },
{ "frames", Range_get_frames, nullptr },
{ "layers", Range_get_layers, Range_set_layers },
{ "frames", Range_get_frames, Range_set_frames },
{ "cels", Range_get_cels, nullptr },
{ "images", Range_get_images, nullptr },
{ "editableImages", Range_get_editableImages, nullptr },

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2018 David Capello
//
// This program is distributed under the terms of
@ -10,6 +10,7 @@
#endif
#include "app/cmd/set_tag_anidir.h"
#include "app/cmd/set_tag_color.h"
#include "app/cmd/set_tag_name.h"
#include "app/cmd/set_tag_range.h"
#include "app/script/docobj.h"
@ -82,6 +83,20 @@ int Tag_get_aniDir(lua_State* L)
return 1;
}
int Tag_get_color(lua_State* L)
{
auto tag = get_docobj<Tag>(L, 1);
doc::color_t docColor = tag->color();
app::Color appColor = app::Color::fromRgb(doc::rgba_getr(docColor),
doc::rgba_getg(docColor),
doc::rgba_getb(docColor),
doc::rgba_geta(docColor));
if (appColor.getAlpha() == 0)
appColor = app::Color::fromMask();
push_obj<app::Color>(L, appColor);
return 1;
}
int Tag_set_fromFrame(lua_State* L)
{
auto tag = get_docobj<Tag>(L, 1);
@ -127,6 +142,16 @@ int Tag_set_aniDir(lua_State* L)
return 0;
}
int Tag_set_color(lua_State* L)
{
auto tag = get_docobj<Tag>(L, 1);
doc::color_t docColor = convert_args_into_pixel_color(L, 2, doc::IMAGE_RGB);
Tx tx;
tx(new cmd::SetTagColor(tag, docColor));
tx.commit();
return 0;
}
const luaL_Reg Tag_methods[] = {
{ "__eq", Tag_eq },
{ nullptr, nullptr }
@ -139,6 +164,7 @@ const Property Tag_properties[] = {
{ "frames", Tag_get_frames, nullptr },
{ "name", Tag_get_name, Tag_set_name },
{ "aniDir", Tag_get_aniDir, Tag_set_aniDir },
{ "color", Tag_get_color, Tag_set_color },
{ nullptr, nullptr, nullptr }
};

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -151,7 +151,6 @@ FOR_ENUM(app::gen::ColorProfileBehavior)
FOR_ENUM(app::gen::EyedropperChannel)
FOR_ENUM(app::gen::EyedropperSample)
FOR_ENUM(app::gen::FillReferTo)
FOR_ENUM(app::gen::HueSaturationMode)
FOR_ENUM(app::gen::OnionskinType)
FOR_ENUM(app::gen::PaintingCursorType)
FOR_ENUM(app::gen::PivotPosition)
@ -162,12 +161,14 @@ FOR_ENUM(app::gen::StopAtGrid)
FOR_ENUM(app::gen::SymmetryMode)
FOR_ENUM(app::gen::TimelinePosition)
FOR_ENUM(app::gen::WindowColorProfile)
FOR_ENUM(app::gen::ToGrayAlgorithm)
FOR_ENUM(app::tools::FreehandAlgorithm)
FOR_ENUM(app::tools::InkType)
FOR_ENUM(app::tools::RotationAlgorithm)
FOR_ENUM(doc::AniDir)
FOR_ENUM(doc::BrushPattern)
FOR_ENUM(doc::ColorMode)
FOR_ENUM(filters::HueSaturationFilter::Mode)
FOR_ENUM(filters::TiledMode)
FOR_ENUM(render::OnionskinPosition)

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -12,7 +12,7 @@
#include "app/site.h"
#include "app/pref/preferences.h"
#include "base/base.h"
#include "base/clamp.h"
#include "doc/cel.h"
#include "doc/grid.h"
#include "doc/layer.h"
@ -60,7 +60,7 @@ Image* Site::image(int* x, int* y, int* opacity) const
image = cel->image();
if (x) *x = cel->x();
if (y) *y = cel->y();
if (opacity) *opacity = MID(0, cel->opacity(), 255);
if (opacity) *opacity = base::clamp(cel->opacity(), 0, 255);
}
}

Some files were not shown because too many files have changed in this diff Show More