diff --git a/Userland/Libraries/LibImageDecoderClient/Client.cpp b/Userland/Libraries/LibImageDecoderClient/Client.cpp index ab27b085bd8..ff7508040af 100644 --- a/Userland/Libraries/LibImageDecoderClient/Client.cpp +++ b/Userland/Libraries/LibImageDecoderClient/Client.cpp @@ -16,48 +16,101 @@ Client::Client(NonnullOwnPtr socket) void Client::die() { + for (auto& [_, promise] : m_pending_decoded_images) { + promise->reject(Error::from_string_literal("ImageDecoder disconnected")); + } + m_pending_decoded_images.clear(); + if (on_death) on_death(); } -Optional Client::decode_image(ReadonlyBytes encoded_data, Optional ideal_size, Optional mime_type) +NonnullRefPtr> Client::decode_image(ReadonlyBytes encoded_data, Function(DecodedImage&)> on_resolved, Function on_rejected, Optional ideal_size, Optional mime_type) { - if (encoded_data.is_empty()) - return {}; + auto promise = Core::Promise::construct(); + if (on_resolved) + promise->on_resolution = move(on_resolved); + if (on_rejected) + promise->on_rejection = move(on_rejected); + + if (encoded_data.is_empty()) { + promise->reject(Error::from_string_literal("No encoded data")); + return promise; + } auto encoded_buffer_or_error = Core::AnonymousBuffer::create_with_size(encoded_data.size()); if (encoded_buffer_or_error.is_error()) { - dbgln("Could not allocate encoded buffer"); - return {}; + dbgln("Could not allocate encoded buffer: {}", encoded_buffer_or_error.error()); + promise->reject(encoded_buffer_or_error.release_error()); + return promise; } auto encoded_buffer = encoded_buffer_or_error.release_value(); memcpy(encoded_buffer.data(), encoded_data.data(), encoded_data.size()); - auto response_or_error = try_decode_image(move(encoded_buffer), ideal_size, mime_type); - if (response_or_error.is_error()) { - dbgln("ImageDecoder died heroically"); - return {}; + auto response = send_sync_but_allow_failure(move(encoded_buffer), ideal_size, mime_type); + if (!response) { + dbgln("ImageDecoder disconnected trying to decode image"); + promise->reject(Error::from_string_literal("ImageDecoder disconnected")); + return promise; } - auto& response = response_or_error.value(); + m_pending_decoded_images.set(response->image_id(), promise); - if (response.bitmaps().is_empty()) + return promise; +} + +Optional Client::decode_image(ReadonlyBytes encoded_data, Optional ideal_size, Optional mime_type) +{ + auto promise = decode_image( + encoded_data, [](auto) -> ErrorOr { return {}; }, [](Error&) -> void {}, ideal_size, mime_type); + auto result = promise->await(); + if (result.is_error()) return {}; + return result.release_value(); +} + +void Client::did_decode_image(i64 image_id, bool is_animated, u32 loop_count, Vector const& bitmaps, Vector const& durations, Gfx::FloatPoint scale) +{ + VERIFY(!bitmaps.is_empty()); + + auto maybe_promise = m_pending_decoded_images.take(image_id); + if (!maybe_promise.has_value()) { + dbgln("ImageDecoderClient: No pending image with ID {}", image_id); + return; + } + auto promise = maybe_promise.release_value(); DecodedImage image; - image.is_animated = response.is_animated(); - image.loop_count = response.loop_count(); - image.scale = response.scale(); - image.frames.ensure_capacity(response.bitmaps().size()); - auto bitmaps = response.take_bitmaps(); + image.is_animated = is_animated; + image.loop_count = loop_count; + image.scale = scale; + image.frames.ensure_capacity(bitmaps.size()); for (size_t i = 0; i < bitmaps.size(); ++i) { - if (!bitmaps[i].is_valid()) - return {}; + if (!bitmaps[i].is_valid()) { + dbgln("ImageDecoderClient: Invalid bitmap for request {} at index {}", image_id, i); + promise->reject(Error::from_string_literal("Invalid bitmap")); + return; + } - image.frames.empend(*bitmaps[i].bitmap(), response.durations()[i]); + image.frames.empend(*bitmaps[i].bitmap(), durations[i]); } - return image; + + promise->resolve(move(image)); +} + +void Client::did_fail_to_decode_image(i64 image_id, String const& error_message) +{ + auto maybe_promise = m_pending_decoded_images.take(image_id); + if (!maybe_promise.has_value()) { + dbgln("ImageDecoderClient: No pending image with ID {}", image_id); + return; + } + auto promise = maybe_promise.release_value(); + + dbgln("ImageDecoderClient: Failed to decode image with ID {}: {}", image_id, error_message); + // FIXME: Include the error message in the Error object when Errors are allowed to hold Strings + promise->reject(Error::from_string_literal("Image decoding failed or aborted")); } } diff --git a/Userland/Libraries/LibImageDecoderClient/Client.h b/Userland/Libraries/LibImageDecoderClient/Client.h index a2a3ebaf54d..b8a13fb3425 100644 --- a/Userland/Libraries/LibImageDecoderClient/Client.h +++ b/Userland/Libraries/LibImageDecoderClient/Client.h @@ -9,6 +9,7 @@ #include #include #include +#include #include namespace ImageDecoderClient { @@ -33,12 +34,20 @@ class Client final public: Client(NonnullOwnPtr); + NonnullRefPtr> decode_image(ReadonlyBytes, Function(DecodedImage&)> on_resolved, Function on_rejected, Optional ideal_size = {}, Optional mime_type = {}); + + // FIXME: Move all clients to the promise-based API and get rid of this synchronous (i.e. EventLoop-spinning) one. Optional decode_image(ReadonlyBytes, Optional ideal_size = {}, Optional mime_type = {}); Function on_death; private: virtual void die() override; + + virtual void did_decode_image(i64 image_id, bool is_animated, u32 loop_count, Vector const& bitmaps, Vector const& durations, Gfx::FloatPoint scale) override; + virtual void did_fail_to_decode_image(i64 image_id, String const& error_message) override; + + HashMap>> m_pending_decoded_images; }; } diff --git a/Userland/Services/ImageDecoder/ConnectionFromClient.cpp b/Userland/Services/ImageDecoder/ConnectionFromClient.cpp index e92c0220851..ff5239abc7b 100644 --- a/Userland/Services/ImageDecoder/ConnectionFromClient.cpp +++ b/Userland/Services/ImageDecoder/ConnectionFromClient.cpp @@ -72,20 +72,50 @@ static void decode_image_to_details(Core::AnonymousBuffer const& encoded_buffer, decode_image_to_bitmaps_and_durations_with_decoder(*decoder, ideal_size, bitmaps, durations); } +ConnectionFromClient::Job ConnectionFromClient::make_decode_image_job(i64 image_id, Core::AnonymousBuffer encoded_buffer, Optional ideal_size, Optional mime_type) +{ + return [strong_this = NonnullRefPtr(*this), image_id, encoded_buffer = move(encoded_buffer), ideal_size = move(ideal_size), mime_type = move(mime_type)]() { + bool is_animated = false; + u32 loop_count = 0; + Gfx::FloatPoint scale { 1, 1 }; + Vector bitmaps; + Vector durations; + + decode_image_to_details(encoded_buffer, ideal_size, mime_type, is_animated, loop_count, bitmaps, durations, scale); + if (bitmaps.is_empty()) { + strong_this->async_did_fail_to_decode_image(image_id, "Could not decode image"_string); + } else { + strong_this->async_did_decode_image(image_id, is_animated, loop_count, bitmaps, durations, scale); + } + }; +} + Messages::ImageDecoderServer::DecodeImageResponse ConnectionFromClient::decode_image(Core::AnonymousBuffer const& encoded_buffer, Optional const& ideal_size, Optional const& mime_type) { + auto image_id = m_next_image_id++; + if (!encoded_buffer.is_valid()) { dbgln_if(IMAGE_DECODER_DEBUG, "Encoded data is invalid"); - return nullptr; + async_did_fail_to_decode_image(image_id, "Encoded data is invalid"_string); + return image_id; } - bool is_animated = false; - u32 loop_count = 0; - Gfx::FloatPoint scale { 1, 1 }; - Vector bitmaps; - Vector durations; - decode_image_to_details(encoded_buffer, ideal_size, mime_type, is_animated, loop_count, bitmaps, durations, scale); - return { is_animated, loop_count, bitmaps, durations, scale }; + m_pending_jobs.set(image_id, make_decode_image_job(image_id, encoded_buffer, ideal_size, mime_type)); + + Core::deferred_invoke([strong_this = NonnullRefPtr(*this), image_id]() { + if (auto job = strong_this->m_pending_jobs.take(image_id); job.has_value()) { + job.value()(); + } + }); + + return image_id; +} + +void ConnectionFromClient::cancel_decoding(i64 image_id) +{ + if (auto job = m_pending_jobs.take(image_id); job.has_value()) { + async_did_fail_to_decode_image(image_id, "Decoding was canceled"_string); + } } } diff --git a/Userland/Services/ImageDecoder/ConnectionFromClient.h b/Userland/Services/ImageDecoder/ConnectionFromClient.h index 7d3fe8a1e2d..a9a6d2fc77b 100644 --- a/Userland/Services/ImageDecoder/ConnectionFromClient.h +++ b/Userland/Services/ImageDecoder/ConnectionFromClient.h @@ -24,9 +24,17 @@ public: virtual void die() override; private: + using Job = Function; + explicit ConnectionFromClient(NonnullOwnPtr); virtual Messages::ImageDecoderServer::DecodeImageResponse decode_image(Core::AnonymousBuffer const&, Optional const& ideal_size, Optional const& mime_type) override; + virtual void cancel_decoding(i64 image_id) override; + + Job make_decode_image_job(i64 image_id, Core::AnonymousBuffer, Optional ideal_size, Optional mime_type); + + i64 m_next_image_id { 0 }; + HashMap m_pending_jobs; }; } diff --git a/Userland/Services/ImageDecoder/ImageDecoderClient.ipc b/Userland/Services/ImageDecoder/ImageDecoderClient.ipc index 10c257a8fa0..f47c27cb54c 100644 --- a/Userland/Services/ImageDecoder/ImageDecoderClient.ipc +++ b/Userland/Services/ImageDecoder/ImageDecoderClient.ipc @@ -1,6 +1,7 @@ -#include #include endpoint ImageDecoderClient { + did_decode_image(i64 image_id, bool is_animated, u32 loop_count, Vector bitmaps, Vector durations, Gfx::FloatPoint scale) =| + did_fail_to_decode_image(i64 image_id, String error_message) =| } diff --git a/Userland/Services/ImageDecoder/ImageDecoderServer.ipc b/Userland/Services/ImageDecoder/ImageDecoderServer.ipc index 2776417df02..93db39a5776 100644 --- a/Userland/Services/ImageDecoder/ImageDecoderServer.ipc +++ b/Userland/Services/ImageDecoder/ImageDecoderServer.ipc @@ -1,7 +1,7 @@ #include -#include endpoint ImageDecoderServer { - decode_image(Core::AnonymousBuffer data, Optional ideal_size, Optional mime_type) => (bool is_animated, u32 loop_count, Vector bitmaps, Vector durations, Gfx::FloatPoint scale) + decode_image(Core::AnonymousBuffer data, Optional ideal_size, Optional mime_type) => (i64 image_id) + cancel_decoding(i64 image_id) =| }