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}