Skip to content

Commit

Permalink
Implement ImageManager (#334)
Browse files Browse the repository at this point in the history
* Initial implementation of an ImageManager

* Fix linting issues

* Fix linting issues

* Attach image manager to the Window

* Validate accepted image types

* Create static utils methods, remove ImageProvider

* Store base64 string of image file

* Send canvas notification if unable to get the pixbuf

* IJnitial work to save images inside the akira file

* Save the image unique name for later. Do not go the base64 route

* Save image inside the Pictures folder

* Delete picutre from saved file if not references anymore

* Clear leftover images if saving the first time, in case we are overriding an existing file

* Clear leftover images only on overwrite

* Load saved images

* Resample pixbuf quality on image resize

* Keep track of edited attributes

* Remove unused methods

* Improve error message

* Use unique IDs for image filename

* Prevent resetting pixbuf width and height when loading images from saved file

* Restore the correct order of the items

* Remove debug messages
  • Loading branch information
Alecaddd authored Apr 26, 2020
1 parent 3feb696 commit 697c977
Show file tree
Hide file tree
Showing 18 changed files with 440 additions and 101 deletions.
50 changes: 50 additions & 0 deletions src/FileFormat/AkiraFile.vala
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public class Akira.FileFormat.AkiraFile : Akira.FileFormat.ZipArchiveHandler {
public File pictures_folder { get; private set; }
public File thumbnails_folder { get; private set; }

public bool overwrite = false;

private File content_file { get; set; }
public string path {
owned get {
Expand All @@ -45,6 +47,7 @@ public class Akira.FileFormat.AkiraFile : Akira.FileFormat.ZipArchiveHandler {
new FileFormat.JsonLoader (window, content_json);

update_recent_list.begin ();

debug ("Version from file: %s", content_json.get_string_member ("version"));
} catch (Error e) {
error ("Could not load file: %s", e.message);
Expand All @@ -53,6 +56,7 @@ public class Akira.FileFormat.AkiraFile : Akira.FileFormat.ZipArchiveHandler {

public void save_file () {
try {
save_images.begin ();
var content = new FileFormat.JsonContent (window);

content.save_content ();
Expand Down Expand Up @@ -126,4 +130,50 @@ public class Akira.FileFormat.AkiraFile : Akira.FileFormat.ZipArchiveHandler {

window.app.update_recent_files_list ();
}

/**
* Save all the images used in the Canvas and make a copy in the Pictures folder.
*/
public async void save_images () {
// Clear potential leftover images if we're overwriting an existing file.
if (overwrite) {
try {
Dir dir = Dir.open (pictures_folder.get_path (), 0);
string? name = null;
while ((name = dir.read_name ()) != null) {
var file = File.new_for_path (Path.build_filename (pictures_folder.get_path (), name));
file_collector.mark_for_deletion (file);
}
} catch (FileError err) {
stderr.printf (err.message);
}
overwrite = false;
}

foreach (var image in window.items_manager.images) {
var image_file = File.new_for_path (
Path.build_filename (pictures_folder.get_path (), image.manager.filename)
);

// Copy the file if it doesn't exist, or increase the reference count.
if (!image_file.query_exists ()) {
copy_image (image.manager.file, image_file);
continue;
}
}
}

/**
* Decrease the reference count to an existing image, which will cause its
* deletion if the count reaches 0.
*/
public async void remove_image (string filename) {
var image_file = File.new_for_path (
Path.build_filename (pictures_folder.get_path (), filename)
);

if (image_file.query_exists ()) {
file_collector.unref_file (image_file);
}
}
}
5 changes: 4 additions & 1 deletion src/FileFormat/FileManager.vala
Original file line number Diff line number Diff line change
Expand Up @@ -78,17 +78,20 @@ public class Akira.FileFormat.FileManager : Object {
}

private void save_file_as_response (Gtk.FileChooserNative dialog, int response_id) {
bool overwrite = false;

switch (response_id) {
case Gtk.ResponseType.ACCEPT:
File file;
var save_file = dialog.get_file ();
var path = save_file.get_path ();
if (path.has_suffix (".akira")) {
file = save_file;
overwrite = true;
} else {
file = File.new_for_path (path + ".akira");
}
window.save_new_file (file);
window.save_new_file (file, overwrite);
window.event_bus.file_saved (dialog.get_current_name ());
break;
}
Expand Down
7 changes: 6 additions & 1 deletion src/FileFormat/JsonObject.vala
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public class Akira.FileFormat.JsonObject : GLib.Object {
}

transform = new Json.Object ();

write_transform ();
}

Expand Down Expand Up @@ -91,8 +92,12 @@ public class Akira.FileFormat.JsonObject : GLib.Object {
} else if (type == typeof (Goo.CanvasItemVisibility)) {
item.get_property (spec.get_name (), ref val);
obj.set_int_member (spec.get_name (), val.get_enum ());
} else if (type == typeof (Akira.Lib.Managers.ImageManager)) {
var canvas_image = item as Akira.Lib.Models.CanvasImage;
obj.set_string_member ("image_id", canvas_image.manager.filename);
} else {
// warning ("Property type %s not yet supported: %s\n", type.name (), spec.get_name ());
// Leave this comment for debug purpose.
// warning ("Property type %s not yet supported: %s\n", type.name (), spec.get_name ());
}
}

Expand Down
13 changes: 13 additions & 0 deletions src/FileFormat/ZipArchiveHandler.vala
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,19 @@ public class Akira.FileFormat.ZipArchiveHandler : GLib.Object {
}
}

public virtual void copy_image (GLib.File old_file, GLib.File new_file) {
try {
old_file.copy (new_file, 0, null, (current_num_bytes, total_num_bytes) => {
// Report copy-status:
print ("%" + int64.FORMAT + " bytes of %" + int64.FORMAT + " bytes copied.\n",
current_num_bytes, total_num_bytes);
});
file_collector.ref_file (new_file);
} catch (Error e) {
print ("Error: %s\n", e.message);
}
}


/**
* Takes care of the reference counting for files inside the archive.
Expand Down
2 changes: 2 additions & 0 deletions src/Layouts/Partials/TransformPanel.vala
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,8 @@ public class Akira.Layouts.Partials.TransformPanel : Gtk.Grid {
y.value = position["y"];
y.notify["value"].connect (y_notify_value);
}

window.event_bus.file_edited ();
}

public void x_notify_value () {
Expand Down
95 changes: 95 additions & 0 deletions src/Lib/Managers/ImageManager.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright (c) 2020 Alecaddd (https://alecaddd.com)
*
* This file is part of Akira.
*
* Akira is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* Akira is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with Akira. If not, see <https://www.gnu.org/licenses/>.
*
* Authored by: Adam Bieńkowski <[email protected]>
* Authored by: Alessandro "Alecaddd" Castellani <[email protected]>
*/

public class Akira.Lib.Managers.ImageManager : Object {
public GLib.File file { get; set; }

// Save the generated Pixbuf for later reference.
public Gdk.Pixbuf pixbuf;
// The unique name of the loaded image, including the current timestamp.
public string filename;

private const string[] ACCEPTED_TYPES = {
"image/jpeg",
"image/png",
"image/tiff",
"image/svg+xml",
"image/gif"
};

public ImageManager (GLib.File _file, int id) {
file = _file;

var timestamp = new GLib.DateTime.now_utc ();
filename = ("akira-img-%i-%s.%s").printf (
id,
timestamp.to_unix ().to_string (),
Utils.Image.get_extension (file)
);
}

/**
* Initialize a new ImageManager from a previously saved file.
* We use this to avoid changing the filename which should be unique.
*
* @param {GLib.File} _file - The file loaded from the saved archive.
* @param {string} _filename - The original filename of the saved file.
*/
public ImageManager.from_archive (GLib.File _file, string _filename) {
file = _file;
filename = _filename;
}

/**
* Generate the Pixbuf from the given file. This method is also called to
* resample the quality of the pixbuf when the image is resized.
*
* @param {int} width - The requested width for the resample.
* @param {int} height - The requested height for the resample.
* @return Gdk.Pixbuf
*/
public async Gdk.Pixbuf get_pixbuf (int width = -1, int height = -1) throws Error {
FileInputStream stream;

try {
stream = yield file.read_async ();
} catch (Error e) {
throw e;
}

if (width != -1 && height != -1) {
try {
pixbuf = yield new Gdk.Pixbuf.from_stream_at_scale_async (stream, width, height, false);
return pixbuf;
} catch (Error e) {
throw e;
}
} else {
try {
pixbuf = yield new Gdk.Pixbuf.from_stream_async (stream);
return pixbuf;
} catch (Error e) {
throw e;
}
}
}
}
Loading

0 comments on commit 697c977

Please sign in to comment.