2020-05-18 07:58:27 +00:00
|
|
|
/*
|
2024-06-16 14:08:31 +00:00
|
|
|
* Copyright (c) 2024, Andreas Kling <andreas@ladybird.org>
|
2020-05-18 07:58:27 +00:00
|
|
|
*
|
2021-04-22 08:24:48 +00:00
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
2020-05-18 07:58:27 +00:00
|
|
|
*/
|
|
|
|
|
2024-06-16 14:08:31 +00:00
|
|
|
#include <LibGfx/CMYKBitmap.h>
|
2023-03-21 18:58:06 +00:00
|
|
|
#include <LibGfx/ImageFormats/JPEGLoader.h>
|
2024-06-16 14:08:31 +00:00
|
|
|
#include <jpeglib.h>
|
|
|
|
#include <setjmp.h>
|
2020-06-22 17:10:20 +00:00
|
|
|
|
2020-05-18 07:58:27 +00:00
|
|
|
namespace Gfx {
|
|
|
|
|
2024-06-16 14:08:31 +00:00
|
|
|
struct JPEGLoadingContext {
|
|
|
|
enum class State {
|
|
|
|
NotDecoded,
|
|
|
|
Error,
|
|
|
|
Decoded,
|
2023-07-29 19:16:33 +00:00
|
|
|
};
|
|
|
|
|
2024-06-16 14:08:31 +00:00
|
|
|
State state { State::NotDecoded };
|
2023-03-05 02:37:11 +00:00
|
|
|
|
2024-06-16 14:08:31 +00:00
|
|
|
RefPtr<Gfx::Bitmap> rgb_bitmap;
|
|
|
|
RefPtr<Gfx::CMYKBitmap> cmyk_bitmap;
|
2023-03-05 02:37:11 +00:00
|
|
|
|
2024-06-16 14:08:31 +00:00
|
|
|
ReadonlyBytes data;
|
|
|
|
Vector<u8> icc_data;
|
2023-03-05 02:37:11 +00:00
|
|
|
|
2024-06-16 14:08:31 +00:00
|
|
|
JPEGLoadingContext(ReadonlyBytes data)
|
|
|
|
: data(data)
|
|
|
|
{
|
2023-03-05 02:37:11 +00:00
|
|
|
}
|
|
|
|
|
2024-06-16 14:08:31 +00:00
|
|
|
ErrorOr<void> decode();
|
|
|
|
};
|
2020-12-20 15:04:29 +00:00
|
|
|
|
2024-06-16 14:08:31 +00:00
|
|
|
struct JPEGErrorManager : jpeg_error_mgr {
|
|
|
|
jmp_buf setjmp_buffer {};
|
|
|
|
};
|
2020-05-18 07:58:27 +00:00
|
|
|
|
2024-06-16 14:08:31 +00:00
|
|
|
ErrorOr<void> JPEGLoadingContext::decode()
|
2024-01-08 02:49:03 +00:00
|
|
|
{
|
2024-06-16 14:08:31 +00:00
|
|
|
struct jpeg_decompress_struct cinfo;
|
|
|
|
struct JPEGErrorManager jerr;
|
|
|
|
cinfo.err = jpeg_std_error(&jerr);
|
2024-01-08 02:49:03 +00:00
|
|
|
|
2024-06-16 14:08:31 +00:00
|
|
|
jpeg_source_mgr source_manager {};
|
2024-01-08 02:49:03 +00:00
|
|
|
|
2024-06-16 14:08:31 +00:00
|
|
|
if (setjmp(jerr.setjmp_buffer)) {
|
|
|
|
jpeg_destroy_decompress(&cinfo);
|
|
|
|
state = State::Error;
|
|
|
|
return Error::from_string_literal("Failed to decode JPEG");
|
2024-01-08 02:49:03 +00:00
|
|
|
}
|
|
|
|
|
2024-06-16 14:08:31 +00:00
|
|
|
jerr.error_exit = [](j_common_ptr cinfo) {
|
|
|
|
char buffer[JMSG_LENGTH_MAX];
|
|
|
|
(*cinfo->err->format_message)(cinfo, buffer);
|
|
|
|
dbgln("JPEG error: {}", buffer);
|
|
|
|
longjmp(static_cast<JPEGErrorManager*>(cinfo->err)->setjmp_buffer, 1);
|
|
|
|
};
|
|
|
|
jpeg_create_decompress(&cinfo);
|
|
|
|
|
|
|
|
source_manager.next_input_byte = data.data();
|
|
|
|
source_manager.bytes_in_buffer = data.size();
|
|
|
|
source_manager.init_source = [](j_decompress_ptr) {};
|
|
|
|
source_manager.fill_input_buffer = [](j_decompress_ptr) -> boolean { return false; };
|
|
|
|
source_manager.skip_input_data = [](j_decompress_ptr context, long num_bytes) {
|
|
|
|
if (num_bytes > static_cast<long>(context->src->bytes_in_buffer)) {
|
|
|
|
context->src->bytes_in_buffer = 0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
context->src->next_input_byte += num_bytes;
|
|
|
|
context->src->bytes_in_buffer -= num_bytes;
|
|
|
|
};
|
|
|
|
source_manager.resync_to_restart = jpeg_resync_to_restart;
|
|
|
|
source_manager.term_source = [](j_decompress_ptr) {};
|
2023-02-20 01:28:39 +00:00
|
|
|
|
2024-06-16 14:08:31 +00:00
|
|
|
cinfo.src = &source_manager;
|
2023-02-20 01:28:39 +00:00
|
|
|
|
2024-06-16 14:08:31 +00:00
|
|
|
jpeg_save_markers(&cinfo, JPEG_APP0 + 2, 0xFFFF);
|
|
|
|
if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) {
|
|
|
|
jpeg_destroy_decompress(&cinfo);
|
|
|
|
return Error::from_string_literal("Failed to read JPEG header");
|
2023-02-20 01:28:39 +00:00
|
|
|
}
|
|
|
|
|
2024-06-16 14:08:31 +00:00
|
|
|
if (cinfo.jpeg_color_space == JCS_CMYK || cinfo.jpeg_color_space == JCS_YCCK) {
|
|
|
|
cinfo.out_color_space = JCS_CMYK;
|
|
|
|
} else {
|
|
|
|
cinfo.out_color_space = JCS_EXT_BGRX;
|
2023-02-20 01:28:39 +00:00
|
|
|
}
|
|
|
|
|
2024-06-16 14:08:31 +00:00
|
|
|
jpeg_start_decompress(&cinfo);
|
2024-10-12 15:53:41 +00:00
|
|
|
bool could_read_all_scanlines = true;
|
2020-07-15 07:46:29 +00:00
|
|
|
|
2024-06-16 14:08:31 +00:00
|
|
|
if (cinfo.out_color_space == JCS_EXT_BGRX) {
|
|
|
|
rgb_bitmap = TRY(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, { static_cast<int>(cinfo.output_width), static_cast<int>(cinfo.output_height) }));
|
|
|
|
while (cinfo.output_scanline < cinfo.output_height) {
|
|
|
|
auto* row_ptr = (u8*)rgb_bitmap->scanline(cinfo.output_scanline);
|
2024-10-12 15:53:41 +00:00
|
|
|
auto out_size = jpeg_read_scanlines(&cinfo, &row_ptr, 1);
|
|
|
|
if (cinfo.output_scanline < cinfo.output_height && out_size == 0) {
|
|
|
|
dbgln("JPEG Warning: Decoding produced no more scanlines in scanline {}/{}.", cinfo.output_scanline, cinfo.output_height);
|
|
|
|
could_read_all_scanlines = false;
|
|
|
|
break;
|
|
|
|
}
|
2023-02-20 01:28:39 +00:00
|
|
|
}
|
2024-06-16 14:08:31 +00:00
|
|
|
} else {
|
|
|
|
cmyk_bitmap = TRY(CMYKBitmap::create_with_size({ static_cast<int>(cinfo.output_width), static_cast<int>(cinfo.output_height) }));
|
|
|
|
while (cinfo.output_scanline < cinfo.output_height) {
|
|
|
|
auto* row_ptr = (u8*)cmyk_bitmap->scanline(cinfo.output_scanline);
|
2024-10-12 15:53:41 +00:00
|
|
|
auto out_size = jpeg_read_scanlines(&cinfo, &row_ptr, 1);
|
|
|
|
if (cinfo.output_scanline < cinfo.output_height && out_size == 0) {
|
|
|
|
dbgln("JPEG Warning: Decoding produced no more scanlines in scanline {}/{}.", cinfo.output_scanline, cinfo.output_height);
|
|
|
|
could_read_all_scanlines = false;
|
|
|
|
break;
|
|
|
|
}
|
2020-05-18 07:58:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-16 14:08:31 +00:00
|
|
|
JOCTET* icc_data_ptr = nullptr;
|
|
|
|
unsigned int icc_data_length = 0;
|
|
|
|
if (jpeg_read_icc_profile(&cinfo, &icc_data_ptr, &icc_data_length)) {
|
|
|
|
icc_data.resize(icc_data_length);
|
|
|
|
memcpy(icc_data.data(), icc_data_ptr, icc_data_length);
|
|
|
|
free(icc_data_ptr);
|
2023-01-26 20:15:50 +00:00
|
|
|
}
|
2023-07-14 03:39:10 +00:00
|
|
|
|
2024-10-12 15:53:41 +00:00
|
|
|
if (could_read_all_scanlines)
|
|
|
|
jpeg_finish_decompress(&cinfo);
|
|
|
|
else
|
|
|
|
jpeg_abort_decompress(&cinfo);
|
2024-06-16 14:08:31 +00:00
|
|
|
jpeg_destroy_decompress(&cinfo);
|
2023-02-20 04:37:30 +00:00
|
|
|
|
2024-06-16 14:08:31 +00:00
|
|
|
if (cmyk_bitmap && !rgb_bitmap)
|
|
|
|
rgb_bitmap = TRY(cmyk_bitmap->to_low_quality_rgb());
|
2023-02-20 04:37:30 +00:00
|
|
|
|
2024-06-16 14:08:31 +00:00
|
|
|
state = State::Decoded;
|
2022-12-22 07:00:54 +00:00
|
|
|
return {};
|
2020-05-18 07:58:27 +00:00
|
|
|
}
|
|
|
|
|
2023-10-29 10:31:04 +00:00
|
|
|
JPEGImageDecoderPlugin::JPEGImageDecoderPlugin(NonnullOwnPtr<JPEGLoadingContext> context)
|
|
|
|
: m_context(move(context))
|
2020-05-18 07:58:27 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2023-02-18 21:09:16 +00:00
|
|
|
JPEGImageDecoderPlugin::~JPEGImageDecoderPlugin() = default;
|
2020-05-18 07:58:27 +00:00
|
|
|
|
2023-02-18 21:09:16 +00:00
|
|
|
IntSize JPEGImageDecoderPlugin::size()
|
2020-05-18 07:58:27 +00:00
|
|
|
{
|
2024-06-16 14:08:31 +00:00
|
|
|
if (m_context->state == JPEGLoadingContext::State::NotDecoded)
|
|
|
|
(void)frame(0);
|
|
|
|
|
|
|
|
if (m_context->state == JPEGLoadingContext::State::Error)
|
|
|
|
return {};
|
|
|
|
if (m_context->rgb_bitmap)
|
|
|
|
return m_context->rgb_bitmap->size();
|
|
|
|
if (m_context->cmyk_bitmap)
|
|
|
|
return m_context->cmyk_bitmap->size();
|
|
|
|
return {};
|
2020-05-18 07:58:27 +00:00
|
|
|
}
|
|
|
|
|
2023-02-26 18:02:50 +00:00
|
|
|
bool JPEGImageDecoderPlugin::sniff(ReadonlyBytes data)
|
2023-01-20 08:13:14 +00:00
|
|
|
{
|
|
|
|
return data.size() > 3
|
|
|
|
&& data.data()[0] == 0xFF
|
|
|
|
&& data.data()[1] == 0xD8
|
|
|
|
&& data.data()[2] == 0xFF;
|
|
|
|
}
|
|
|
|
|
2023-02-18 21:09:16 +00:00
|
|
|
ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> JPEGImageDecoderPlugin::create(ReadonlyBytes data)
|
2023-11-15 17:58:06 +00:00
|
|
|
{
|
2024-06-16 14:08:31 +00:00
|
|
|
return adopt_own(*new JPEGImageDecoderPlugin(make<JPEGLoadingContext>(data)));
|
2020-05-18 07:58:27 +00:00
|
|
|
}
|
|
|
|
|
2023-07-02 21:20:06 +00:00
|
|
|
ErrorOr<ImageFrameDescriptor> JPEGImageDecoderPlugin::frame(size_t index, Optional<IntSize>)
|
2020-05-18 07:58:27 +00:00
|
|
|
{
|
2021-11-20 13:29:33 +00:00
|
|
|
if (index > 0)
|
2023-02-18 21:09:16 +00:00
|
|
|
return Error::from_string_literal("JPEGImageDecoderPlugin: Invalid frame index");
|
2021-11-18 12:47:29 +00:00
|
|
|
|
2023-02-18 21:09:16 +00:00
|
|
|
if (m_context->state == JPEGLoadingContext::State::Error)
|
|
|
|
return Error::from_string_literal("JPEGImageDecoderPlugin: Decoding failed");
|
2021-11-18 12:47:29 +00:00
|
|
|
|
2024-06-16 14:08:31 +00:00
|
|
|
if (m_context->state < JPEGLoadingContext::State::Decoded) {
|
|
|
|
TRY(m_context->decode());
|
|
|
|
m_context->state = JPEGLoadingContext::State::Decoded;
|
2021-11-18 12:47:29 +00:00
|
|
|
}
|
|
|
|
|
2024-06-16 14:08:31 +00:00
|
|
|
return ImageFrameDescriptor { m_context->rgb_bitmap, 0 };
|
2020-05-18 07:58:27 +00:00
|
|
|
}
|
2021-07-26 23:29:50 +00:00
|
|
|
|
2024-01-21 20:25:00 +00:00
|
|
|
Optional<Metadata const&> JPEGImageDecoderPlugin::metadata()
|
|
|
|
{
|
|
|
|
return OptionalNone {};
|
|
|
|
}
|
|
|
|
|
2023-02-18 21:09:16 +00:00
|
|
|
ErrorOr<Optional<ReadonlyBytes>> JPEGImageDecoderPlugin::icc_data()
|
2023-01-26 12:23:59 +00:00
|
|
|
{
|
2024-06-16 14:08:31 +00:00
|
|
|
if (m_context->state == JPEGLoadingContext::State::NotDecoded)
|
|
|
|
(void)frame(0);
|
|
|
|
|
|
|
|
if (!m_context->icc_data.is_empty())
|
|
|
|
return m_context->icc_data;
|
2023-01-26 12:23:59 +00:00
|
|
|
return OptionalNone {};
|
|
|
|
}
|
|
|
|
|
2024-01-08 02:49:03 +00:00
|
|
|
NaturalFrameFormat JPEGImageDecoderPlugin::natural_frame_format() const
|
|
|
|
{
|
2024-06-16 14:08:31 +00:00
|
|
|
if (m_context->state == JPEGLoadingContext::State::NotDecoded)
|
|
|
|
(void)const_cast<JPEGImageDecoderPlugin&>(*this).frame(0);
|
|
|
|
|
|
|
|
if (m_context->cmyk_bitmap)
|
2024-01-08 03:18:18 +00:00
|
|
|
return NaturalFrameFormat::CMYK;
|
|
|
|
return NaturalFrameFormat::RGB;
|
2024-01-08 02:49:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ErrorOr<NonnullRefPtr<CMYKBitmap>> JPEGImageDecoderPlugin::cmyk_frame()
|
|
|
|
{
|
2024-06-16 14:08:31 +00:00
|
|
|
if (m_context->state == JPEGLoadingContext::State::NotDecoded)
|
|
|
|
(void)frame(0);
|
2024-01-08 02:49:03 +00:00
|
|
|
|
2024-06-16 14:08:31 +00:00
|
|
|
if (m_context->state == JPEGLoadingContext::State::Error)
|
|
|
|
return Error::from_string_literal("JPEGImageDecoderPlugin: Decoding failed");
|
|
|
|
if (!m_context->cmyk_bitmap)
|
|
|
|
return Error::from_string_literal("JPEGImageDecoderPlugin: No CMYK data available");
|
2024-01-08 02:49:03 +00:00
|
|
|
return *m_context->cmyk_bitmap;
|
|
|
|
}
|
|
|
|
|
2020-05-18 07:58:27 +00:00
|
|
|
}
|