diff --git a/CHANGELOG.md b/CHANGELOG.md
index d033b914e..3bf2e2112 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,9 +5,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [v0.8] - Unreleased
+This update has been brought to you by the contributions of:
+Darshan Phaldesai (luiq54)
### Added
- Project tabs! You can now have multiple projects open at the same time, and access each one with tabs.
+- You can now draw on the tiling mode previews! ([#65](https://github.com/Orama-Interactive/Pixelorama/issues/65))
+
+### Changed
+- The .pxo file structure has been changed. It's now consisted of a JSON-structured metadata part, where all the data that can be stored as text are, and a binary part, that contain all the actual image data for each cel and project brush.
## [v0.7.1] - Unreleased
diff --git a/src/Autoload/DrawingAlgos.gd b/src/Autoload/DrawingAlgos.gd
index 858046336..679a95f32 100644
--- a/src/Autoload/DrawingAlgos.gd
+++ b/src/Autoload/DrawingAlgos.gd
@@ -17,11 +17,11 @@ func draw_pixel_blended(sprite : Image, pos : Vector2, color : Color, pen_pressu
var x_max = Global.current_project.x_max
var y_min = Global.current_project.y_min
var y_max = Global.current_project.y_max
-
+
# #Check if Tiling is enabled and whether mouse is in TilingPreviews
if Global.tile_mode and point_in_rectangle(pos,Vector2( - Global.current_project.size.x - 1 , - Global.current_project.size.y -1 ), Vector2(2 * Global.current_project.size.x, 2 * Global.current_project.size.y)):
pos = pos.posmodv(Global.current_project.size)
-
+
if !point_in_rectangle(pos, Vector2(x_min - 1, y_min - 1), Vector2(x_max, y_max)):
return
@@ -107,7 +107,7 @@ func draw_brush(sprite : Image, pos : Vector2, color : Color, current_mouse_butt
# #Check if Tiling is enabled and whether mouse is in TilingPreviews
if Global.tile_mode and point_in_rectangle(pos,Vector2( - Global.current_project.size.x - 1 , - Global.current_project.size.y -1 ), Vector2(2 * Global.current_project.size.x, 2 * Global.current_project.size.y)):
pos = pos.posmodv(Global.current_project.size)
-
+
var dst := rectangle_center(pos, custom_brush_size)
var src_rect := Rect2(Vector2.ZERO, custom_brush_size + Vector2.ONE)
# Rectangle with the same size as the brush, but at cursor's position
diff --git a/src/Autoload/OpenSave.gd b/src/Autoload/OpenSave.gd
index 7f7229fb9..3f160f313 100644
--- a/src/Autoload/OpenSave.gd
+++ b/src/Autoload/OpenSave.gd
@@ -38,7 +38,52 @@ func open_pxo_file(path : String, untitled_backup : bool = false) -> void:
else:
new_project = Project.new([], path.get_file())
- var file_version := file.get_line() # Example, "v0.7.10-beta"
+ var first_line := file.get_line()
+ var dict := JSON.parse(first_line)
+ if dict.error != OK:
+ open_old_pxo_file(file, new_project, first_line)
+ else:
+ if typeof(dict.result) != TYPE_DICTIONARY:
+ print("Error, json parsed result is: %s" % typeof(dict.result))
+ file.close()
+ return
+
+ new_project.deserialize(dict.result)
+ for frame in new_project.frames:
+ for cel in frame.cels:
+ var buffer := file.get_buffer(new_project.size.x * new_project.size.y * 4)
+ cel.image.create_from_data(new_project.size.x, new_project.size.y, false, Image.FORMAT_RGBA8, buffer)
+ cel.image = cel.image # Just to call image_changed
+
+ if dict.result.has("brushes"):
+ for brush in dict.result.brushes:
+ var b_width = brush.size_x
+ var b_height = brush.size_y
+ var buffer := file.get_buffer(b_width * b_height * 4)
+ var image := Image.new()
+ image.create_from_data(b_width, b_height, false, Image.FORMAT_RGBA8, buffer)
+ new_project.brushes.append(image)
+ Global.create_brush_button(image)
+
+ file.close()
+ if !empty_project:
+ Global.projects.append(new_project)
+ Global.tabs.current_tab = Global.tabs.get_tab_count() - 1
+ else:
+ new_project.frames = new_project.frames # Just to call frames_changed
+ new_project.layers = new_project.layers # Just to call layers_changed
+ Global.canvas.camera_zoom()
+
+ if not untitled_backup:
+ # Untitled backup should not change window title and save path
+ current_save_paths[Global.current_project_index] = path
+ Global.window_title = path.get_file() + " - Pixelorama " + Global.current_version
+
+
+# For pxo files older than v0.8
+func open_old_pxo_file(file : File, new_project : Project, first_line : String) -> void:
+# var file_version := file.get_line() # Example, "v0.7.10-beta"
+ var file_version := first_line
var file_ver_splitted := file_version.split("-")
var file_ver_splitted_numbers := file_ver_splitted[0].split(".")
@@ -104,6 +149,8 @@ func open_pxo_file(path : String, untitled_backup : bool = false) -> void:
if file_major_version >= 0 and file_minor_version >= 7:
if frame in linked_cels[layer_i]:
new_project.layers[layer_i].linked_cels.append(frame_class)
+ frame_class.cels[layer_i].image = new_project.layers[layer_i].linked_cels[0].cels[layer_i].image
+ frame_class.cels[layer_i].image_texture = new_project.layers[layer_i].linked_cels[0].cels[layer_i].image_texture
layer_i += 1
layer_line = file.get_line()
@@ -121,6 +168,7 @@ func open_pxo_file(path : String, untitled_backup : bool = false) -> void:
guide.add_point(Vector2(file.get_16(), 99999))
guide.has_focus = false
Global.canvas.add_child(guide)
+ new_project.guides.append(guide)
guide_line = file.get_line()
new_project.size = Vector2(width, height)
@@ -141,6 +189,7 @@ func open_pxo_file(path : String, untitled_backup : bool = false) -> void:
guide.add_point(Vector2(file.get_16(), 99999))
guide.has_focus = false
Global.canvas.add_child(guide)
+ new_project.guides.append(guide)
guide_line = file.get_line()
# Load tool options
@@ -181,113 +230,39 @@ func open_pxo_file(path : String, untitled_backup : bool = false) -> void:
new_project.animation_tags = new_project.animation_tags # To execute animation_tags_changed()
tag_line = file.get_line()
- file.close()
- if !empty_project:
- Global.projects.append(new_project)
- Global.tabs.current_tab = Global.tabs.get_tab_count() - 1
- else:
- new_project.frames = new_project.frames # Just to call frames_changed
- new_project.layers = new_project.layers # Just to call layers_changed
- Global.canvas.camera_zoom()
-
- if not untitled_backup:
- # Untitled backup should not change window title and save path
- current_save_paths[Global.current_project_index] = path
- Global.window_title = path.get_file() + " - Pixelorama " + Global.current_version
-
func save_pxo_file(path : String, autosave : bool, project : Project = Global.current_project) -> void:
var file := File.new()
var err := file.open_compressed(path, File.WRITE, File.COMPRESSION_ZSTD)
if err == OK:
- # Store Pixelorama version
- file.store_line(Global.current_version)
+ if !autosave:
+ project.name = path.get_file()
+ current_save_paths[Global.current_project_index] = path
- # Store Global layers
- for layer in project.layers:
- file.store_line(".")
- file.store_line(layer.name)
- file.store_8(layer.visible)
- file.store_8(layer.locked)
- file.store_8(layer.new_cels_linked)
- var linked_cels := []
- for frame in layer.linked_cels:
- linked_cels.append(project.frames.find(frame))
- file.store_var(linked_cels) # Linked cels as cel numbers
-
- file.store_line("END_GLOBAL_LAYERS")
-
- # Store frames
+ var to_save = JSON.print(project.serialize())
+ file.store_line(to_save)
for frame in project.frames:
- file.store_line("--")
- file.store_16(project.size.x)
- file.store_16(project.size.y)
- for cel in frame.cels: # Store canvas layers
- file.store_line("-")
+ for cel in frame.cels:
file.store_buffer(cel.image.get_data())
- file.store_float(cel.opacity)
- file.store_line("END_LAYERS")
- file.store_line("END_FRAMES")
-
- # Store guides
- for child in Global.canvas.get_children():
- if child is Guide:
- file.store_line("|")
- file.store_8(child.type)
- if child.type == child.Types.HORIZONTAL:
- file.store_16(child.points[0].y)
- file.store_16(child.points[1].y)
- else:
- file.store_16(child.points[1].x)
- file.store_16(child.points[0].x)
- file.store_line("END_GUIDES")
-
- # Save tool options
- var left_color : Color = Global.color_pickers[0].color
- var right_color : Color = Global.color_pickers[1].color
- var left_brush_size : int = Global.brush_sizes[0]
- var right_brush_size : int = Global.brush_sizes[1]
- file.store_var(left_color)
- file.store_var(right_color)
- file.store_8(left_brush_size)
- file.store_8(right_brush_size)
-
- # Save custom brushes
- for i in range(project.brushes.size()):
- var brush = project.brushes[i]
- file.store_line("/")
- file.store_16(brush.get_size().x)
- file.store_16(brush.get_size().y)
+ for brush in project.brushes:
file.store_buffer(brush.get_data())
- file.store_line("END_BRUSHES")
-
- # Store animation tags
- for tag in project.animation_tags:
- file.store_line(".T/")
- file.store_line(tag.name)
- file.store_var(tag.color)
- file.store_8(tag.from)
- file.store_8(tag.to)
- file.store_line("END_FRAME_TAGS")
file.close()
- if project.has_changed and not autosave:
- project.has_changed = false
-
if autosave:
Global.notification_label("File autosaved")
else:
# First remove backup then set current save path
+ if project.has_changed:
+ project.has_changed = false
remove_backup(Global.current_project_index)
- current_save_paths[Global.current_project_index] = path
Global.notification_label("File saved")
- project.name = path.get_file()
Global.window_title = path.get_file() + " - Pixelorama " + Global.current_version
else:
Global.notification_label("File failed to save")
+ file.close()
func update_autosave() -> void:
diff --git a/src/Canvas.gd b/src/Canvas.gd
index 325642dc7..a729f79bb 100644
--- a/src/Canvas.gd
+++ b/src/Canvas.gd
@@ -81,7 +81,7 @@ func _draw() -> void:
draw_set_transform(mouse_pos, rotation, scale)
for rect in Global.left_circle_points:
draw_rect(Rect2(rect, Vector2.ONE), Color.blue, false)
-
+
#Check for tile mode
if Global.tile_mode and point_in_rectangle(mouse_pos,Vector2( - Global.current_project.size.x - 1 , - Global.current_project.size.y -1 ), Vector2(2 * Global.current_project.size.x, 2 * Global.current_project.size.y)):
if !point_in_rectangle(mouse_pos, Vector2(Global.current_project.x_min - 1,Global.current_project.y_min - 1), Vector2(Global.current_project.x_max,Global.current_project.y_max)):
diff --git a/src/Classes/Cel.gd b/src/Classes/Cel.gd
index 1a79058c8..d08d28d66 100644
--- a/src/Classes/Cel.gd
+++ b/src/Classes/Cel.gd
@@ -10,11 +10,12 @@ var opacity : float
func _init(_image := Image.new(), _opacity := 1.0) -> void:
+ image_texture = ImageTexture.new()
self.image = _image
opacity = _opacity
func image_changed(value : Image) -> void:
image = value
- image_texture = ImageTexture.new()
- image_texture.create_from_image(image, 0)
+ if !image.is_empty():
+ image_texture.create_from_image(image, 0)
diff --git a/src/Classes/Layer.gd b/src/Classes/Layer.gd
index da8d20a3d..357f22c81 100644
--- a/src/Classes/Layer.gd
+++ b/src/Classes/Layer.gd
@@ -1,5 +1,5 @@
class_name Layer extends Reference
-# A class for layer properties
+# A class for layer properties.
var name := ""
@@ -7,7 +7,7 @@ var visible := true
var locked := false
var frame_container : HBoxContainer
var new_cels_linked := false
-var linked_cels := [] # Array of Canvases
+var linked_cels := [] # Array of Frames
func _init(_name := tr("Layer") + " 0", _visible := true, _locked := false, _frame_container := HBoxContainer.new(), _new_cels_linked := false, _linked_cels := []) -> void:
diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd
index ddb900a29..f9135ea1c 100644
--- a/src/Classes/Project.gd
+++ b/src/Classes/Project.gd
@@ -144,6 +144,117 @@ func change_project() -> void:
Global.control.file_menu.set_item_text(3, tr("Save"))
+func serialize() -> Dictionary:
+ var layer_data := []
+ for layer in layers:
+ var linked_cels := []
+ for cel in layer.linked_cels:
+ linked_cels.append(frames.find(cel))
+
+ layer_data.append({
+ "name" : layer.name,
+ "visible" : layer.visible,
+ "locked" : layer.locked,
+ "new_cels_linked" : layer.new_cels_linked,
+ "linked_cels" : linked_cels,
+ })
+
+ var tag_data := []
+ for tag in animation_tags:
+ tag_data.append({
+ "name" : tag.name,
+ "color" : tag.color.to_html(),
+ "from" : tag.from,
+ "to" : tag.to,
+ })
+
+ var guide_data := []
+ for guide in guides:
+ var coords = guide.points[0].x
+ if guide.type == Guide.Types.HORIZONTAL:
+ coords = guide.points[0].y
+
+ guide_data.append({"type" : guide.type, "pos" : coords})
+
+ var frame_data := []
+ for frame in frames:
+ var cel_data := []
+ for cel in frame.cels:
+ cel_data.append({
+ "opacity" : cel.opacity,
+# "image_data" : cel.image.get_data()
+ })
+ frame_data.append({
+ "cels" : cel_data
+ })
+ var brush_data := []
+ for brush in brushes:
+ brush_data.append({
+ "size_x" : brush.get_size().x,
+ "size_y" : brush.get_size().y
+ })
+
+ var project_data := {
+ "pixelorama_version" : Global.current_version,
+ "name" : name,
+ "size_x" : size.x,
+ "size_y" : size.y,
+ "save_path" : OpenSave.current_save_paths[Global.projects.find(self)],
+ "layers" : layer_data,
+ "tags" : tag_data,
+ "guides" : guide_data,
+ "frames" : frame_data,
+ "brushes" : brush_data,
+ }
+
+ return project_data
+
+
+func deserialize(dict : Dictionary) -> void:
+ if dict.has("name"):
+ name = dict.name
+ if dict.has("size_x"):
+ size.x = dict.size_x
+ if dict.has("size_y"):
+ size.y = dict.size_y
+ if dict.has("save_path"):
+ OpenSave.current_save_paths[Global.projects.find(self)] = dict.save_path
+ if dict.has("frames"):
+ for frame in dict.frames:
+ var cels := []
+ for cel in frame.cels:
+ cels.append(Cel.new(Image.new(), cel.opacity))
+ frames.append(Frame.new(cels))
+ if dict.has("layers"):
+ var layer_i := 0
+ for saved_layer in dict.layers:
+ var linked_cels := []
+ for linked_cel_number in saved_layer.linked_cels:
+ linked_cels.append(frames[linked_cel_number])
+ frames[linked_cel_number].cels[layer_i].image = linked_cels[0].cels[layer_i].image
+ frames[linked_cel_number].cels[layer_i].image_texture = linked_cels[0].cels[layer_i].image_texture
+ var layer := Layer.new(saved_layer.name, saved_layer.visible, saved_layer.locked, HBoxContainer.new(), saved_layer.new_cels_linked, linked_cels)
+ layers.append(layer)
+ layer_i += 1
+ if dict.has("tags"):
+ for tag in dict.tags:
+ animation_tags.append(AnimationTag.new(tag.name, Color(tag.color), tag.from, tag.to))
+ self.animation_tags = animation_tags
+ if dict.has("guides"):
+ for g in dict.guides:
+ var guide := Guide.new()
+ guide.type = g.type
+ if guide.type == Guide.Types.HORIZONTAL:
+ guide.add_point(Vector2(-99999, g.pos))
+ guide.add_point(Vector2(99999, g.pos))
+ else:
+ guide.add_point(Vector2(g.pos, -99999))
+ guide.add_point(Vector2(g.pos, 99999))
+ guide.has_focus = false
+ Global.canvas.add_child(guide)
+ guides.append(guide)
+
+
func name_changed(value : String) -> void:
name = value
Global.tabs.set_tab_title(Global.tabs.current_tab, name)
diff --git a/src/UI/Rulers/Guides.gd b/src/UI/Rulers/Guides.gd
index 62552f663..39a11ce83 100644
--- a/src/UI/Rulers/Guides.gd
+++ b/src/UI/Rulers/Guides.gd
@@ -1,5 +1,4 @@
-class_name Guide
-extends Line2D
+class_name Guide extends Line2D
enum Types {HORIZONTAL, VERTICAL}