911 lines
30 KiB
GDScript
911 lines
30 KiB
GDScript
@tool
|
|
## Resource or Project FILE for every PixelPenProject
|
|
class_name PixelPenProject
|
|
extends Resource
|
|
|
|
# Increase this number if @export property change
|
|
const COMPATIBILITY_NUMBER : int = 3
|
|
|
|
@export var compatibility_version : int
|
|
@export var project_name : String
|
|
@export var file_path : String
|
|
@export var last_export_file_path : String
|
|
@export var canvas_size : Vector2i
|
|
|
|
@export var palette : IndexedPalette
|
|
|
|
@export var pool_frames : Array[Frame]
|
|
@export var canvas_pool_frame_uid : Vector3i
|
|
|
|
@export var show_timeline : bool = false
|
|
@export var animation_timeline : Array[AnimationCell]
|
|
@export var animation_frame_index : int = -1
|
|
@export var animation_fps : float = 30
|
|
@export var animation_loop : bool = true
|
|
@export var onion_skinning : bool = false
|
|
|
|
## Used by internal for auto name layer
|
|
@export var layer_index_counter : int
|
|
@export var active_layer_uid : Vector3i
|
|
@export var use_sample : bool = false
|
|
@export var _sample_offset : Vector2i
|
|
@export var _cache_canvas_size : Vector2i
|
|
@export var _cache_pool_frames : Array[Frame]
|
|
@export var _cache_canvas_pool_frame_uid : Vector3i
|
|
|
|
@export var show_grid : bool = false
|
|
@export var show_tile : bool = false
|
|
@export var show_preview : bool = true
|
|
@export var show_symetric_vertical : bool = false
|
|
@export var show_symetric_horizontal : bool = false
|
|
@export var symetric_guid : Vector2
|
|
@export var background_color : BackgroundColor = BackgroundColor.TRANSPARENT
|
|
|
|
@export var uid_counter : int
|
|
|
|
var paint : PixelPenImage = PixelPenImage.new()
|
|
var brush_index : int = -1
|
|
|
|
var animation_is_play : bool = false
|
|
var skinning_frame_index : int = -1 # Record reference frame
|
|
var animation_prev_skinning_image : Array[Image] = []
|
|
var animation_next_skinning_image : Array[Image] = []
|
|
|
|
var active_frame : Frame:
|
|
get:
|
|
return get_pool_frame(canvas_pool_frame_uid)
|
|
var active_layer : IndexedColorImage:
|
|
get:
|
|
return get_index_image(active_layer_uid)
|
|
var is_saved : bool = false
|
|
|
|
var undo_redo : UndoRedoManager
|
|
|
|
var cache_copied_colormap : IndexedColorImage
|
|
var cache_thumbnail : Image
|
|
var multilayer_selected : Array[Vector3i]
|
|
|
|
var _cache_undo_redo : UndoRedoManager
|
|
|
|
enum ProjectMode{
|
|
BASE = 0,
|
|
SAMPLE = 1
|
|
}
|
|
|
|
enum BackgroundColor{
|
|
TRANSPARENT = 0,
|
|
WHITE,
|
|
GREY,
|
|
BLACK
|
|
}
|
|
|
|
|
|
func initialized(p_size : Vector2i, p_name : String = "Untitled", p_file_path : String = "", one_layer : bool = true):
|
|
compatibility_version = COMPATIBILITY_NUMBER
|
|
layer_index_counter = 0
|
|
project_name = p_name
|
|
canvas_size = p_size
|
|
animation_fps = PixelPen.state.userconfig.default_animation_fps
|
|
symetric_guid = canvas_size * 0.5
|
|
palette = IndexedPalette.new()
|
|
palette.set_color_index_preset()
|
|
sync_gui_palette()
|
|
pool_frames = [Frame.create(get_uid())]
|
|
canvas_pool_frame_uid = pool_frames[0].frame_uid
|
|
var cell : AnimationCell = AnimationCell.create(get_uid())
|
|
cell.frame = pool_frames[0]
|
|
animation_timeline.push_back(cell)
|
|
animation_frame_index = 0
|
|
if one_layer:
|
|
add_layer()
|
|
active_layer_uid = active_frame.layers[0].layer_uid
|
|
|
|
|
|
func get_uid():
|
|
uid_counter += 1
|
|
var uid = Vector3i(uid_counter, randi(), Time.get_unix_time_from_system() as int)
|
|
if uid == Vector3i.ZERO:
|
|
return get_uid()
|
|
return uid
|
|
|
|
|
|
func get_json() -> String:
|
|
var pool_frames_data : Array = []
|
|
for frame in pool_frames:
|
|
pool_frames_data.push_back(frame.get_data())
|
|
var _cache_pool_frames_data : Array = []
|
|
for frame in _cache_pool_frames:
|
|
_cache_pool_frames_data.push_back(frame.get_data())
|
|
var arr_animation_timeline : Array = []
|
|
for cell in animation_timeline:
|
|
arr_animation_timeline.push_back(cell.get_data())
|
|
var metadata : Dictionary = {
|
|
"compatibility_version" : compatibility_version,
|
|
"project_name" : project_name,
|
|
"file_path" : file_path,
|
|
"last_export_file_path" : last_export_file_path,
|
|
"canvas_size" : var_to_str(canvas_size),
|
|
"palette" : palette.get_data(),
|
|
"pool_frames" : pool_frames_data,
|
|
"canvas_pool_frame_uid" : var_to_str(canvas_pool_frame_uid),
|
|
"show_timeline" : show_timeline,
|
|
"animation_timeline" : arr_animation_timeline,
|
|
"animation_frame_index" : animation_frame_index,
|
|
"animation_fps" : animation_fps,
|
|
"animation_loop" : animation_loop,
|
|
"onion_skinning" : onion_skinning,
|
|
"layer_index_counter" : layer_index_counter,
|
|
"active_layer_uid" : var_to_str(active_layer_uid),
|
|
"use_sample" : use_sample,
|
|
"_sample_offset" : var_to_str(_sample_offset),
|
|
"_cache_canvas_size" : var_to_str(_cache_canvas_size),
|
|
"_cache_pool_frames" : _cache_pool_frames_data,
|
|
"_cache_canvas_pool_frame_uid" : var_to_str(_cache_canvas_pool_frame_uid),
|
|
"show_grid" : show_grid,
|
|
"show_tile" : show_tile,
|
|
"show_preview" : show_preview,
|
|
"show_symetric_vertical" : show_symetric_vertical,
|
|
"show_symetric_horizontal" : show_symetric_horizontal,
|
|
"symetric_guid" : var_to_str(symetric_guid),
|
|
"background_color" : var_to_str(background_color),
|
|
"uid_counter" : uid_counter
|
|
}
|
|
var json := JSON.new()
|
|
return json.stringify(metadata)
|
|
|
|
|
|
func from_json(json_string : String) -> Error:
|
|
var json := JSON.new()
|
|
var err := json.parse(json_string)
|
|
if err != OK:
|
|
return err
|
|
|
|
var json_data : Dictionary = json.data as Dictionary
|
|
if json_data.has("compatibility_version"):
|
|
compatibility_version = json_data["compatibility_version"] as int
|
|
else:
|
|
return FAILED
|
|
if json_data.has("project_name"):
|
|
project_name = json_data["project_name"] as String
|
|
else:
|
|
return FAILED
|
|
if json_data.has("file_path"):
|
|
file_path = json_data["file_path"] as String
|
|
else:
|
|
return FAILED
|
|
if json_data.has("last_export_file_path"):
|
|
last_export_file_path = json_data["last_export_file_path"] as String
|
|
else:
|
|
return FAILED
|
|
if json_data.has("canvas_size"):
|
|
canvas_size = str_to_var(json_data["canvas_size"]) as Vector2i
|
|
else:
|
|
return FAILED
|
|
if json_data.has("palette"):
|
|
palette = IndexedPalette.new()
|
|
if palette.from_data(json_data["palette"]) != OK:
|
|
return FAILED
|
|
else:
|
|
return FAILED
|
|
if json_data.has("pool_frames"):
|
|
pool_frames.clear()
|
|
var pool_frames_data : Array = json_data["pool_frames"] as Array
|
|
for data in pool_frames_data:
|
|
var frame := Frame.new()
|
|
var frame_err := frame.from_data(data)
|
|
if frame_err != OK:
|
|
return FAILED
|
|
pool_frames.push_back(frame)
|
|
|
|
else:
|
|
return FAILED
|
|
if json_data.has("canvas_pool_frame_uid"):
|
|
canvas_pool_frame_uid = str_to_var(json_data["canvas_pool_frame_uid"]) as Vector3i
|
|
else:
|
|
return FAILED
|
|
if json_data.has("show_timeline"):
|
|
show_timeline = json_data["show_timeline"] as bool
|
|
else:
|
|
return FAILED
|
|
if json_data.has("animation_frame_index"):
|
|
animation_frame_index = json_data["animation_frame_index"] as int
|
|
else:
|
|
return FAILED
|
|
if json_data.has("animation_fps"):
|
|
animation_fps = json_data["animation_fps"] as float
|
|
else:
|
|
return FAILED
|
|
if json_data.has("animation_loop"):
|
|
animation_loop = json_data["animation_loop"] as bool
|
|
else:
|
|
return FAILED
|
|
if json_data.has("onion_skinning"):
|
|
onion_skinning = json_data["onion_skinning"] as bool
|
|
else:
|
|
return FAILED
|
|
if json_data.has("layer_index_counter"):
|
|
layer_index_counter = json_data["layer_index_counter"] as int
|
|
else:
|
|
return FAILED
|
|
if json_data.has("active_layer_uid"):
|
|
active_layer_uid = str_to_var(json_data["active_layer_uid"]) as Vector3i
|
|
else:
|
|
return FAILED
|
|
if json_data.has("use_sample"):
|
|
use_sample = json_data["use_sample"] as bool
|
|
else:
|
|
return FAILED
|
|
if json_data.has("_sample_offset"):
|
|
_sample_offset = str_to_var(json_data["_sample_offset"]) as Vector2i
|
|
else:
|
|
return FAILED
|
|
if json_data.has("_cache_canvas_size"):
|
|
_cache_canvas_size = str_to_var(json_data["_cache_canvas_size"]) as Vector2i
|
|
else:
|
|
return FAILED
|
|
if json_data.has("_cache_pool_frames"):
|
|
_cache_pool_frames.clear()
|
|
var pool_frames_data : Array = json_data["_cache_pool_frames"] as Array
|
|
for data in pool_frames_data:
|
|
var frame := Frame.new()
|
|
var frame_err := frame.from_data(data)
|
|
if frame_err != OK:
|
|
return FAILED
|
|
_cache_pool_frames.push_back(frame)
|
|
else:
|
|
return FAILED
|
|
if json_data.has("animation_timeline"):
|
|
animation_timeline.clear()
|
|
var arr_animation_timeline : Array = json_data["animation_timeline"] as Array
|
|
for data in arr_animation_timeline:
|
|
var cell := AnimationCell.new()
|
|
var cell_err := cell.from_data(data, self)
|
|
if cell_err != OK:
|
|
return FAILED
|
|
animation_timeline.push_back(cell)
|
|
else:
|
|
return FAILED
|
|
if json_data.has("_cache_canvas_pool_frame_uid"):
|
|
_cache_canvas_pool_frame_uid = str_to_var(json_data["_cache_canvas_pool_frame_uid"]) as Vector3i
|
|
else:
|
|
return FAILED
|
|
if json_data.has("show_grid"):
|
|
show_grid = json_data["show_grid"] as bool
|
|
else:
|
|
return FAILED
|
|
if json_data.has("show_tile"):
|
|
show_tile = json_data["show_tile"] as bool
|
|
else:
|
|
return FAILED
|
|
if json_data.has("show_preview"):
|
|
show_preview = json_data["show_preview"] as bool
|
|
else:
|
|
return FAILED
|
|
if json_data.has("show_symetric_vertical"):
|
|
show_symetric_vertical = json_data["show_symetric_vertical"] as bool
|
|
else:
|
|
return FAILED
|
|
if json_data.has("show_symetric_horizontal"):
|
|
show_symetric_horizontal = json_data["show_symetric_horizontal"] as bool
|
|
else:
|
|
return FAILED
|
|
if json_data.has("symetric_guid"):
|
|
symetric_guid = str_to_var(json_data["symetric_guid"]) as Vector2
|
|
else:
|
|
return FAILED
|
|
if json_data.has("background_color"):
|
|
background_color = str_to_var(json_data["background_color"]) as BackgroundColor
|
|
else:
|
|
return FAILED
|
|
if json_data.has("uid_counter"):
|
|
uid_counter = json_data["uid_counter"] as int
|
|
else:
|
|
return FAILED
|
|
return OK
|
|
|
|
|
|
func sync_gui_palette(ok_save : bool = false):
|
|
if palette.is_gui_valid():
|
|
var sync : bool = palette.is_gui_order_equal_to_color_index_order()
|
|
if not sync:
|
|
var new_palette : PackedColorArray = [Color.TRANSPARENT]
|
|
for idx in palette.grid_color_index:
|
|
new_palette.push_back(palette.color_index[idx])
|
|
for frame in pool_frames:
|
|
for layer in frame.layers:
|
|
PixelPenCPP.swap_palette(palette.color_index, new_palette, layer.colormap)
|
|
if use_sample:
|
|
for frame in _cache_pool_frames:
|
|
for layer in frame.layers:
|
|
PixelPenCPP.swap_palette(palette.color_index, new_palette, layer.colormap)
|
|
palette.color_index = new_palette
|
|
palette.grid_sync_to_palette()
|
|
if ok_save:
|
|
ResourceSaver.save(self, file_path)
|
|
else:
|
|
is_saved = false
|
|
else:
|
|
palette.grid_sync_to_palette()
|
|
|
|
|
|
func set_mode(mode : ProjectMode, mask : Image = null):
|
|
if mode == ProjectMode.BASE and use_sample:
|
|
var frame := get_pool_frame(_cache_canvas_pool_frame_uid, true)
|
|
for layer_i in range(frame.layers.size()):
|
|
var cache : IndexedColorImage = frame.layers[layer_i]
|
|
var sample : IndexedColorImage = find_index_image(cache.layer_uid)
|
|
if sample != null:
|
|
cache.colormap.blit_rect(sample.colormap, Rect2i(Vector2i.ZERO, sample.colormap.get_size()), _sample_offset)
|
|
use_sample = false
|
|
|
|
# NOTE: break compat after typo `_cache_canvs_size`
|
|
#canvas_size = _cache_canvs_size
|
|
canvas_size = _cache_pool_frames[0].layers[0].size
|
|
|
|
pool_frames = _cache_pool_frames
|
|
canvas_pool_frame_uid = _cache_canvas_pool_frame_uid
|
|
undo_redo = _cache_undo_redo
|
|
elif mode == ProjectMode.SAMPLE and mask != null and not use_sample:
|
|
use_sample = true
|
|
_cache_canvas_size = canvas_size
|
|
_cache_pool_frames = pool_frames#get_frames_duplicate(false)
|
|
_cache_canvas_pool_frame_uid = canvas_pool_frame_uid
|
|
_cache_undo_redo = undo_redo
|
|
var region : Rect2i = PixelPenCPP.get_mask_used_rect(mask)
|
|
canvas_size = region.size
|
|
_sample_offset = region.position
|
|
var new_pool_frames : Array[Frame] = [active_frame.get_duplicate(false)]
|
|
for layer_i in range(new_pool_frames[0].layers.size()):
|
|
var new_layer : IndexedColorImage = new_pool_frames[0].layers[layer_i].get_duplicate(false)
|
|
new_layer.size = canvas_size
|
|
new_layer.colormap = new_pool_frames[0].layers[layer_i].colormap.get_region(region)
|
|
new_pool_frames[0].layers[layer_i] = new_layer
|
|
pool_frames = new_pool_frames
|
|
undo_redo = UndoRedoManager.new()
|
|
PixelPen.state.edit_mode_changed.emit(mode)
|
|
|
|
|
|
func resize_canvas(new_size : Vector2i, anchor : PixelPenEnum.ResizeAnchor):
|
|
canvas_size = new_size
|
|
for frame in pool_frames:
|
|
for layer in frame.layers:
|
|
layer.resize(new_size, anchor)
|
|
cache_copied_colormap = null
|
|
symetric_guid = canvas_size * 0.5
|
|
show_symetric_vertical = false
|
|
show_symetric_horizontal = false
|
|
|
|
|
|
func break_history():
|
|
undo_redo.break_history()
|
|
if not (undo_redo.history_breaked as Signal).is_connected(clean_invisible_color):
|
|
undo_redo.history_breaked.connect(clean_invisible_color)
|
|
|
|
|
|
func unbreak_history():
|
|
undo_redo.unbreak_history()
|
|
|
|
|
|
func undo():
|
|
undo_redo.undo()
|
|
|
|
|
|
func redo():
|
|
undo_redo.redo()
|
|
|
|
|
|
func create_undo_layer_and_palette(name : String, callable : Callable):
|
|
undo_redo.create_action(name)
|
|
undo_redo.add_undo_property(active_frame, "layers", active_frame.get_layer_duplicate(false))
|
|
undo_redo.add_undo_property(palette, "color_index", palette.color_index.duplicate())
|
|
undo_redo.add_undo_method(callable)
|
|
|
|
|
|
func create_redo_layer_and_palette(callable : Callable):
|
|
undo_redo.add_do_property(active_frame, "layers", active_frame.get_layer_duplicate(false))
|
|
undo_redo.add_do_property(palette, "color_index", palette.color_index.duplicate())
|
|
undo_redo.add_do_method(callable)
|
|
undo_redo.commit_action()
|
|
|
|
|
|
func create_undo_layers(name : String, callable : Callable):
|
|
undo_redo.create_action(name)
|
|
undo_redo.add_undo_property(active_frame, "layers", active_frame.get_layer_duplicate(false))
|
|
undo_redo.add_undo_method(callable)
|
|
|
|
|
|
func create_redo_layers(callable : Callable):
|
|
undo_redo.add_do_property(active_frame, "layers", active_frame.get_layer_duplicate(false))
|
|
undo_redo.add_do_method(callable)
|
|
undo_redo.commit_action()
|
|
|
|
|
|
func create_undo_layer(name : String, layer_uid : Vector3i , callable : Callable = Callable(), create_action : bool = true):
|
|
var image : IndexedColorImage = find_index_image(layer_uid)
|
|
assert(image != null, "Error: IndexedColorImage != null -> fail")
|
|
if create_action:
|
|
undo_redo.create_action(name)
|
|
undo_redo.add_undo_property(image, "colormap", image.colormap.duplicate())
|
|
undo_redo.add_undo_method(callable)
|
|
|
|
|
|
func create_redo_layer(layer_uid : Vector3i, callable : Callable = Callable(), commit_action : bool = true):
|
|
var image : IndexedColorImage = find_index_image(layer_uid)
|
|
assert(image != null, "Error: IndexedColorImage != null -> fail")
|
|
undo_redo.add_do_property(image, "colormap", image.colormap.duplicate())
|
|
undo_redo.add_do_method(callable)
|
|
if commit_action:
|
|
undo_redo.commit_action()
|
|
|
|
|
|
func create_undo_palette(name : String, callable : Callable):
|
|
undo_redo.create_action(name)
|
|
undo_redo.add_undo_property(palette, "color_index", palette.color_index.duplicate())
|
|
undo_redo.add_undo_method(callable)
|
|
|
|
|
|
func create_redo_palette(callable : Callable):
|
|
undo_redo.add_do_property(palette, "color_index", palette.color_index.duplicate())
|
|
undo_redo.add_do_method(callable)
|
|
undo_redo.commit_action()
|
|
|
|
|
|
func create_undo_palette_gui(name : String, callable : Callable):
|
|
undo_redo.create_action(name)
|
|
undo_redo.add_undo_property(palette, "grid_color_index", palette.grid_color_index.duplicate())
|
|
undo_redo.add_undo_method(callable)
|
|
|
|
|
|
func create_redo_palette_gui(callable : Callable):
|
|
undo_redo.add_do_property(palette, "grid_color_index", palette.grid_color_index.duplicate())
|
|
undo_redo.add_do_method(callable)
|
|
undo_redo.commit_action()
|
|
|
|
|
|
func create_undo_palette_all(name : String, callable : Callable):
|
|
undo_redo.create_action(name)
|
|
undo_redo.add_undo_property(palette, "color_index", palette.color_index.duplicate())
|
|
undo_redo.add_undo_property(palette, "grid_color_index", palette.grid_color_index.duplicate())
|
|
undo_redo.add_undo_method(callable)
|
|
|
|
|
|
func create_redo_palette_all(callable : Callable):
|
|
undo_redo.add_do_property(palette, "color_index", palette.color_index.duplicate())
|
|
undo_redo.add_do_property(palette, "grid_color_index", palette.grid_color_index.duplicate())
|
|
undo_redo.add_do_method(callable)
|
|
undo_redo.commit_action()
|
|
|
|
|
|
func create_undo_property(name : String, object : Object, property : String, value : Variant, callable : Callable, create : bool = false):
|
|
if create:
|
|
undo_redo.create_action(name)
|
|
undo_redo.add_undo_property(object, property, value)
|
|
undo_redo.add_undo_method(callable)
|
|
|
|
|
|
func create_redo_property(object : Object, property : String, value : Variant, callable : Callable, commit : bool = false):
|
|
undo_redo.add_do_property(object, property, value)
|
|
undo_redo.add_do_method(callable)
|
|
if commit:
|
|
undo_redo.commit_action()
|
|
|
|
## TODO: prevent linked layer to get duplicated twice
|
|
func get_frames_duplicate(new_uid : bool = true) -> Array[Frame]:
|
|
var new_frames : Array[Frame] = pool_frames.duplicate()
|
|
for i in range(new_frames.size()):
|
|
new_frames[i] = new_frames[i].get_duplicate(new_uid)
|
|
return new_frames
|
|
|
|
|
|
func active_layer_is_valid():
|
|
return get_image_index(active_layer_uid) != -1
|
|
|
|
|
|
func add_layer(label : String = "", above_layer_uid : Vector3i = Vector3i.ZERO) -> Vector3i:
|
|
var new_index_image = IndexedColorImage.new()
|
|
if label == "":
|
|
layer_index_counter += 1
|
|
new_index_image.label = str("Layer " , layer_index_counter)
|
|
else:
|
|
new_index_image.label = label
|
|
new_index_image.layer_uid = get_uid()
|
|
new_index_image.size = canvas_size
|
|
var index = get_image_index(above_layer_uid)
|
|
if index == -1 or above_layer_uid == Vector3i.ZERO:
|
|
active_frame.layers.insert(active_frame.layers.size(), new_index_image)
|
|
else:
|
|
active_frame.layers.insert(index + 1, new_index_image)
|
|
return new_index_image.layer_uid
|
|
|
|
|
|
func duplicate_layer(layer_uid : Vector3i):
|
|
var new_index_image : IndexedColorImage = get_index_image(layer_uid)
|
|
if new_index_image != null:
|
|
new_index_image = new_index_image.get_duplicate()
|
|
else:
|
|
return
|
|
layer_index_counter += 1
|
|
new_index_image.label = str(new_index_image.label , " Duplicate", layer_index_counter)
|
|
var index = get_image_index(layer_uid)
|
|
if index == -1:
|
|
active_frame.layers.insert(active_frame.layers.size(), new_index_image)
|
|
else:
|
|
active_frame.layers.insert(index + 1, new_index_image)
|
|
|
|
|
|
func paste_copied_layer(above_layer_uid : Vector3i = Vector3i.ZERO):
|
|
var new_index_image : IndexedColorImage = cache_copied_colormap
|
|
if new_index_image != null:
|
|
new_index_image = new_index_image.get_duplicate()
|
|
else:
|
|
return
|
|
layer_index_counter += 1
|
|
new_index_image.label = str(new_index_image.label , " Copy", layer_index_counter)
|
|
var index = get_image_index(above_layer_uid)
|
|
if index == -1:
|
|
active_frame.layers.insert(active_frame.layers.size(), new_index_image)
|
|
else:
|
|
active_frame.layers.insert(index + 1, new_index_image)
|
|
active_layer_uid = new_index_image.layer_uid
|
|
|
|
|
|
func delete_layer(layer_uid : Vector3i):
|
|
var index = get_image_index(layer_uid)
|
|
if index != -1:
|
|
active_frame.layers.remove_at(index)
|
|
if active_frame.layers.size() == 0:
|
|
layer_index_counter = 0
|
|
|
|
|
|
func sort_palette() -> PackedInt32Array:
|
|
var new_palette : PackedColorArray = palette.get_sorted_palette()
|
|
var new_grid_palette : PackedInt32Array = []
|
|
for i in range(1, new_palette.size()):
|
|
var exist_index : int = palette.color_index.find(new_palette[i], 1)
|
|
while exist_index != -1 and new_grid_palette.has(exist_index):
|
|
if exist_index + 1 >= palette.color_index.size():
|
|
break
|
|
exist_index = palette.color_index.find(new_palette[i], exist_index + 1)
|
|
if new_grid_palette.has(exist_index):
|
|
continue
|
|
elif exist_index != -1:
|
|
new_grid_palette.push_back(exist_index)
|
|
if new_grid_palette.size() == palette.color_index.size() - 1:
|
|
return new_grid_palette
|
|
return []
|
|
|
|
|
|
func delete_unused_color_palette():
|
|
var new_palette : PackedColorArray = []
|
|
|
|
for frame in pool_frames:
|
|
for layer in frame.layers:
|
|
for x in range(layer.colormap.get_width()):
|
|
for y in range(layer.colormap.get_height()):
|
|
var idx : int = layer.colormap.get_pixel(x, y).r8
|
|
var color : Color = palette.color_index[idx]
|
|
if color not in new_palette:
|
|
new_palette.push_back(color)
|
|
|
|
var i = 0
|
|
while i < palette.color_index.size() and i < palette.INDEX_COLOR_SIZE:
|
|
if palette.color_index[i] not in new_palette and palette.color_index[i] != Color.TRANSPARENT:
|
|
palette.color_index[i] = Color.TRANSPARENT
|
|
i += 1
|
|
|
|
|
|
func delete_color(palette_index : int):
|
|
var gui_index := palette.palette_to_gui_index(palette_index)
|
|
if gui_index != -1:
|
|
palette.color_index[palette_index] = Color.TRANSPARENT
|
|
palette.grid_color_index.remove_at(gui_index)
|
|
palette.grid_color_index.push_back(palette_index)
|
|
|
|
|
|
func get_image_index(layer_uid : Vector3i) -> int:
|
|
var active_layer_index : int = -1
|
|
for i in range(active_frame.layers.size()):
|
|
if active_frame.layers[i].layer_uid == layer_uid:
|
|
active_layer_index = i
|
|
break
|
|
return active_layer_index
|
|
|
|
|
|
func get_index_image(layer_uid : Vector3i) -> IndexedColorImage:
|
|
var active_layer_index : int = -1
|
|
for i in range(active_frame.layers.size()):
|
|
if active_frame.layers[i].layer_uid == layer_uid:
|
|
active_layer_index = i
|
|
break
|
|
if active_layer_index == -1:
|
|
return null
|
|
return active_frame.layers[active_layer_index]
|
|
|
|
|
|
func find_index_image(layer_uid : Vector3i, cache : bool = false) -> IndexedColorImage:
|
|
var frames : Array[Frame]
|
|
if cache:
|
|
frames = _cache_pool_frames
|
|
else:
|
|
frames = pool_frames
|
|
for frame in frames:
|
|
for layer in frame.layers:
|
|
if layer.layer_uid == layer_uid:
|
|
return layer
|
|
return null
|
|
|
|
|
|
func get_layer_image(layer_uid : Vector3i) -> Image:
|
|
var image = Image.create(canvas_size.x, canvas_size.y, false, Image.FORMAT_RGBAF)
|
|
var color_map : Image = get_index_image(layer_uid).colormap
|
|
for y in range(canvas_size.y):
|
|
for x in range(canvas_size.x):
|
|
image.set_pixel(x, y, palette.color_index[color_map.get_pixel(x, y).r8])
|
|
|
|
return image
|
|
|
|
|
|
func get_image(frame : Frame = null) -> Image:
|
|
var src_frame : Frame = active_frame if frame == null else frame
|
|
var canvas_image = Image.create(canvas_size.x, canvas_size.y, false, Image.FORMAT_RGBAF)
|
|
for index in range(src_frame.layers.size()):
|
|
if not src_frame.layers[index].visible:
|
|
continue
|
|
var image = PixelPenCPP.get_image(palette.color_index, src_frame.layers[index].colormap, false)
|
|
var rect = image.get_used_rect()
|
|
canvas_image.blend_rect(image, rect, rect.position)
|
|
if frame == null:
|
|
cache_thumbnail = canvas_image
|
|
return canvas_image
|
|
|
|
|
|
func get_region_project_image(mask : Image = null) -> Image:
|
|
var canvas_image = Image.create(canvas_size.x, canvas_size.y, false, Image.FORMAT_RGBAF)
|
|
for index in range(active_frame.layers.size()):
|
|
if not active_frame.layers[index].visible:
|
|
continue
|
|
var image = PixelPenCPP.get_image(palette.color_index, active_frame.layers[index].colormap, false)
|
|
var rect = image.get_used_rect()
|
|
canvas_image.blend_rect(image, rect, rect.position)
|
|
|
|
if mask == null:
|
|
return canvas_image
|
|
else:
|
|
var rect : Rect2i = PixelPenCPP.get_mask_used_rect(mask)
|
|
return canvas_image.get_region(rect)
|
|
|
|
|
|
func get_region_project_colormap(mask : Image = null) -> Image:
|
|
var canvas_image = Image.create(canvas_size.x, canvas_size.y, false, Image.FORMAT_R8)
|
|
for index in range(active_frame.layers.size()):
|
|
if not active_frame.layers[index].visible:
|
|
continue
|
|
if mask == null:
|
|
PixelPenCPP.fill_color(active_frame.layers[index].colormap, canvas_image, Color8(255, 0, 0), null)
|
|
else:
|
|
PixelPenCPP.fill_color(active_frame.layers[index].colormap, canvas_image, Color8(255, 0, 0), mask)
|
|
if mask == null:
|
|
return canvas_image
|
|
else:
|
|
var rect : Rect2i = PixelPenCPP.get_mask_used_rect(canvas_image)
|
|
return canvas_image.get_region(rect)
|
|
|
|
|
|
func import_file(path : String) -> Vector3i:
|
|
var image : Image = Image.load_from_file(path)
|
|
image.convert(Image.FORMAT_RGBA8)
|
|
var image_size : Vector2i = image.get_size()
|
|
var layer_uid = add_layer(path.get_file().get_basename(), active_layer_uid)
|
|
var index_image : IndexedColorImage = get_index_image(layer_uid)
|
|
palette.color_index = PixelPenCPP.import_image(index_image.colormap, image, palette.color_index)
|
|
return layer_uid
|
|
|
|
|
|
func import_image(image : Image, path : String) -> Vector3i:
|
|
image.convert(Image.FORMAT_RGBA8)
|
|
var image_size : Vector2i = image.get_size()
|
|
var layer_uid : Vector3i = add_layer(path.get_file().get_basename(), active_layer_uid)
|
|
var index_image : IndexedColorImage = get_index_image(layer_uid)
|
|
palette.color_index = PixelPenCPP.import_image(index_image.colormap, image, palette.color_index)
|
|
return layer_uid
|
|
|
|
|
|
func export_png_image(path : String) -> Error:
|
|
var image : Image = get_image()
|
|
image.convert(Image.FORMAT_RGBA8)
|
|
last_export_file_path = path
|
|
return image.save_png(path)
|
|
|
|
|
|
func export_jpg_image(path : String) -> Error:
|
|
var image : Image = get_image()
|
|
last_export_file_path = path
|
|
return image.save_jpg(path, 1.0)
|
|
|
|
|
|
func export_webp_image(path : String) -> Error:
|
|
var image : Image = get_image()
|
|
image.convert(Image.FORMAT_RGBA8)
|
|
last_export_file_path = path
|
|
return image.save_webp(path)
|
|
|
|
|
|
func export_animation_gif(path : String):
|
|
# initialize exporter object with width and height of gif canvas
|
|
var exporter = PixelPen.state.GIFExporter.new(canvas_size.x, canvas_size.y)
|
|
# write image using median cut quantization method and with one second animation delay
|
|
for cell in animation_timeline:
|
|
var img : Image = get_image(cell.frame)
|
|
img.convert(Image.FORMAT_RGBA8)
|
|
exporter.add_frame(img, 1.0 / animation_fps, PixelPen.state.MedianCutQuantization)
|
|
|
|
# when you have exported all frames of animation you, then you can save data into file
|
|
# open new file with write privlige
|
|
var file: FileAccess = FileAccess.open(path, FileAccess.WRITE)
|
|
if file.get_open_error() == OK:
|
|
# save data stream into file
|
|
file.store_buffer(exporter.export_file_data())
|
|
# close the file
|
|
file.close()
|
|
|
|
|
|
func export_animation_frame(folder_path : String):
|
|
for cell_i in range(animation_timeline.size()):
|
|
var cell : AnimationCell = animation_timeline[cell_i]
|
|
var img : Image = get_image(cell.frame)
|
|
img.convert(Image.FORMAT_RGBA8)
|
|
|
|
var file_path : String = str(folder_path, "/" , project_name ,"." , cell_i + 1, ".png")
|
|
img.save_png(file_path)
|
|
|
|
|
|
func get_pool_index(frame_uid : Vector3i) ->int:
|
|
for i in range(pool_frames.size()):
|
|
if pool_frames[i].frame_uid == frame_uid:
|
|
return i
|
|
return -1
|
|
|
|
|
|
func get_pool_frame(frame_uid : Vector3i, cache : bool = false) -> Frame:
|
|
var frames : Array[Frame]
|
|
if cache:
|
|
frames = _cache_pool_frames
|
|
else:
|
|
frames = pool_frames
|
|
for frame in frames:
|
|
if frame.frame_uid == frame_uid:
|
|
return frame
|
|
return null
|
|
|
|
|
|
func get_animation_draft_pool_index() -> Array[int]:
|
|
var used : Array[int] = []
|
|
for cell in animation_timeline:
|
|
used.push_back(get_pool_index(cell.frame.frame_uid))
|
|
var unused : Array[int] = []
|
|
for frame_i in range(pool_frames.size()):
|
|
if not used.has(frame_i):
|
|
unused.push_back(frame_i)
|
|
return unused
|
|
|
|
|
|
func update_onion_skin_images():
|
|
if animation_frame_index == -1:
|
|
animation_prev_skinning_image.clear()
|
|
animation_next_skinning_image.clear()
|
|
return
|
|
|
|
if skinning_frame_index == animation_frame_index: # Do nothing active frame not change
|
|
return
|
|
|
|
animation_prev_skinning_image.clear()
|
|
animation_next_skinning_image.clear()
|
|
|
|
skinning_frame_index = animation_frame_index
|
|
var last_uid : Vector3i = animation_timeline[animation_frame_index].frame.frame_uid # to ignore linked frame
|
|
var prev_idx : int = animation_frame_index
|
|
for i in PixelPen.state.userconfig.onion_skin_total:
|
|
prev_idx -= 1
|
|
while prev_idx > 0 and animation_timeline[prev_idx].frame.frame_uid == last_uid:
|
|
prev_idx -= 1
|
|
if prev_idx >= 0 and animation_timeline[prev_idx].frame.frame_uid != animation_timeline[animation_frame_index].frame.frame_uid:
|
|
animation_prev_skinning_image.push_back(get_image(animation_timeline[prev_idx].frame))
|
|
last_uid = animation_timeline[prev_idx].frame.frame_uid
|
|
|
|
last_uid = animation_timeline[animation_frame_index].frame.frame_uid
|
|
var next_idx : int = animation_frame_index
|
|
var timeline_total = animation_timeline.size()
|
|
for i in PixelPen.state.userconfig.onion_skin_total:
|
|
next_idx += 1
|
|
while next_idx < timeline_total and animation_timeline[next_idx].frame.frame_uid == last_uid:
|
|
next_idx += 1
|
|
if next_idx < timeline_total and animation_timeline[next_idx].frame.frame_uid != animation_timeline[animation_frame_index].frame.frame_uid:
|
|
animation_next_skinning_image.push_back(get_image(animation_timeline[next_idx].frame))
|
|
last_uid = animation_timeline[next_idx].frame.frame_uid
|
|
|
|
|
|
func resolve_missing_visible_frame() -> bool:
|
|
var active_index : int = -1
|
|
if active_frame != null:
|
|
active_index = get_pool_index(active_frame.frame_uid)
|
|
if active_index != -1:
|
|
var frame := pool_frames[active_index]
|
|
for cell_i in range(animation_timeline.size()):
|
|
if frame.frame_uid == animation_timeline[cell_i].frame.frame_uid:
|
|
animation_frame_index = cell_i
|
|
canvas_pool_frame_uid = frame.frame_uid
|
|
return true
|
|
animation_frame_index = -1
|
|
canvas_pool_frame_uid = frame.frame_uid
|
|
return true
|
|
elif animation_frame_index == -1:
|
|
var unused : Array[int] = get_animation_draft_pool_index()
|
|
if unused.size() > 0:
|
|
canvas_pool_frame_uid = pool_frames[unused[0]].frame_uid
|
|
return true
|
|
|
|
if animation_timeline.size() > 0:
|
|
animation_frame_index = 0
|
|
canvas_pool_frame_uid = animation_timeline[0].frame.frame_uid
|
|
return true
|
|
elif pool_frames.size() > 0:
|
|
animation_frame_index = -1
|
|
canvas_pool_frame_uid = pool_frames[0].frame_uid
|
|
return true
|
|
else:
|
|
pool_frames = [Frame.create(get_uid())]
|
|
canvas_pool_frame_uid = pool_frames[0].frame_uid
|
|
add_layer()
|
|
active_layer_uid = active_frame.layers[0].layer_uid
|
|
return true
|
|
|
|
|
|
func clean_invisible_color():
|
|
for frame in pool_frames:
|
|
for layer in frame.layers:
|
|
PixelPenCPP.clean_invisible_color(layer.colormap, palette.color_index)
|
|
if use_sample:
|
|
for frame in _cache_pool_frames:
|
|
for layer in frame.layers:
|
|
PixelPenCPP.clean_invisible_color(layer.colormap, palette.color_index)
|
|
for i in palette.color_index.size():
|
|
if palette.color_index[i].a == 0:
|
|
palette.color_index[i] = Color.TRANSPARENT
|
|
|
|
|
|
func reset_brush_to_default():
|
|
var Tool := load("res://addons/net.yarvis.pixel_pen/editor/editor_canvas/tool.gd")
|
|
PixelPen.state.userconfig.brush.clear()
|
|
for i in range(0, 16):
|
|
var start : Vector2 = Vector2(0.5, 0.5)
|
|
var end : Vector2 = Vector2(i, i) + Vector2(0.5, 0.5)
|
|
var rect : Rect2 = Rect2(start, end - start)
|
|
var image : Image = Image.create(rect.size.x + 1, rect.size.y + 1, false, Image.FORMAT_R8)
|
|
var center_mass : Vector2 = Tool.get_midpoint_ellipse(start, end, Color8(255, 0, 0), image)
|
|
if center_mass != Vector2(-1, -1):
|
|
var image_f = PixelPenCPP.get_image_flood(center_mass, image, Vector2i.ZERO, true)
|
|
if image_f != null:
|
|
PixelPenCPP.fill_color(image_f, image, Color8(255, 0, 0), null)
|
|
image = image.get_region(PixelPenCPP.get_mask_used_rect(image))
|
|
PixelPen.state.userconfig.brush.push_back(image)
|
|
PixelPen.state.userconfig.save()
|
|
|
|
|
|
func reset_stamp_to_default():
|
|
PixelPen.state.userconfig.stamp.clear()
|
|
PixelPen.state.userconfig.save()
|
|
|
|
|
|
func crop_canvas(mask : Image) -> void:
|
|
if use_sample:
|
|
return
|
|
var rect : Rect2i = PixelPenCPP.get_mask_used_rect(mask)
|
|
for frame in pool_frames:
|
|
for layer in frame.layers:
|
|
var colormap : Image = layer.colormap.duplicate()
|
|
layer.size = rect.size
|
|
layer.colormap = colormap.get_region(rect)
|
|
_cache_pool_frames = []
|
|
undo_redo.clear_history()
|
|
canvas_size = rect.size
|