2023-07-04 00:13:34 -04:00
|
|
|
/*
|
2024-07-21 17:23:28 -04:00
|
|
|
* Copyright (c) 2023-2024, Lucas Chollet <lucas.chollet@serenityos.org>
|
2023-07-04 00:13:34 -04:00
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
*/
|
|
|
|
|
2024-07-21 17:23:28 -04:00
|
|
|
#include <AK/Debug.h>
|
|
|
|
#include <AK/Error.h>
|
2023-07-04 00:13:34 -04:00
|
|
|
#include <LibGfx/ImageFormats/JPEGXLLoader.h>
|
2024-07-21 17:23:28 -04:00
|
|
|
#include <jxl/decode.h>
|
2023-07-04 00:13:34 -04:00
|
|
|
|
|
|
|
namespace Gfx {
|
|
|
|
|
2024-07-21 17:23:28 -04:00
|
|
|
class JPEGXLLoadingContext {
|
2024-07-22 17:55:11 -04:00
|
|
|
AK_MAKE_NONCOPYABLE(JPEGXLLoadingContext);
|
|
|
|
AK_MAKE_NONMOVABLE(JPEGXLLoadingContext);
|
|
|
|
|
2023-07-30 23:08:54 -04:00
|
|
|
public:
|
2024-07-21 17:23:28 -04:00
|
|
|
JPEGXLLoadingContext(JxlDecoder* decoder)
|
|
|
|
: m_decoder(decoder)
|
2023-07-30 23:08:54 -04:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2024-07-21 17:23:28 -04:00
|
|
|
~JPEGXLLoadingContext()
|
2023-07-30 23:08:54 -04:00
|
|
|
{
|
2024-07-21 17:23:28 -04:00
|
|
|
JxlDecoderDestroy(m_decoder);
|
2023-07-30 23:08:54 -04:00
|
|
|
}
|
|
|
|
|
2024-07-21 17:23:28 -04:00
|
|
|
ErrorOr<void> decode_image_header()
|
2023-07-30 23:08:54 -04:00
|
|
|
{
|
2024-07-21 17:23:28 -04:00
|
|
|
return run_state_machine_until(State::HeaderDecoded);
|
2023-07-30 23:08:54 -04:00
|
|
|
}
|
|
|
|
|
2024-07-21 17:23:28 -04:00
|
|
|
ErrorOr<void> decode_image()
|
2023-07-30 23:08:54 -04:00
|
|
|
{
|
2024-07-21 17:23:28 -04:00
|
|
|
return run_state_machine_until(State::FrameDecoded);
|
2023-07-30 23:08:54 -04:00
|
|
|
}
|
|
|
|
|
2024-07-21 17:23:28 -04:00
|
|
|
enum class State : u8 {
|
|
|
|
NotDecoded = 0,
|
|
|
|
Error,
|
|
|
|
HeaderDecoded,
|
|
|
|
FrameDecoded,
|
2023-07-30 23:08:54 -04:00
|
|
|
};
|
|
|
|
|
2024-07-21 17:23:28 -04:00
|
|
|
State state() const
|
2023-07-30 23:08:54 -04:00
|
|
|
{
|
2024-07-21 17:23:28 -04:00
|
|
|
return m_state;
|
2023-07-31 16:56:12 -04:00
|
|
|
}
|
2023-07-04 00:13:34 -04:00
|
|
|
|
2024-07-21 17:23:28 -04:00
|
|
|
IntSize size() const
|
|
|
|
{
|
|
|
|
return m_size;
|
2023-07-04 00:13:34 -04:00
|
|
|
}
|
|
|
|
|
2024-07-21 17:23:28 -04:00
|
|
|
RefPtr<Bitmap> bitmap() const
|
|
|
|
{
|
|
|
|
return m_bitmap;
|
2023-07-04 00:13:34 -04:00
|
|
|
}
|
|
|
|
|
2024-07-21 17:23:28 -04:00
|
|
|
private:
|
|
|
|
ErrorOr<void> run_state_machine_until(State requested_state)
|
|
|
|
{
|
|
|
|
for (;;) {
|
|
|
|
auto const status = JxlDecoderProcessInput(m_decoder);
|
2023-07-30 23:08:54 -04:00
|
|
|
|
2024-07-21 17:23:28 -04:00
|
|
|
if (status == JXL_DEC_ERROR)
|
|
|
|
return Error::from_string_literal("JPEGXLImageDecoderPlugin: Decoder is corrupted.");
|
|
|
|
if (status == JXL_DEC_NEED_MORE_INPUT)
|
|
|
|
return Error::from_string_literal("JPEGXLImageDecoderPlugin: Decoder need more input.");
|
2023-07-04 00:13:34 -04:00
|
|
|
|
2024-07-21 17:23:28 -04:00
|
|
|
if (status == JXL_DEC_BASIC_INFO) {
|
|
|
|
TRY(decode_image_header_impl());
|
|
|
|
if (requested_state <= State::HeaderDecoded)
|
|
|
|
return {};
|
2023-07-04 00:13:34 -04:00
|
|
|
}
|
2023-07-30 23:08:54 -04:00
|
|
|
|
2024-07-21 17:23:28 -04:00
|
|
|
if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
|
|
|
|
TRY(set_output_buffer());
|
|
|
|
continue;
|
2023-07-04 00:13:34 -04:00
|
|
|
}
|
|
|
|
|
2024-07-21 17:23:28 -04:00
|
|
|
if (status == JXL_DEC_FULL_IMAGE) {
|
|
|
|
// Called once per frame, let's return for now
|
|
|
|
return {};
|
2023-07-04 00:13:34 -04:00
|
|
|
}
|
|
|
|
|
2024-07-21 17:23:28 -04:00
|
|
|
if (status == JXL_DEC_SUCCESS)
|
|
|
|
return {};
|
2023-08-10 23:51:11 -04:00
|
|
|
|
2024-07-21 17:23:28 -04:00
|
|
|
return Error::from_string_literal("JPEGXLImageDecoderPlugin: Unknown event.");
|
2023-08-10 23:51:11 -04:00
|
|
|
}
|
|
|
|
}
|
2023-07-04 00:13:34 -04:00
|
|
|
|
2024-07-21 17:23:28 -04:00
|
|
|
ErrorOr<void> decode_image_header_impl()
|
2023-07-04 00:13:34 -04:00
|
|
|
{
|
2024-07-21 17:23:28 -04:00
|
|
|
JxlBasicInfo info;
|
2023-07-04 00:13:34 -04:00
|
|
|
|
2024-07-21 17:23:28 -04:00
|
|
|
if (auto res = JxlDecoderGetBasicInfo(m_decoder, &info); res != JXL_DEC_SUCCESS)
|
|
|
|
return Error::from_string_literal("JPEGXLImageDecoderPlugin: Unable to decode basic information.");
|
2023-07-04 00:13:34 -04:00
|
|
|
|
2024-07-21 17:23:28 -04:00
|
|
|
m_size = { info.xsize, info.ysize };
|
2023-07-04 00:13:34 -04:00
|
|
|
|
|
|
|
m_state = State::HeaderDecoded;
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2024-07-21 17:23:28 -04:00
|
|
|
ErrorOr<void> set_output_buffer()
|
2023-07-04 00:13:34 -04:00
|
|
|
{
|
|
|
|
auto result = [this]() -> ErrorOr<void> {
|
2024-07-21 17:23:28 -04:00
|
|
|
if (JxlDecoderProcessInput(m_decoder) != JXL_DEC_NEED_IMAGE_OUT_BUFFER)
|
|
|
|
return Error::from_string_literal("JPEGXLImageDecoderPlugin: Decoder is in an unexpected state.");
|
2023-07-04 00:13:34 -04:00
|
|
|
|
2024-07-21 17:23:28 -04:00
|
|
|
m_bitmap = TRY(Bitmap::create(Gfx::BitmapFormat::RGBA8888, m_size));
|
2023-07-04 00:13:34 -04:00
|
|
|
|
2024-07-21 17:23:28 -04:00
|
|
|
JxlPixelFormat format = { 4, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0 };
|
2023-07-04 00:13:34 -04:00
|
|
|
|
2024-07-21 17:23:28 -04:00
|
|
|
size_t needed_size = 0;
|
|
|
|
JxlDecoderImageOutBufferSize(m_decoder, &format, &needed_size);
|
2023-07-04 00:13:34 -04:00
|
|
|
|
2024-07-21 17:23:28 -04:00
|
|
|
if (needed_size != m_bitmap->size_in_bytes())
|
|
|
|
return Error::from_string_literal("JPEGXLImageDecoderPlugin: Expected bitmap size is wrong.");
|
2023-07-04 00:13:34 -04:00
|
|
|
|
2024-07-21 17:23:28 -04:00
|
|
|
if (auto res = JxlDecoderSetImageOutBuffer(m_decoder, &format, m_bitmap->begin(), m_bitmap->size_in_bytes());
|
|
|
|
res != JXL_DEC_SUCCESS)
|
|
|
|
return Error::from_string_literal("JPEGXLImageDecoderPlugin: Unable to decode frame.");
|
2023-08-11 13:16:43 -04:00
|
|
|
|
2023-07-04 00:13:34 -04:00
|
|
|
return {};
|
|
|
|
}();
|
|
|
|
|
|
|
|
m_state = result.is_error() ? State::Error : State::FrameDecoded;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
State m_state { State::NotDecoded };
|
|
|
|
|
2024-07-21 17:23:28 -04:00
|
|
|
JxlDecoder* m_decoder;
|
2023-08-11 13:16:43 -04:00
|
|
|
|
2024-07-21 17:23:28 -04:00
|
|
|
IntSize m_size;
|
|
|
|
RefPtr<Bitmap> m_bitmap;
|
2023-07-04 00:13:34 -04:00
|
|
|
};
|
|
|
|
|
2024-07-21 17:23:28 -04:00
|
|
|
JPEGXLImageDecoderPlugin::JPEGXLImageDecoderPlugin(OwnPtr<JPEGXLLoadingContext> context)
|
|
|
|
: m_context(move(context))
|
2023-07-04 00:13:34 -04:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
JPEGXLImageDecoderPlugin::~JPEGXLImageDecoderPlugin() = default;
|
|
|
|
|
|
|
|
IntSize JPEGXLImageDecoderPlugin::size()
|
|
|
|
{
|
|
|
|
return m_context->size();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool JPEGXLImageDecoderPlugin::sniff(ReadonlyBytes data)
|
|
|
|
{
|
2024-07-21 17:23:28 -04:00
|
|
|
auto signature = JxlSignatureCheck(data.data(), data.size());
|
|
|
|
return signature == JXL_SIG_CODESTREAM || signature == JXL_SIG_CONTAINER;
|
2023-07-04 00:13:34 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> JPEGXLImageDecoderPlugin::create(ReadonlyBytes data)
|
|
|
|
{
|
2024-07-21 17:23:28 -04:00
|
|
|
auto* decoder = JxlDecoderCreate(nullptr);
|
|
|
|
if (!decoder)
|
|
|
|
return Error::from_errno(ENOMEM);
|
|
|
|
|
|
|
|
if (auto res = JxlDecoderSubscribeEvents(decoder, JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE); res == JXL_DEC_ERROR)
|
|
|
|
return Error::from_string_literal("JPEGXLImageDecoderPlugin: Unable to subscribe to events.");
|
|
|
|
|
|
|
|
if (auto res = JxlDecoderSetInput(decoder, data.data(), data.size()); res == JXL_DEC_ERROR)
|
|
|
|
return Error::from_string_literal("JPEGXLImageDecoderPlugin: Unable to set decoder input.");
|
|
|
|
|
|
|
|
// Tell the decoder that it won't receive more data for the image.
|
|
|
|
JxlDecoderCloseInput(decoder);
|
|
|
|
|
|
|
|
auto context = TRY(adopt_nonnull_own_or_enomem(new (nothrow) JPEGXLLoadingContext(decoder)));
|
|
|
|
auto plugin = TRY(adopt_nonnull_own_or_enomem(new (nothrow) JPEGXLImageDecoderPlugin(move(context))));
|
|
|
|
|
2023-07-04 00:13:34 -04:00
|
|
|
TRY(plugin->m_context->decode_image_header());
|
|
|
|
return plugin;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool JPEGXLImageDecoderPlugin::is_animated()
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t JPEGXLImageDecoderPlugin::loop_count()
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t JPEGXLImageDecoderPlugin::frame_count()
|
|
|
|
{
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t JPEGXLImageDecoderPlugin::first_animated_frame_index()
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
ErrorOr<ImageFrameDescriptor> JPEGXLImageDecoderPlugin::frame(size_t index, Optional<IntSize>)
|
|
|
|
{
|
|
|
|
if (index > 0)
|
2024-07-21 17:23:28 -04:00
|
|
|
return Error::from_string_literal("JPEGXLImageDecoderPlugin: Invalid frame index.");
|
2023-07-04 00:13:34 -04:00
|
|
|
|
|
|
|
if (m_context->state() == JPEGXLLoadingContext::State::Error)
|
2024-07-21 17:23:28 -04:00
|
|
|
return Error::from_string_literal("JPEGXLImageDecoderPlugin: Decoding failed.");
|
2023-07-04 00:13:34 -04:00
|
|
|
|
2023-07-22 16:13:22 -04:00
|
|
|
if (m_context->state() < JPEGXLLoadingContext::State::FrameDecoded)
|
2024-07-21 17:23:28 -04:00
|
|
|
TRY(m_context->decode_image());
|
2023-07-04 00:13:34 -04:00
|
|
|
|
|
|
|
return ImageFrameDescriptor { m_context->bitmap(), 0 };
|
|
|
|
}
|
|
|
|
|
|
|
|
ErrorOr<Optional<ReadonlyBytes>> JPEGXLImageDecoderPlugin::icc_data()
|
|
|
|
{
|
|
|
|
return OptionalNone {};
|
|
|
|
}
|
|
|
|
}
|