- [Gif, Image animation] Add properties parity (animation speed, loop modes) and custom frame order mode.

This commit is contained in:
MakhamDev 2023-10-01 19:57:57 +07:00
parent 69d7cd73de
commit f7c119a77b
6 changed files with 12929 additions and 12803 deletions

Binary file not shown.

File diff suppressed because it is too large Load diff

View file

@ -105,10 +105,10 @@
globalvar VERSION, SAVE_VERSION, VERSION_STRING, BUILD_NUMBER;
VERSION = 11531;
VERSION = 11540;
SAVE_VERSION = 11530;
VERSION_STRING = "1.15.3.1";
BUILD_NUMBER = 11531;
VERSION_STRING = "1.15.4";
BUILD_NUMBER = 11540;
globalvar APPEND_MAP;
APPEND_MAP = ds_map_create();

View file

@ -41,7 +41,7 @@ enum NODE_EXPORT_FORMAT {
gif,
}
function Node_Export(_x, _y, _group = noone) : Node(_x, _y, _group) constructor {
function Node_Export(_x, _y, _group = noone) : Node(_x, _y, _group) constructor {
name = "Export";
preview_channel = 1;
autoUpdatedTrigger = false;
@ -105,10 +105,15 @@ function Node_Export(_x, _y, _group = noone) : Node(_x, _y, _group) constructor
.setDisplay(VALUE_DISPLAY.slider, [0, 100, 1])
.rejectArray();
inputs[| 11] = nodeValue("Sequence begin", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 0)
inputs[| 11] = nodeValue("Sequence begin", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 0);
inputs[| 12] = nodeValue("Frame range", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, [0, -1])
.setDisplay(VALUE_DISPLAY.slider_range, [0, PROJECT.animator.frames_total, 1])
.setDisplay(VALUE_DISPLAY.slider_range, [0, PROJECT.animator.frames_total, 1]);
png_format = [ "INDEX4", "INDEX8", "Default (PNG32)" ];
png_format_r = [ "PNG4", "PNG8" ];
inputs[| 13] = nodeValue("Subformat", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 4)
.setDisplay(VALUE_DISPLAY.enum_scroll, png_format, { update_hover: false });
outputs[| 0] = nodeValue("Loop exit", self, JUNCTION_CONNECT.output, VALUE_TYPE.any, 0);
@ -196,7 +201,8 @@ function Node_Export(_x, _y, _group = noone) : Node(_x, _y, _group) constructor
input_display_list = [
["Export", false], 0, 1, 2, export_template,
["Format ", false], 3, 9,
["Settings", false], 12, 8, 5, 6, 7, 10, 11,
["Animation", false], 12, 8, 5, 11,
["Quality", false], 6, 7, 10, 13,
];
directory = DIRECTORY + "temp/" + string(irandom_range(100000, 999999));
@ -448,32 +454,43 @@ function Node_Export(_x, _y, _group = noone) : Node(_x, _y, _group) constructor
var extd = inputs[| 9].getValue();
var qual = inputs[| 10].getValue();
var indx = inputs[| 13].getValue();
var ext = array_safe_get(format_image, extd, ".png");
var _pathOut = _path;
var _pathTemp = directory + "/" + string(irandom_range(10000, 99999)) + ".png";
var _pathTemp = $"{directory}/{irandom_range(10000, 99999)}.png";
switch(ext) {
case ".png":
surface_save_safe(_surf, _path);
if(indx == 0) {
surface_save_safe(_surf, _pathTemp);
var shell_cmd = $"convert \"{_pathTemp}\" \"{_pathOut}\"";
shell_execute(magick, shell_cmd, self);
} else if(indx == 2) {
surface_save_safe(_surf, _pathOut);
} else {
surface_save_safe(_surf, _pathTemp);
var shell_cmd = $"convert {_pathTemp} {png_format_r[indx]}:\"{_pathOut}\"";
shell_execute(magick, shell_cmd, self);
}
break;
case ".jpg":
surface_save_safe(_surf, _pathTemp);
_pathOut = "\"" + string_replace_all(_path, ".png", "") + ".jpg\"";
_pathTemp = "\"" + _pathTemp + "\"";
var shell_cmd = _pathTemp + " -quality " + string(qual) + " " + _pathOut;
_pathOut = $"\"{string_replace_all(_path, ".png", "")}.jpg\"";
var shell_cmd = $"\"{_pathTemp}\" -quality {qual} {_pathOut}";
shell_execute(magick, shell_cmd, self);
break;
case ".webp":
case ".webp":
surface_save_safe(_surf, _pathTemp);
_pathOut = "\"" + string_replace_all(_path, ".png", "") + ".webp\"";
_pathTemp = "\"" + _pathTemp + "\"";
var shell_cmd = _pathTemp + " -quality " + string(qual) + " -define webp:lossless=true " + _pathOut;
_pathOut = $"\"{string_replace_all(_path, ".png", "")}.webp\"";
var shell_cmd = $"\"{_pathTemp}\" -quality {qual} -define webp:lossless=true {_pathOut}";
shell_execute(magick, shell_cmd, self);
break;
@ -589,7 +606,9 @@ function Node_Export(_x, _y, _group = noone) : Node(_x, _y, _group) constructor
} #endregion
static step = function() { #region
var surf = inputs[| 0].getValue();
var surf = inputs[| 0].getValue();
var pngf = inputs[| 13].getValue();
if(is_array(surf)) {
inputs[| 3].display_data = format_array;
inputs[| 3].editWidget.data_list = format_array;
@ -600,7 +619,7 @@ function Node_Export(_x, _y, _group = noone) : Node(_x, _y, _group) constructor
outputs[| 1].setValue(surf);
var anim = inputs[| 3].getValue();
var anim = inputs[| 3].getValue(); // single, sequence, animation
var extn = inputs[| 9].getValue();
inputs[| 5].setVisible(anim == 2);
@ -608,8 +627,9 @@ function Node_Export(_x, _y, _group = noone) : Node(_x, _y, _group) constructor
inputs[| 7].setVisible(anim == 2);
inputs[| 8].setVisible(anim == 2);
inputs[| 11].setVisible(anim == 1);
inputs[| 12].setVisible(anim >= 1);
inputs[| 12].setVisible(anim > 0);
inputs[| 12].editWidget.maxx = PROJECT.animator.frames_total;
inputs[| 13].setVisible(anim < 2);
if(anim == NODE_EXPORT_FORMAT.gif) {
inputs[| 9].display_data = format_animation;

View file

@ -49,10 +49,10 @@ function Node_Image_Animated(_x, _y, _group = noone) : Node(_x, _y, _group) cons
inputs[| 2] = nodeValue("Stretch frame", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false, "Stretch animation speed to match project length.")
.rejectArray();
inputs[| 3] = nodeValue("Frame duration", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, 1)
inputs[| 3] = nodeValue("Animation speed", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, 1)
.rejectArray();
inputs[| 4] = nodeValue("Animation end", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 0)
inputs[| 4] = nodeValue("Loop modes", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 0)
.setDisplay(VALUE_DISPLAY.enum_scroll, ["Loop", "Ping pong", "Hold last frame", "Hide"])
.rejectArray();
@ -62,18 +62,22 @@ function Node_Image_Animated(_x, _y, _group = noone) : Node(_x, _y, _group) cons
PROJECT.animator.frames_total = array_length(spr);
}, "Match length"] );
inputs[| 6] = nodeValue("Custom frame order", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false);
inputs[| 7] = nodeValue("Frame", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 0);
outputs[| 0] = nodeValue("Surface out", self, JUNCTION_CONNECT.output, VALUE_TYPE.surface, noone);
input_display_list = [
["Image", false], 0, 1,
["Animation", false], 5, 2, 3, 4,
["Animation", false], 5, 4, 6, 7, 2, 3,
];
attribute_surface_depth();
path_loaded = [];
on_drop_file = function(path) {
on_drop_file = function(path) { #region
if(directory_exists(path)) {
with(dialogCall(o_dialog_drag_folder, WIN_W / 2, WIN_H / 2)) {
dir_paths = path;
@ -91,9 +95,9 @@ function Node_Image_Animated(_x, _y, _group = noone) : Node(_x, _y, _group) cons
}
return false;
}
} #endregion
function updatePaths(paths) {
function updatePaths(paths) { #region
if(!is_array(paths) && ds_exists(paths, ds_type_list))
paths = ds_list_to_array(paths);
@ -123,56 +127,71 @@ function Node_Image_Animated(_x, _y, _group = noone) : Node(_x, _y, _group) cons
}
return true;
}
} #endregion
insp1UpdateTooltip = __txt("Refresh");
insp1UpdateIcon = [ THEME.refresh, 1, COLORS._main_value_positive ];
static onInspector1Update = function() {
static onInspector1Update = function() { #region
var path = inputs[| 0].getValue();
if(path == "") return;
updatePaths(path);
update();
}
} #endregion
static update = function(frame = PROJECT.animator.current_frame) {
static step = function() { #region
var str = inputs[| 2].getValue();
var _cus = inputs[| 6].getValue();
inputs[| 7].setVisible( _cus);
inputs[| 2].setVisible(!_cus);
inputs[| 3].setVisible(!_cus && !str);
inputs[| 4].setVisible(!_cus && !str);
} #endregion
static update = function(frame = PROJECT.animator.current_frame) { #region
var path = inputs[| 0].getValue();
if(path == "") return;
if(is_array(path) && !array_equals(path, path_loaded))
updatePaths(path);
if(array_length(spr) == 0) return;
var pad = inputs[| 1].getValue();
var str = inputs[| 2].getValue();
inputs[| 3].setVisible(!str);
inputs[| 4].setVisible(!str);
var _pad = inputs[| 1].getValue();
var spd = str? (PROJECT.animator.frames_total + 1) / array_length(spr) : inputs[| 3].getValue();
var _cus = inputs[| 6].getValue();
var _str = inputs[| 2].getValue();
var _end = inputs[| 4].getValue();
if(spd == 0) spd = 1;
var _spd = _str? (PROJECT.animator.frames_total + 1) / array_length(spr) : 1 / inputs[| 3].getValue();
if(_spd == 0) _spd = 1;
var _frame = _cus? inputs[| 7].getValue() : floor(PROJECT.animator.current_frame / _spd);
var _len = array_length(spr);
var _drw = true;
var ww = sprite_get_width(spr[0]);
var hh = sprite_get_height(spr[0]);
ww += pad[0] + pad[2];
hh += pad[1] + pad[3];
ww += _pad[0] + _pad[2];
hh += _pad[1] + _pad[3];
var surfs = outputs[| 0].getValue();
surfs = surface_verify(surfs, ww, hh, attrDepth());
outputs[| 0].setValue(surfs);
var _frame = floor(PROJECT.animator.current_frame / spd);
switch(_end) {
case ANIMATION_END.loop :
_frame = safe_mod(_frame, array_length(spr));
_frame = safe_mod(_frame, _len);
break;
case ANIMATION_END.ping :
_frame = safe_mod(_frame, array_length(spr) * 2 - 2);
if(_frame >= array_length(spr))
_frame = array_length(spr) * 2 - 2 - _frame;
_frame = safe_mod(_frame, _len * 2 - 2);
if(_frame >= _len)
_frame = _len * 2 - 2 - _frame;
break;
case ANIMATION_END.hold :
_frame = min(_frame, array_length(spr) - 1);
_frame = min(_frame, _len - 1);
break;
case ANIMATION_END.hide :
if(_frame < 0 || _frame >= _len)
_drw = false;
break;
}
@ -181,18 +200,11 @@ function Node_Image_Animated(_x, _y, _group = noone) : Node(_x, _y, _group) cons
var curr_w = sprite_get_width(spr[_frame]);
var curr_h = sprite_get_height(spr[_frame]);
var curr_x = pad[2] + (ww - curr_w) / 2;
var curr_y = pad[1] + (hh - curr_h) / 2;
var curr_x = _pad[2] + (ww - curr_w) / 2;
var curr_y = _pad[1] + (hh - curr_h) / 2;
surface_set_target(surfs);
DRAW_CLEAR
BLEND_OVERRIDE;
if(_end == ANIMATION_END.hide) {
if(_frame < array_length(spr))
draw_sprite(spr[_frame], 0, curr_x, curr_y);
} else
draw_sprite(spr[_frame], 0, curr_x, curr_y);
BLEND_NORMAL;
surface_reset_target();
}
surface_set_shader(surfs);
if(_drw) draw_sprite(spr[_frame], 0, curr_x, curr_y);
surface_reset_shader();
} #endregion
}

View file

@ -10,7 +10,6 @@ function Node_create_Image_gif(_x, _y, _group = noone) {
node.inputs[| 0].setValue(path);
node.doUpdate();
//ds_list_add(PANEL_GRAPH.nodes_list, node);
return node;
}
@ -21,7 +20,6 @@ function Node_create_Image_gif_path(_x, _y, path) {
node.inputs[| 0].setValue(path);
node.doUpdate();
//ds_list_add(PANEL_GRAPH.nodes_list, node);
return node;
}
@ -30,7 +28,7 @@ function Node_Image_gif(_x, _y, _group = noone) : Node(_x, _y, _group) construct
color = COLORS.node_blend_input;
update_on_frame = true;
inputs[| 0] = nodeValue("Path", self, JUNCTION_CONNECT.input, VALUE_TYPE.path, "")
inputs[| 0] = nodeValue("Path", self, JUNCTION_CONNECT.input, VALUE_TYPE.path, "")
.setDisplay(VALUE_DISPLAY.path_load, ["*.gif", ""]);
inputs[| 1] = nodeValue("Set animation length to gif", self, JUNCTION_CONNECT.input, VALUE_TYPE.trigger, 0)
@ -43,18 +41,35 @@ function Node_Image_gif(_x, _y, _group = noone) : Node(_x, _y, _group) construct
inputs[| 2] = nodeValue("Output as array", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false);
inputs[| 3] = nodeValue("Loop modes", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 0)
.setDisplay(VALUE_DISPLAY.enum_scroll, ["Loop", "Ping pong", "Hold last frame", "Hide"])
.rejectArray();
inputs[| 4] = nodeValue("Start frame", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 0);
inputs[| 5] = nodeValue("Custom frame order", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false);
inputs[| 6] = nodeValue("Frame", self, JUNCTION_CONNECT.input, VALUE_TYPE.integer, 0);
inputs[| 7] = nodeValue("Animation speed", self, JUNCTION_CONNECT.input, VALUE_TYPE.float, 1);
outputs[| 0] = nodeValue("Surface out", self, JUNCTION_CONNECT.output, VALUE_TYPE.surface, noone);
outputs[| 1] = nodeValue("Path", self, JUNCTION_CONNECT.output, VALUE_TYPE.path, "")
.setVisible(true, true);
input_display_list = [
["Image", false], 0,
["Output", false], 2,
["Animation", false], 1, 3, 5, 4, 6, 7,
];
attribute_surface_depth();
spr = noone;
spr = noone;
path_current = "";
loading = 0;
spr_builder = noone;
surfaces = [];
loading = 0;
spr_builder = noone;
surfaces = [];
on_drop_file = function(path) {
inputs[| 0].setValue(path);
@ -70,14 +85,14 @@ function Node_Image_gif(_x, _y, _group = noone) : Node(_x, _y, _group) construct
insp1UpdateTooltip = __txt("Refresh");
insp1UpdateIcon = [ THEME.refresh, 1, COLORS._main_value_positive ];
static onInspector1Update = function() {
static onInspector1Update = function() { #region
var path = inputs[| 0].getValue();
if(path == "") return;
updatePaths(path);
update();
}
} #endregion
function updatePaths(path) {
function updatePaths(path) { #region
path = try_get_path(path);
if(path == -1) return false;
@ -101,9 +116,18 @@ function Node_Image_gif(_x, _y, _group = noone) : Node(_x, _y, _group) construct
path_current = path;
return true;
}
} #endregion
static step = function() {
static step = function() { #region
var _arr = inputs[| 2].getValue();
var _lop = inputs[| 3].getValue();
var _cus = inputs[| 5].getValue();
inputs[| 3].setVisible(!_arr);
inputs[| 4].setVisible(!_cus);
inputs[| 6].setVisible( _cus);
inputs[| 7].setVisible(!_cus);
if(loading == 2 && spr_builder != noone && spr_builder.building()) {
surfaces = [];
spr = spr_builder._spr;
@ -112,9 +136,9 @@ function Node_Image_gif(_x, _y, _group = noone) : Node(_x, _y, _group) construct
gc_collect();
}
}
} #endregion
static update = function(frame = PROJECT.animator.current_frame) {
static update = function(frame = PROJECT.animator.current_frame) { #region
var path = inputs[| 0].getValue();
if(path == "") return;
if(path_current != path) updatePaths(path);
@ -148,13 +172,40 @@ function Node_Image_gif(_x, _y, _group = noone) : Node(_x, _y, _group) construct
return;
}
var _loop = inputs[| 3].getValue();
var _strt = inputs[| 4].getValue();
var _cust = inputs[| 5].getValue();
var _spd = inputs[| 7].getValue();
var _frm = _cust? inputs[| 6].getValue() : PROJECT.animator.current_frame * _spd - _strt;
var _len = sprite_get_number(spr);
var _drw = true;
switch(_loop) {
case ANIMATION_END.loop :
_frm = safe_mod(_frm, _len);
break;
case ANIMATION_END.ping :
_frm = safe_mod(_frm, _len * 2 - 2);
if(_frm >= _len)
_frm = _len * 2 - 2 - _frm;
break;
case ANIMATION_END.hold :
_frm = clamp(_frm, -_len, _len - 1);
break;
case ANIMATION_END.hide :
if(_frm < 0 || _frm >= _len)
_drw = false;
break;
}
_outsurf = surface_verify(_outsurf, ww, hh, attrDepth());
outputs[| 0].setValue(_outsurf);
surface_set_shader(_outsurf);
draw_sprite(spr, PROJECT.animator.current_frame, 0, 0);
if(_drw) draw_sprite(spr, _frm, 0, 0);
surface_reset_shader();
}
} #endregion
static onDrawNode = function(xx, yy, _mx, _my, _s, _hover, _focus) {
if(loading) draw_sprite_ui(THEME.loading, 0, xx + w * _s / 2, yy + h * _s / 2, _s, _s, current_time / 2, COLORS._main_icon, 1);