From b0338ab09a0ba3224c2f760e9efa7dd0b7b64982 Mon Sep 17 00:00:00 2001
From: OverloadedOrama <35376950+OverloadedOrama@users.noreply.github.com>
Date: Wed, 10 Jun 2020 04:17:39 +0300
Subject: [PATCH] Changed structure of .pxo files
The structure of the .pxo files is 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.
This makes it easier for users to understand the .pxo structure, easier to add more changes without having to check versions for backwards compatibility, easier to be opened by third-party apps and it allows us to make an "Export JSON metadata" option, that will export just the metadata in JSON format, without the binary image data.
It's backwards compatible and .pxo files from as far as v0.5 are still supported.
---
CHANGELOG.md | 6 ++
src/Autoload/DrawingAlgos.gd | 6 +-
src/Autoload/OpenSave.gd | 149 +++++++++++++++--------------------
src/Canvas.gd | 2 +-
src/Classes/Cel.gd | 5 +-
src/Classes/Layer.gd | 4 +-
src/Classes/Project.gd | 111 ++++++++++++++++++++++++++
src/UI/Rulers/Guides.gd | 3 +-
8 files changed, 189 insertions(+), 97 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d033b914e12..3bf2e2112f0 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 858046336b8..679a95f326f 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 7f7229fb979..3f160f313da 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)
-
- # 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
+ if !autosave:
+ project.name = path.get_file()
+ current_save_paths[Global.current_project_index] = path
+
+ 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)
- 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")
+ for brush in project.brushes:
+ file.store_buffer(brush.get_data())
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 325642dc77b..a729f79bb78 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 1a79058c8d9..d08d28d66fd 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 da8d20a3d58..357f22c817a 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 ddb900a298d..f9135ea1c44 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 62552f663fb..39a11ce83d3 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}