summaryrefslogtreecommitdiff
path: root/Userland/Applications/PixelPaint
diff options
context:
space:
mode:
authorkleines Filmröllchen <filmroellchen@serenityos.org>2022-08-13 13:05:03 +0200
committerSam Atkins <atkinssj@gmail.com>2022-09-27 14:23:11 +0100
commitade868aa56bf789d6a97b5a8ee2bc3b339de65da (patch)
treed7a1dfad99959f127b4f2492b37bbfa1f248635c /Userland/Applications/PixelPaint
parent9483adee465732cd8acc2bb1991dffdae813673c (diff)
downloadserenity-ade868aa56bf789d6a97b5a8ee2bc3b339de65da.zip
PixelPaint: Add a general-purpose parallel image processing pipeline
The ImageProcessor singleton is intended to be used by all sorts of image processing which might take some time to complete; or other background actions. We're not using BackgroundTask here because this system is specifically designed to work with task queues and PixelPaint interaction; e.g. it provides common image processing tasks such as filter application.
Diffstat (limited to 'Userland/Applications/PixelPaint')
-rw-r--r--Userland/Applications/PixelPaint/CMakeLists.txt1
-rw-r--r--Userland/Applications/PixelPaint/ImageProcessor.cpp69
-rw-r--r--Userland/Applications/PixelPaint/ImageProcessor.h76
3 files changed, 146 insertions, 0 deletions
diff --git a/Userland/Applications/PixelPaint/CMakeLists.txt b/Userland/Applications/PixelPaint/CMakeLists.txt
index 7f75f18ed3..bb44e2cb66 100644
--- a/Userland/Applications/PixelPaint/CMakeLists.txt
+++ b/Userland/Applications/PixelPaint/CMakeLists.txt
@@ -40,6 +40,7 @@ set(SOURCES
IconBag.cpp
Image.cpp
ImageEditor.cpp
+ ImageProcessor.cpp
Layer.cpp
LayerListWidget.cpp
LayerPropertiesWidget.cpp
diff --git a/Userland/Applications/PixelPaint/ImageProcessor.cpp b/Userland/Applications/PixelPaint/ImageProcessor.cpp
new file mode 100644
index 0000000000..c6573cb46d
--- /dev/null
+++ b/Userland/Applications/PixelPaint/ImageProcessor.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2022, kleines Filmröllchen <filmroellchen@serenityos.org>.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "ImageProcessor.h"
+
+namespace PixelPaint {
+
+FilterApplicationCommand::FilterApplicationCommand(NonnullRefPtr<Filter> filter, NonnullRefPtr<Layer> target_layer)
+ : m_filter(move(filter))
+ , m_target_layer(move(target_layer))
+{
+}
+
+void FilterApplicationCommand::execute()
+{
+ m_filter->apply(m_target_layer->content_bitmap(), m_target_layer->content_bitmap());
+ m_filter->m_editor->gui_event_loop().deferred_invoke([strong_this = NonnullRefPtr(*this)]() {
+ // HACK: we can't tell strong_this to not be const
+ (*const_cast<NonnullRefPtr<Layer>*>(&strong_this->m_target_layer))->did_modify_bitmap(strong_this->m_target_layer->rect());
+ strong_this->m_filter->m_editor->did_complete_action();
+ });
+}
+
+static Singleton<ImageProcessor> s_image_processor;
+
+ImageProcessor::ImageProcessor()
+ : m_command_queue(MUST(Queue::try_create()))
+ , m_processor_thread(Threading::Thread::construct([this]() {
+ while (true) {
+ if (auto next_command = m_command_queue.try_dequeue(); !next_command.is_error()) {
+ next_command.value()->execute();
+ } else {
+ Threading::MutexLocker locker { m_wakeup_mutex };
+ m_wakeup_variable.wait_while([this]() { return m_command_queue.weak_used() == 0; });
+ }
+ }
+ return 0;
+ },
+ "Image Processor"sv))
+ , m_wakeup_variable(m_wakeup_mutex)
+{
+}
+
+ImageProcessor* ImageProcessor::the()
+{
+ return s_image_processor;
+}
+
+ErrorOr<void> ImageProcessor::enqueue_command(NonnullRefPtr<ImageProcessingCommand> command)
+{
+ if (auto queue_status = m_command_queue.try_enqueue(move(command)); queue_status.is_error())
+ return ENOSPC;
+
+ if (!m_processor_thread->is_started()) {
+ m_processor_thread->start();
+ m_processor_thread->detach();
+ }
+
+ m_wakeup_mutex.lock();
+ m_wakeup_variable.signal();
+ m_wakeup_mutex.unlock();
+
+ return {};
+}
+
+}
diff --git a/Userland/Applications/PixelPaint/ImageProcessor.h b/Userland/Applications/PixelPaint/ImageProcessor.h
new file mode 100644
index 0000000000..4566aae279
--- /dev/null
+++ b/Userland/Applications/PixelPaint/ImageProcessor.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2022, kleines Filmröllchen <filmroellchen@serenityos.org>.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include "Filters/Filter.h"
+#include <AK/Function.h>
+#include <AK/NonnullRefPtr.h>
+#include <AK/RefCounted.h>
+#include <AK/Singleton.h>
+#include <LibCore/SharedCircularQueue.h>
+#include <LibThreading/ConditionVariable.h>
+#include <LibThreading/Thread.h>
+
+namespace PixelPaint {
+
+class ImageProcessingCommand : public RefCounted<ImageProcessingCommand> {
+public:
+ virtual void execute() = 0;
+ virtual ~ImageProcessingCommand() = default;
+};
+
+// A command applying a filter from a source to a target bitmap.
+class FilterApplicationCommand : public ImageProcessingCommand {
+public:
+ FilterApplicationCommand(NonnullRefPtr<Filter>, NonnullRefPtr<Layer>);
+
+ virtual void execute() override;
+ virtual ~FilterApplicationCommand() = default;
+
+private:
+ NonnullRefPtr<Filter> m_filter;
+ NonnullRefPtr<Layer> m_target_layer;
+};
+
+// A command based on a custom user function.
+class FunctionCommand : public ImageProcessingCommand {
+public:
+ FunctionCommand(Function<void()> function)
+ : m_function(move(function))
+ {
+ }
+
+ virtual void execute() override { m_function(); }
+
+private:
+ Function<void()> m_function;
+};
+
+// A utility class that allows various PixelPaint systems to execute image processing commands asynchronously on another thread.
+class ImageProcessor final {
+ friend struct AK::SingletonInstanceCreator<ImageProcessor>;
+
+public:
+ static ImageProcessor* the();
+
+ ErrorOr<void> enqueue_command(NonnullRefPtr<ImageProcessingCommand> command);
+
+private:
+ ImageProcessor();
+ void processor_main();
+
+ // Only the memory in the queue is in shared memory, i.e. the smart pointers themselves.
+ // The actual data will remain in normal memory, but for this application we're not using multiple processes so it's fine.
+ using Queue = Core::SharedSingleProducerCircularQueue<RefPtr<ImageProcessingCommand>>;
+ Queue m_command_queue;
+
+ NonnullRefPtr<Threading::Thread> m_processor_thread;
+ Threading::Mutex m_wakeup_mutex {};
+ Threading::ConditionVariable m_wakeup_variable;
+};
+
+}