Browse Source

PixelPaint: Add ProjectLoader to abstract away opening of files

This new class will open and parse files (either images directly or .pp
project files) and one can get the parsed Image as well as other
information from it.

This patch removes a bunch of 'try_create_from..." methods from Image in
favor of using the ProjectLoader.

The only json_metadata that is available are Guides for now.
Tobias Christiansen 3 years ago
parent
commit
508d563189

+ 1 - 0
Userland/Applications/PixelPaint/CMakeLists.txt

@@ -30,6 +30,7 @@ set(SOURCES
     PenTool.cpp
     PickerTool.cpp
     PixelPaintWindowGML.h
+    ProjectLoader.cpp
     RectangleTool.cpp
     RectangleSelectTool.cpp
     Mask.cpp

+ 17 - 101
Userland/Applications/PixelPaint/Image.cpp

@@ -24,23 +24,6 @@
 
 namespace PixelPaint {
 
-static RefPtr<Gfx::Bitmap> try_decode_bitmap(ByteBuffer const& bitmap_data)
-{
-    // Spawn a new ImageDecoder service process and connect to it.
-    auto client = ImageDecoderClient::Client::construct();
-
-    // FIXME: Find a way to avoid the memory copying here.
-    auto decoded_image_or_error = client->decode_image(bitmap_data);
-    if (!decoded_image_or_error.has_value())
-        return nullptr;
-
-    // FIXME: Support multi-frame images?
-    auto decoded_image = decoded_image_or_error.release_value();
-    if (decoded_image.frames.is_empty())
-        return nullptr;
-    return move(decoded_image.frames[0].bitmap);
-}
-
 RefPtr<Image> Image::try_create_with_size(Gfx::IntSize const& size)
 {
     if (size.is_empty())
@@ -72,6 +55,23 @@ void Image::paint_into(GUI::Painter& painter, Gfx::IntRect const& dest_rect) con
     }
 }
 
+RefPtr<Gfx::Bitmap> Image::try_decode_bitmap(ByteBuffer const& bitmap_data)
+{
+    // Spawn a new ImageDecoder service process and connect to it.
+    auto client = ImageDecoderClient::Client::construct();
+
+    // FIXME: Find a way to avoid the memory copying here.
+    auto decoded_image_or_error = client->decode_image(bitmap_data);
+    if (!decoded_image_or_error.has_value())
+        return nullptr;
+
+    // FIXME: Support multi-frame images?
+    auto decoded_image = decoded_image_or_error.release_value();
+    if (decoded_image.frames.is_empty())
+        return nullptr;
+    return move(decoded_image.frames[0].bitmap);
+}
+
 RefPtr<Image> Image::try_create_from_bitmap(NonnullRefPtr<Gfx::Bitmap> bitmap)
 {
     auto image = try_create_with_size({ bitmap->width(), bitmap->height() });
@@ -86,44 +86,6 @@ RefPtr<Image> Image::try_create_from_bitmap(NonnullRefPtr<Gfx::Bitmap> bitmap)
     return image;
 }
 
-Result<NonnullRefPtr<Image>, String> Image::try_create_from_pixel_paint_fd(int fd, String const& file_path)
-{
-    auto file = Core::File::construct();
-    file->open(fd, Core::OpenMode::ReadOnly, Core::File::ShouldCloseFileDescriptor::No);
-    if (file->has_error())
-        return String { file->error_string() };
-
-    return try_create_from_pixel_paint_file(file, file_path);
-}
-
-Result<NonnullRefPtr<Image>, String> Image::try_create_from_pixel_paint_path(String const& file_path)
-{
-    auto file_or_error = Core::File::open(file_path, Core::OpenMode::ReadOnly);
-    if (file_or_error.is_error())
-        return String { file_or_error.error().string() };
-
-    return try_create_from_pixel_paint_file(*file_or_error.value(), file_path);
-}
-
-Result<NonnullRefPtr<Image>, String> Image::try_create_from_pixel_paint_file(Core::File& file, String const& file_path)
-{
-    auto contents = file.read_all();
-
-    auto json_or_error = JsonValue::from_string(contents);
-    if (!json_or_error.has_value())
-        return String { "Not a valid PP file"sv };
-
-    auto& json = json_or_error.value().as_object();
-    auto image_or_error = try_create_from_pixel_paint_json(json);
-
-    if (image_or_error.is_error())
-        return image_or_error.release_error();
-
-    auto image = image_or_error.release_value();
-    image->set_path(file_path);
-    return image;
-}
-
 Result<NonnullRefPtr<Image>, String> Image::try_create_from_pixel_paint_json(JsonObject const& json)
 {
     auto image = try_create_with_size({ json.get("width").to_i32(), json.get("height").to_i32() });
@@ -163,52 +125,6 @@ Result<NonnullRefPtr<Image>, String> Image::try_create_from_pixel_paint_json(Jso
     return image.release_nonnull();
 }
 
-Result<NonnullRefPtr<Image>, String> Image::try_create_from_fd_and_close(int fd, String const& file_path)
-{
-    auto image_or_error = try_create_from_pixel_paint_fd(fd, file_path);
-    if (!image_or_error.is_error()) {
-        close(fd);
-        return image_or_error.release_value();
-    }
-
-    auto file_or_error = MappedFile::map_from_fd_and_close(fd, file_path);
-    if (file_or_error.is_error())
-        return String::formatted("Unable to mmap file {}", file_or_error.error().string());
-
-    auto& mapped_file = *file_or_error.value();
-    // FIXME: Find a way to avoid the memory copy here.
-    auto bitmap = try_decode_bitmap(ByteBuffer::copy(mapped_file.bytes()));
-    if (!bitmap)
-        return String { "Unable to decode image"sv };
-    auto image = Image::try_create_from_bitmap(bitmap.release_nonnull());
-    if (!image)
-        return String { "Unable to allocate Image"sv };
-    image->set_path(file_path);
-    return image.release_nonnull();
-}
-
-Result<NonnullRefPtr<Image>, String> Image::try_create_from_path(String const& file_path)
-{
-    auto image_or_error = try_create_from_pixel_paint_path(file_path);
-    if (!image_or_error.is_error())
-        return image_or_error.release_value();
-
-    auto file_or_error = MappedFile::map(file_path);
-    if (file_or_error.is_error())
-        return String { "Unable to mmap file"sv };
-
-    auto& mapped_file = *file_or_error.value();
-    // FIXME: Find a way to avoid the memory copy here.
-    auto bitmap = try_decode_bitmap(ByteBuffer::copy(mapped_file.bytes()));
-    if (!bitmap)
-        return String { "Unable to decode image"sv };
-    auto image = Image::try_create_from_bitmap(bitmap.release_nonnull());
-    if (!image)
-        return String { "Unable to allocate Image"sv };
-    image->set_path(file_path);
-    return image.release_nonnull();
-}
-
 void Image::serialize_as_json(JsonObjectSerializer<StringBuilder>& json) const
 {
     json.add("width", m_size.width());

+ 3 - 7
Userland/Applications/PixelPaint/Image.h

@@ -46,10 +46,10 @@ protected:
 class Image : public RefCounted<Image> {
 public:
     static RefPtr<Image> try_create_with_size(Gfx::IntSize const&);
-    static Result<NonnullRefPtr<Image>, String> try_create_from_fd_and_close(int fd, String const& file_path);
-    static Result<NonnullRefPtr<Image>, String> try_create_from_path(String const& file_path);
-    static RefPtr<Image> try_create_from_bitmap(NonnullRefPtr<Gfx::Bitmap>);
     static Result<NonnullRefPtr<Image>, String> try_create_from_pixel_paint_json(JsonObject const&);
+    static RefPtr<Image> try_create_from_bitmap(NonnullRefPtr<Gfx::Bitmap>);
+
+    static RefPtr<Gfx::Bitmap> try_decode_bitmap(const ByteBuffer& bitmap_data);
 
     // This generates a new Bitmap with the final image (all layers composed according to their attributes.)
     RefPtr<Gfx::Bitmap> try_compose_bitmap(Gfx::BitmapFormat format) const;
@@ -103,10 +103,6 @@ public:
 private:
     explicit Image(Gfx::IntSize const&);
 
-    static Result<NonnullRefPtr<Image>, String> try_create_from_pixel_paint_fd(int fd, String const& file_path);
-    static Result<NonnullRefPtr<Image>, String> try_create_from_pixel_paint_path(String const& file_path);
-    static Result<NonnullRefPtr<Image>, String> try_create_from_pixel_paint_file(Core::File& file, String const& file_path);
-
     void did_change(Gfx::IntRect const& modified_rect = {});
     void did_change_rect();
     void did_modify_layer_stack();

+ 75 - 0
Userland/Applications/PixelPaint/ProjectLoader.cpp

@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "ProjectLoader.h"
+#include "Image.h"
+#include "Layer.h"
+#include <AK/JsonObject.h>
+#include <AK/MappedFile.h>
+#include <AK/Result.h>
+#include <AK/String.h>
+#include <LibCore/File.h>
+#include <LibImageDecoderClient/Client.h>
+
+namespace PixelPaint {
+
+Result<void, String> ProjectLoader::try_load_from_fd_and_close(int fd, StringView path)
+{
+    auto file = Core::File::construct();
+    file->open(fd, Core::OpenMode::ReadOnly, Core::File::ShouldCloseFileDescriptor::No);
+    if (file->has_error())
+        return String { file->error_string() };
+
+    auto contents = file->read_all();
+
+    auto json_or_error = JsonValue::from_string(contents);
+    if (!json_or_error.has_value()) {
+        m_is_raw_image = true;
+
+        auto file_or_error = MappedFile::map_from_fd_and_close(fd, path);
+        if (file_or_error.is_error())
+            return String::formatted("Unable to mmap file {}", file_or_error.error().string());
+
+        auto& mapped_file = *file_or_error.value();
+        // FIXME: Find a way to avoid the memory copy here.
+        auto bitmap = Image::try_decode_bitmap(ByteBuffer::copy(mapped_file.bytes()));
+        if (!bitmap)
+            return String { "Unable to decode image"sv };
+        auto image = Image::try_create_from_bitmap(bitmap.release_nonnull());
+        if (!image)
+            return String { "Unable to allocate Image"sv };
+
+        image->set_path(path);
+        m_image = image;
+        return {};
+    }
+
+    close(fd);
+    auto& json = json_or_error.value().as_object();
+    auto image_or_error = Image::try_create_from_pixel_paint_json(json);
+
+    if (image_or_error.is_error())
+        return image_or_error.release_error();
+
+    auto image = image_or_error.release_value();
+    image->set_path(path);
+
+    if (json.has("guides"))
+        m_json_metadata = json.get("guides").as_array();
+
+    m_image = image;
+    return {};
+}
+Result<void, String> ProjectLoader::try_load_from_path(StringView path)
+{
+    auto file_or_error = Core::File::open(path, Core::OpenMode::ReadOnly);
+    if (file_or_error.is_error())
+        return String::formatted("Unable to open file because: {}", file_or_error.release_error());
+
+    return try_load_from_fd_and_close(file_or_error.release_value()->fd(), path);
+}
+
+}

+ 35 - 0
Userland/Applications/PixelPaint/ProjectLoader.h

@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include "Image.h"
+#include <AK/JsonArray.h>
+#include <AK/Result.h>
+#include <AK/StringView.h>
+
+namespace PixelPaint {
+
+class ProjectLoader {
+public:
+    ProjectLoader() = default;
+    ~ProjectLoader() = default;
+
+    Result<void, String> try_load_from_fd_and_close(int fd, StringView path);
+    Result<void, String> try_load_from_path(StringView path);
+
+    bool is_raw_image() const { return m_is_raw_image; }
+    bool has_image() const { return !m_image.is_null(); }
+    RefPtr<Image> release_image() const { return move(m_image); }
+    JsonArray const& json_metadata() const { return m_json_metadata; }
+
+private:
+    RefPtr<Image> m_image { nullptr };
+    bool m_is_raw_image { false };
+    JsonArray m_json_metadata {};
+};
+
+}

+ 11 - 7
Userland/Applications/PixelPaint/main.cpp

@@ -16,6 +16,7 @@
 #include "LayerListWidget.h"
 #include "LayerPropertiesWidget.h"
 #include "PaletteWidget.h"
+#include "ProjectLoader.h"
 #include "Tool.h"
 #include "ToolPropertiesWidget.h"
 #include "ToolboxWidget.h"
@@ -114,6 +115,7 @@ int main(int argc, char** argv)
     };
 
     Function<PixelPaint::ImageEditor&(NonnullRefPtr<PixelPaint::Image>)> create_new_editor;
+    PixelPaint::ProjectLoader loader;
 
     auto& layer_list_widget = *main_widget.find_descendant_of_type_named<PixelPaint::LayerListWidget>("layer_list_widget");
     layer_list_widget.on_layer_select = [&](auto* layer) {
@@ -151,23 +153,25 @@ int main(int argc, char** argv)
         window);
 
     auto open_image_file = [&](auto& path) {
-        auto image_or_error = PixelPaint::Image::try_create_from_path(path);
-        if (image_or_error.is_error()) {
+        auto try_load = loader.try_load_from_path(path);
+        if (try_load.is_error()) {
             GUI::MessageBox::show_error(window, String::formatted("Unable to open file: {}", path));
             return;
         }
-        auto& image = *image_or_error.value();
+        auto& image = *loader.release_image();
         create_new_editor(image);
         layer_list_widget.set_image(&image);
     };
 
     auto open_image_fd = [&](int fd, auto& path) {
-        auto image_or_error = PixelPaint::Image::try_create_from_fd_and_close(fd, path);
-        if (image_or_error.is_error()) {
-            GUI::MessageBox::show_error(window, String::formatted("Unable to open file: {}, {}", path, image_or_error.error()));
+        auto try_load = loader.try_load_from_fd_and_close(fd, path);
+
+        if (try_load.is_error()) {
+            GUI::MessageBox::show_error(window, String::formatted("Unable to open file: {}, {}", path, try_load.error()));
             return;
         }
-        auto& image = *image_or_error.value();
+
+        auto& image = *loader.release_image();
         create_new_editor(image);
         layer_list_widget.set_image(&image);
     };